vscode-apollo 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ // @ts-check
2
+ const { pathToFileURL } = require("node:url");
3
+
4
+ /** @import { ResolveContext, ResolutionResult, LoadResult, ImportContext } from "./cache-busting-resolver.types" */
5
+
6
+ /**
7
+ * @param {string} specifier
8
+ * @returns {string}
9
+ */
10
+ function bustFileName(specifier) {
11
+ const url = pathToFileURL(specifier);
12
+ url.pathname = url.pathname + "." + Date.now() + ".js";
13
+ return url.toString();
14
+ }
15
+
16
+ /**
17
+ *
18
+ * @param {string} specifier
19
+ * @param {ResolveContext} context
20
+ * @param {(specifier: string,context: ResolveContext) => Promise<ResolutionResult>} nextResolve
21
+ * @returns {Promise<ResolutionResult>}
22
+ */
23
+ async function resolve(specifier, context, nextResolve) {
24
+ if (context.importAttributes.as !== "cachebust") {
25
+ return nextResolve(specifier, context);
26
+ }
27
+ if (context.importAttributes.format) {
28
+ // no need to resolve at all, we have all necessary information
29
+ return {
30
+ url: bustFileName(specifier),
31
+ format: context.importAttributes.format,
32
+ importAttributes: context.importAttributes,
33
+ shortCircuit: true,
34
+ };
35
+ }
36
+ const result = await nextResolve(specifier, context);
37
+ return {
38
+ ...result,
39
+ url: bustFileName(result.url),
40
+ importAttributes: context.importAttributes,
41
+ };
42
+ }
43
+
44
+ /**
45
+ *
46
+ * @param {string} url
47
+ * @param {ImportContext} context
48
+ * @param {(url: string, context: ImportContext) => Promise<LoadResult>} nextLoad
49
+ * @returns {Promise<LoadResult>}
50
+ */
51
+ async function load(url, context, nextLoad) {
52
+ if (context.importAttributes.as !== "cachebust") {
53
+ return nextLoad(url, context);
54
+ }
55
+ return {
56
+ format: context.format || "module",
57
+ shortCircuit: true,
58
+ source: context.importAttributes.contents,
59
+ };
60
+ }
61
+
62
+ module.exports = {
63
+ resolve,
64
+ load,
65
+ };
@@ -0,0 +1,45 @@
1
+ import { pathToFileURL } from "node:url";
2
+
3
+ export type ImportAttributes =
4
+ | {
5
+ as: "cachebust";
6
+ contents: string;
7
+ format?: Format;
8
+ }
9
+ | { as?: undefined };
10
+
11
+ type Format =
12
+ | "builtin"
13
+ | "commonjs"
14
+ | "json"
15
+ | "module"
16
+ | "wasm"
17
+ | null
18
+ | undefined;
19
+
20
+ export interface ResolveContext {
21
+ conditions: string[];
22
+ importAttributes: ImportAttributes;
23
+ parentURL?: string;
24
+ }
25
+
26
+ export interface ImportContext {
27
+ conditions: string[];
28
+ importAttributes: ImportAttributes;
29
+ format: Format;
30
+ }
31
+
32
+ export interface ResolutionResult {
33
+ format: Format;
34
+ importAttributes?: ImportAttributes;
35
+ shortCircuit?: boolean;
36
+ url: string;
37
+ }
38
+
39
+ export interface LoadResult {
40
+ format: Format;
41
+ shortCircuit?: boolean;
42
+ source: string;
43
+ }
44
+
45
+ export {};
@@ -25,7 +25,10 @@ function ignoredFieldWarning(
25
25
  Debug.warning(getMessage(ctx.path.join(".")));
26
26
  }
27
27
  })
28
- .optional();
28
+ .optional()
29
+ .describe(
30
+ `This option is no longer supported, please remove it from your configuration file.`,
31
+ );
29
32
  }
30
33
  export interface Context {
31
34
  apiKey?: string;
@@ -34,53 +37,113 @@ export interface Context {
34
37
  }
35
38
  const contextStore = new AsyncLocalStorage<Context>();
36
39
 
37
- const studioServiceConfig = z.string();
40
+ const NAME_DESCRIPTION =
41
+ "The name your project will be referred to by the Apollo GraphQL extension.";
38
42
 
39
- const remoteServiceConfig = z.object({
40
- name: z.string().optional(),
41
- url: z.string(),
42
- headers: z.record(z.string()).default({}),
43
- skipSSLValidation: z.boolean().default(false),
44
- });
43
+ const studioServiceConfig = z
44
+ .string()
45
+ .describe(
46
+ "The name of the Apollo Studio graph to use. Alternatively pass in an object to configure a local schema.",
47
+ );
48
+
49
+ const remoteServiceConfig = z
50
+ .object({
51
+ name: z.string().optional().describe(NAME_DESCRIPTION),
52
+ url: z
53
+ .string()
54
+ .describe(
55
+ "URL of a GraphQL to use for the GraphQL Schema for this project. Needs introspection enabled.",
56
+ ),
57
+ headers: z
58
+ .record(z.string())
59
+ .default({})
60
+ .describe("Additional headers to send to the server."),
61
+ skipSSLValidation: z
62
+ .boolean()
63
+ .default(false)
64
+ .describe(
65
+ "Skip SSL validation. May be required for self-signed certificates.",
66
+ ),
67
+ })
68
+ .describe("Configuration for using a local schema from a URL.");
45
69
  export type RemoteServiceConfig = z.infer<typeof remoteServiceConfig>;
46
70
 
47
- const localServiceConfig = z.object({
48
- name: z.string().optional(),
49
- localSchemaFile: z.union([z.string(), z.array(z.string())]),
50
- });
71
+ const LOCAL_SCHEMA_FILE_DESCRIPTION =
72
+ "Path to a local schema file to use as GraphQL Schema for this project. Can be a string or an array of strings to merge multiple partial schemas into one.";
73
+ const localServiceConfig = z
74
+ .object({
75
+ name: z.string().optional().describe(NAME_DESCRIPTION),
76
+ localSchemaFile: z
77
+ .union([
78
+ z.string().describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
79
+ z.array(z.string()).describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
80
+ ])
81
+ .describe(LOCAL_SCHEMA_FILE_DESCRIPTION),
82
+ })
83
+ .describe("Configuration for using a local schema from a file.");
51
84
  export type LocalServiceConfig = z.infer<typeof localServiceConfig>;
52
85
 
53
- const clientServiceConfig = z.preprocess(
54
- (value) => value || contextStore.getStore()?.serviceName,
55
- z.union([studioServiceConfig, remoteServiceConfig, localServiceConfig]),
56
- );
86
+ const clientServiceConfig = z
87
+ .preprocess(
88
+ (value) => value || contextStore.getStore()?.serviceName,
89
+ z.union([studioServiceConfig, remoteServiceConfig, localServiceConfig]),
90
+ )
91
+ .describe(
92
+ "A string to refer to a graph in Apollo Studio, or an object for a local schema.",
93
+ );
57
94
  export type ClientServiceConfig = z.infer<typeof clientServiceConfig>;
58
95
 
59
- const clientConfig = z.object({
60
- service: clientServiceConfig,
61
- validationRules: z
62
- .union([
63
- z.array(z.custom<ValidationRule>()),
64
- z.function().args(z.custom<ValidationRule>()).returns(z.boolean()),
65
- ])
66
- .optional(),
67
- // maybe shared with rover?
68
- includes: z.array(z.string()).optional(),
69
- // maybe shared with rover?
70
- excludes: z.array(z.string()).default(["**/node_modules", "**/__tests__"]),
71
- // maybe shared with rover?
72
- tagName: z.string().default("gql"),
73
- // removed:
74
- clientOnlyDirectives: ignoredFieldWarning(),
75
- clientSchemaDirectives: ignoredFieldWarning(),
76
- statsWindow: ignoredFieldWarning(),
77
- name: ignoredFieldWarning(),
78
- referenceId: ignoredFieldWarning(),
79
- version: ignoredFieldWarning(),
80
- });
96
+ const VALIDATION_RULES_DESCRIPTION =
97
+ "Additional validation rules to check for. To use this feature, please use a configuration file format that allows passing JavaScript objects.";
98
+ export const clientConfig = z
99
+ .object({
100
+ service: clientServiceConfig,
101
+ validationRules: z
102
+ .union([
103
+ z
104
+ .array(z.custom<ValidationRule>())
105
+ .describe(VALIDATION_RULES_DESCRIPTION),
106
+ z
107
+ .function()
108
+ .args(z.custom<ValidationRule>())
109
+ .returns(z.boolean())
110
+ .describe(VALIDATION_RULES_DESCRIPTION),
111
+ ])
112
+ .optional()
113
+ .describe(VALIDATION_RULES_DESCRIPTION),
114
+ // maybe shared with rover?
115
+ includes: z
116
+ .array(z.string())
117
+ .optional()
118
+ .describe(
119
+ "An array of glob patterns this project should be active on. The Apollo GraphQL extension will only support IntelliSense-like features in files listed here.",
120
+ ),
121
+ // maybe shared with rover?
122
+ excludes: z
123
+ .array(z.string())
124
+ .default(["**/node_modules", "**/__tests__"])
125
+ .describe(
126
+ "Files to exclude from this project. The Apollo GraphQL extension will not provide IntelliSense-like features in these files.",
127
+ ),
128
+ // maybe shared with rover?
129
+ tagName: z
130
+ .string()
131
+ .default("gql")
132
+ .describe(
133
+ "The name of the template literal tag or function used in JavaScript files to declare GraphQL Documents.",
134
+ ),
135
+ // removed:
136
+ clientOnlyDirectives: ignoredFieldWarning(),
137
+ clientSchemaDirectives: ignoredFieldWarning(),
138
+ statsWindow: ignoredFieldWarning(),
139
+ name: ignoredFieldWarning(),
140
+ referenceId: ignoredFieldWarning(),
141
+ version: ignoredFieldWarning(),
142
+ })
143
+ .describe("Configuration for a Client project.");
81
144
  export type ClientConfigFormat = z.infer<typeof clientConfig>;
82
145
 
83
- const roverConfig = z.object({
146
+ export const roverConfig = z.object({
84
147
  bin: z
85
148
  .preprocess(
86
149
  (val) => val || which.sync("rover", { nothrow: true }) || undefined,
@@ -104,8 +167,9 @@ const roverConfig = z.object({
104
167
  message:
105
168
  "Rover binary is not marked as an executable. If you are using OS X or Linux, ensure to set the executable bit.",
106
169
  },
107
- ),
108
- profile: z.string().optional(),
170
+ )
171
+ .describe("The path to your Rover binary. If omitted, will look in PATH."),
172
+ profile: z.string().optional().describe("The name of the profile to use."),
109
173
  supergraphConfig: z
110
174
  .preprocess((value) => {
111
175
  if (value !== undefined) return value;
@@ -124,24 +188,36 @@ const roverConfig = z.object({
124
188
  });
125
189
  type RoverConfigFormat = z.infer<typeof roverConfig>;
126
190
 
127
- const engineConfig = z.object({
128
- endpoint: z
129
- .string()
130
- .default(
131
- process.env.APOLLO_ENGINE_ENDPOINT ||
132
- "https://graphql.api.apollographql.com/api/graphql",
133
- ),
134
- apiKey: z.preprocess(
135
- (val) => val || contextStore.getStore()?.apiKey,
136
- z.string().optional(),
137
- ),
138
- });
191
+ export const engineConfig = z
192
+ .object({
193
+ endpoint: z
194
+ .string()
195
+ .default(
196
+ process.env.APOLLO_ENGINE_ENDPOINT ||
197
+ "https://graphql.api.apollographql.com/api/graphql",
198
+ )
199
+ .describe("The URL of the Apollo Studio API."),
200
+ apiKey: z
201
+ .preprocess(
202
+ (val) => val || contextStore.getStore()?.apiKey,
203
+ z.string().optional(),
204
+ )
205
+ .describe(
206
+ "The API key to use for Apollo Studio. If possible, use a `.env` file or `.env.local` file instead to store secrets like this.",
207
+ ),
208
+ })
209
+ .describe("Network configuration for Apollo Studio API.");
139
210
  export type EngineConfig = z.infer<typeof engineConfig>;
140
211
 
141
- const baseConfig = z.object({
212
+ export const baseConfig = z.object({
142
213
  engine: engineConfig.default({}),
143
- client: z.unknown().optional(),
144
- rover: z.unknown().optional(),
214
+ client: z.unknown().optional().describe(clientConfig.description!),
215
+ ...ifRoverAvailable(
216
+ {
217
+ rover: z.unknown().optional(),
218
+ },
219
+ {},
220
+ ),
145
221
  service: ignoredFieldWarning(
146
222
  (path) =>
147
223
  `Service-type projects are no longer supported. Please remove the "${path}" field from your configuration file.`,
@@ -268,7 +344,7 @@ export abstract class ApolloConfig {
268
344
  get configDirURI() {
269
345
  // if the filepath has a _file_ in it, then we get its dir
270
346
  return this.configURI &&
271
- this.configURI.fsPath.match(/\.(ts|js|cjs|mjs|json)$/i)
347
+ this.configURI.fsPath.match(/\.(ts|js|cjs|mjs|yaml|yml|json)$/i)
272
348
  ? URI.parse(dirname(this.configURI.fsPath))
273
349
  : this.configURI;
274
350
  }
@@ -1,4 +1,4 @@
1
- import { cosmiconfig, defaultLoaders } from "cosmiconfig";
1
+ import { cosmiconfig, defaultLoaders, Loader } from "cosmiconfig";
2
2
  import { dirname, resolve } from "path";
3
3
  import { readFileSync, existsSync, lstatSync } from "fs";
4
4
  import {
@@ -9,18 +9,22 @@ import {
9
9
  import { getServiceFromKey } from "./utils";
10
10
  import { URI } from "vscode-uri";
11
11
  import { Debug } from "../utilities";
12
- import { loadTs } from "./loadTsConfig";
12
+ import { ParseError, parse as parseJsonC } from "jsonc-parser";
13
+ import { loadJs, loadTs } from "./loadTsConfig";
13
14
 
14
15
  // config settings
15
16
  const MODULE_NAME = "apollo";
16
- const defaultFileNames = [
17
+ export const supportedConfigFileNames = [
17
18
  "package.json",
18
19
  `${MODULE_NAME}.config.js`,
19
20
  `${MODULE_NAME}.config.ts`,
20
21
  `${MODULE_NAME}.config.mjs`,
21
22
  `${MODULE_NAME}.config.cjs`,
23
+ `${MODULE_NAME}.config.yaml`,
24
+ `${MODULE_NAME}.config.yml`,
25
+ `${MODULE_NAME}.config.json`,
22
26
  ];
23
- const envFileNames = [".env", ".env.local"];
27
+ export const envFileNames = [".env", ".env.local"];
24
28
 
25
29
  export const keyEnvVar = "APOLLO_KEY";
26
30
 
@@ -39,14 +43,31 @@ export type ConfigResult<T> = {
39
43
 
40
44
  // XXX load .env files automatically
41
45
 
46
+ const loadJsonc: Loader = (filename, contents) => {
47
+ const errors: ParseError[] = [];
48
+ try {
49
+ return parseJsonC(contents, errors);
50
+ } finally {
51
+ if (errors.length) {
52
+ Debug.error(
53
+ `Error parsing JSONC file ${filename}, file might not be valid JSONC`,
54
+ );
55
+ }
56
+ }
57
+ };
58
+
42
59
  export async function loadConfig({
43
60
  configPath,
44
61
  }: LoadConfigSettings): Promise<ApolloConfig | null> {
45
62
  const explorer = cosmiconfig(MODULE_NAME, {
46
- searchPlaces: defaultFileNames,
63
+ searchPlaces: supportedConfigFileNames,
47
64
  loaders: {
48
65
  ...defaultLoaders,
49
- [".ts"]: loadTs,
66
+ ".ts": loadTs,
67
+ ".mjs": loadJs,
68
+ ".cjs": loadJs,
69
+ ".js": loadJs,
70
+ ".json": loadJsonc,
50
71
  },
51
72
  });
52
73
 
@@ -1,56 +1,78 @@
1
- import { Loader, defaultLoaders } from "cosmiconfig";
1
+ import { Loader } from "cosmiconfig";
2
2
  import { dirname } from "node:path";
3
- import { rm, writeFile } from "node:fs/promises";
4
- import { existsSync } from "node:fs";
5
-
3
+ import typescript from "typescript";
4
+ import { pathToFileURL } from "node:url";
5
+ import { register } from "node:module";
6
+ import { ImportAttributes } from "./cache-busting-resolver.types";
6
7
  // implementation based on https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/src/loaders.ts
7
8
  // Copyright (c) 2015 David Clark licensed MIT. Full license can be found here:
8
9
  // https://github.com/cosmiconfig/cosmiconfig/blob/a5a842547c13392ebb89a485b9e56d9f37e3cbd3/LICENSE
9
10
 
10
- let typescript: typeof import("typescript");
11
+ if (process.env.JEST_WORKER_ID === undefined) {
12
+ register(
13
+ pathToFileURL(require.resolve("./config/cache-busting-resolver.js")),
14
+ );
15
+ } else {
16
+ register(pathToFileURL(require.resolve("./cache-busting-resolver.js")));
17
+ }
18
+
11
19
  export const loadTs: Loader = async function loadTs(filepath, content) {
12
20
  try {
13
- return await defaultLoaders[".ts"](filepath, content);
21
+ return await load(filepath, content, "module", {
22
+ module: typescript.ModuleKind.ES2022,
23
+ });
14
24
  } catch (error) {
15
25
  if (
16
- !(error instanceof Error) ||
17
- !error.message.includes("module is not defined")
18
- )
26
+ error instanceof Error &&
27
+ // [ERROR] ReferenceError: module is not defined in ES module scope
28
+ error.message.includes("module is not defined")
29
+ ) {
30
+ return await load(filepath, content, "commonjs", {
31
+ module: typescript.ModuleKind.CommonJS,
32
+ });
33
+ } else {
19
34
  throw error;
35
+ }
20
36
  }
37
+ };
21
38
 
22
- if (typescript === undefined) {
23
- typescript = await import("typescript");
24
- }
25
- const compiledFilepath = `${filepath.slice(0, -2)}cjs`;
39
+ async function load(
40
+ filepath: string,
41
+ content: string,
42
+ type: "module" | "commonjs",
43
+ compilerOptions: Partial<import("typescript").CompilerOptions>,
44
+ ) {
26
45
  let transpiledContent;
46
+
27
47
  try {
28
- try {
29
- const config = resolveTsConfig(dirname(filepath)) ?? {};
30
- config.compilerOptions = {
31
- ...config.compilerOptions,
32
- module: typescript.ModuleKind.CommonJS,
33
- moduleResolution: typescript.ModuleResolutionKind.Bundler,
34
- target: typescript.ScriptTarget.ES2022,
35
- noEmit: false,
36
- };
37
- transpiledContent = typescript.transpileModule(
38
- content,
39
- config,
40
- ).outputText;
41
- await writeFile(compiledFilepath, transpiledContent);
42
- } catch (error: any) {
43
- error.message = `TypeScript Error in ${filepath}:\n${error.message}`;
44
- throw error;
45
- }
46
- // eslint-disable-next-line @typescript-eslint/return-await
47
- return await defaultLoaders[".js"](compiledFilepath, transpiledContent);
48
- } finally {
49
- if (existsSync(compiledFilepath)) {
50
- await rm(compiledFilepath);
51
- }
48
+ const config = resolveTsConfig(dirname(filepath)) ?? {};
49
+ config.compilerOptions = {
50
+ ...config.compilerOptions,
51
+
52
+ moduleResolution: typescript.ModuleResolutionKind.Bundler,
53
+ target: typescript.ScriptTarget.ES2022,
54
+ noEmit: false,
55
+ ...compilerOptions,
56
+ };
57
+ transpiledContent = typescript.transpileModule(content, config).outputText;
58
+ } catch (error: any) {
59
+ error.message = `TypeScript Error in ${filepath}:\n${error.message}`;
60
+ throw error;
52
61
  }
53
- };
62
+ // eslint-disable-next-line @typescript-eslint/return-await
63
+ const imported = await import(
64
+ filepath,
65
+ //@ts-ignore
66
+ {
67
+ with: {
68
+ as: "cachebust",
69
+ contents: transpiledContent,
70
+ format: type,
71
+ } satisfies ImportAttributes,
72
+ }
73
+ );
74
+ return imported.default;
75
+ }
54
76
 
55
77
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
78
  function resolveTsConfig(directory: string): any {
@@ -68,3 +90,17 @@ function resolveTsConfig(directory: string): any {
68
90
  }
69
91
  return;
70
92
  }
93
+
94
+ export const loadJs: Loader = async function loadJs(filepath, contents) {
95
+ return (
96
+ await import(
97
+ filepath, // @ts-ignore
98
+ {
99
+ with: {
100
+ as: "cachebust",
101
+ contents,
102
+ } satisfies ImportAttributes,
103
+ }
104
+ )
105
+ ).default;
106
+ };
@@ -18,7 +18,13 @@ import { TextDocument } from "vscode-languageserver-textdocument";
18
18
 
19
19
  import type { LoadingHandler } from "../loadingHandler";
20
20
  import { FileSet } from "../fileSet";
21
- import { ApolloConfig, ClientConfig, RoverConfig } from "../config";
21
+ import {
22
+ ApolloConfig,
23
+ ClientConfig,
24
+ envFileNames,
25
+ RoverConfig,
26
+ supportedConfigFileNames,
27
+ } from "../config";
22
28
  import type { ProjectStats } from "../../messages";
23
29
 
24
30
  export type DocumentUri = string;
@@ -67,13 +73,7 @@ export abstract class GraphQLProject {
67
73
 
68
74
  this.configFileSet = new FileSet({
69
75
  rootURI: this.rootURI,
70
- includes: [
71
- ".env",
72
- "apollo.config.js",
73
- "apollo.config.cjs",
74
- "apollo.config.mjs",
75
- "apollo.config.ts",
76
- ],
76
+ includes: supportedConfigFileNames.concat(envFileNames),
77
77
  excludes: [],
78
78
  });
79
79
 
@@ -24,6 +24,7 @@ import {
24
24
  SemanticTokensRegistrationType,
25
25
  SemanticTokensOptions,
26
26
  SemanticTokensRegistrationOptions,
27
+ DefinitionRequest,
27
28
  } from "vscode-languageserver/node";
28
29
  import cp from "node:child_process";
29
30
  import { GraphQLProjectConfig } from "../base";
@@ -281,6 +282,11 @@ export class RoverProject extends GraphQLProject {
281
282
  this.sendRequest(HoverRequest.type, virtualParams, token),
282
283
  );
283
284
 
285
+ onDefinition: GraphQLProject["onDefinition"] = async (params, token) =>
286
+ this.documents.insideVirtualDocument(params, (virtualParams) =>
287
+ this.sendRequest(DefinitionRequest.type, virtualParams, token),
288
+ );
289
+
284
290
  onUnhandledRequest: GraphQLProject["onUnhandledRequest"] = async (
285
291
  type,
286
292
  params,
@@ -9,7 +9,8 @@ import {
9
9
  FileEvent,
10
10
  } from "vscode-languageserver/node";
11
11
  import { TextDocument } from "vscode-languageserver-textdocument";
12
- import type { QuickPickItem } from "vscode";
12
+ import { type QuickPickItem } from "vscode";
13
+ import { basename } from "node:path";
13
14
  import { GraphQLWorkspace } from "./workspace";
14
15
  import { LanguageServerLoadingHandler } from "./loadingHandler";
15
16
  import { debounceHandler, Debug } from "./utilities";
@@ -23,6 +24,7 @@ import { isValidationError } from "zod-validation-error";
23
24
  import { GraphQLProject } from "./project/base";
24
25
  import type { LanguageIdExtensionMap } from "../tools/utilities/languageInformation";
25
26
  import { setLanguageIdExtensionMap } from "./utilities/languageIdForExtension";
27
+ import { envFileNames, supportedConfigFileNames } from "./config";
26
28
 
27
29
  export type InitializationOptions = {
28
30
  languageIdExtensionMap: LanguageIdExtensionMap;
@@ -190,14 +192,13 @@ documents.onDidClose(
190
192
  connection.onDidChangeWatchedFiles((params) => {
191
193
  const handledByProject = new Map<GraphQLProject, FileEvent[]>();
192
194
  for (const { uri, type } of params.changes) {
195
+ const fsPath = URI.parse(uri).fsPath;
196
+ const fileName = basename(fsPath);
193
197
  if (
194
- uri.endsWith("apollo.config.js") ||
195
- uri.endsWith("apollo.config.cjs") ||
196
- uri.endsWith("apollo.config.mjs") ||
197
- uri.endsWith("apollo.config.ts") ||
198
- uri.endsWith(".env")
198
+ supportedConfigFileNames.includes(fileName) ||
199
+ envFileNames.includes(fileName)
199
200
  ) {
200
- workspace.reloadProjectForConfig(uri);
201
+ workspace.reloadProjectForConfigOrCompanionFile(uri);
201
202
  }
202
203
 
203
204
  // Don't respond to changes in files that are currently open,
@@ -125,7 +125,7 @@ export class GraphQLWorkspace {
125
125
 
126
126
  */
127
127
  const apolloConfigFiles: string[] = globSync(
128
- "**/apollo.config.@(js|ts|cjs|mjs)",
128
+ "**/apollo.config.@(js|ts|cjs|mjs|yaml|yml|json)",
129
129
  {
130
130
  cwd: URI.parse(folder.uri).fsPath,
131
131
  absolute: true,
@@ -197,7 +197,7 @@ export class GraphQLWorkspace {
197
197
  });
198
198
  }
199
199
 
200
- async reloadProjectForConfig(configUri: DocumentUri) {
200
+ async reloadProjectForConfigOrCompanionFile(configUri: DocumentUri) {
201
201
  const configPath = dirname(URI.parse(configUri).fsPath);
202
202
  let config: ApolloConfig | null;
203
203
  let error;
@@ -215,9 +215,6 @@ export class GraphQLWorkspace {
215
215
  }
216
216
  // If project exists, update the config
217
217
  if (project && config) {
218
- if (equal(project.config.rawConfig, config.rawConfig)) {
219
- return;
220
- }
221
218
  await Promise.all(project.updateConfig(config));
222
219
  this.reloadService();
223
220
  }
@@ -52,7 +52,9 @@ export function getLanguageServerClient(
52
52
  documentSelector: supportedLanguageIds,
53
53
  synchronize: {
54
54
  fileEvents: [
55
- workspace.createFileSystemWatcher("**/.env?(.local)"),
55
+ workspace.createFileSystemWatcher(
56
+ "**/{.env?(.local),apollo.config.{json,yml,yaml}}",
57
+ ),
56
58
  workspace.createFileSystemWatcher(
57
59
  "**/*{" + supportedExtensions.join(",") + "}",
58
60
  ),