HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/shop.komma.nl/node_modules/apollo-language-server/src/workspace.ts
import {
  WorkspaceFolder,
  NotificationHandler,
  PublishDiagnosticsParams
} from "vscode-languageserver";
import { QuickPickItem } from "vscode";
import { GraphQLProject, DocumentUri } from "./project/base";
import { dirname } from "path";
import fg from "glob";
import {
  loadConfig,
  ApolloConfig,
  isClientConfig,
  ServiceConfig
} from "./config";
import { LanguageServerLoadingHandler } from "./loadingHandler";
import { ServiceID, SchemaTag, ClientIdentity } from "./engine";
import { GraphQLClientProject, isClientProject } from "./project/client";
import { GraphQLServiceProject } from "./project/service";
import URI from "vscode-uri";
import { Debug } from "./utilities";

export interface WorkspaceConfig {
  clientIdentity?: ClientIdentity;
}

export class GraphQLWorkspace {
  private _onDiagnostics?: NotificationHandler<PublishDiagnosticsParams>;
  private _onDecorations?: NotificationHandler<any>;
  private _onSchemaTags?: NotificationHandler<[ServiceID, SchemaTag[]]>;
  private _onConfigFilesFound?: NotificationHandler<ApolloConfig[]>;
  private _projectForFileCache: Map<string, GraphQLProject> = new Map();

  private projectsByFolderUri: Map<string, GraphQLProject[]> = new Map();

  constructor(
    private LanguageServerLoadingHandler: LanguageServerLoadingHandler,
    private config: WorkspaceConfig
  ) {}

  onDiagnostics(handler: NotificationHandler<PublishDiagnosticsParams>) {
    this._onDiagnostics = handler;
  }

  onDecorations(handler: NotificationHandler<any>) {
    this._onDecorations = handler;
  }

  onSchemaTags(handler: NotificationHandler<[ServiceID, SchemaTag[]]>) {
    this._onSchemaTags = handler;
  }

  onConfigFilesFound(handler: NotificationHandler<ApolloConfig[]>) {
    this._onConfigFilesFound = handler;
  }

  private createProject({
    config,
    folder
  }: {
    config: ApolloConfig;
    folder: WorkspaceFolder;
  }) {
    const { clientIdentity } = this.config;
    const project = isClientConfig(config)
      ? new GraphQLClientProject({
          config,
          loadingHandler: this.LanguageServerLoadingHandler,
          rootURI: URI.parse(folder.uri),
          clientIdentity
        })
      : new GraphQLServiceProject({
          config: config as ServiceConfig,
          loadingHandler: this.LanguageServerLoadingHandler,
          rootURI: URI.parse(folder.uri),
          clientIdentity
        });

    project.onDiagnostics(params => {
      this._onDiagnostics && this._onDiagnostics(params);
    });

    if (isClientProject(project)) {
      project.onDecorations(params => {
        this._onDecorations && this._onDecorations(params);
      });

      project.onSchemaTags(tags => {
        this._onSchemaTags && this._onSchemaTags(tags);
      });
    }

    // after a project has loaded, we do an initial validation to surface errors
    // on the start of the language server. Instead of doing this in the
    // base class which is used by codegen and other tools
    project.whenReady.then(() => project.validate());

    return project;
  }

  async addProjectsInFolder(folder: WorkspaceFolder) {
    // load all possible workspace projects (contains possible config)
    // see if we can move this detection to cosmiconfig
    /*

      - monorepo (GraphQLWorkspace) as WorkspaceFolder
        -- engine-api (GraphQLProject)
        -- engine-frontend (GraphQLProject)

      OR

      - vscode workspace (fullstack)
        -- ~/:user/client (GraphQLProject) as WorkspaceFolder
        -- ~/:user/server (GraphQLProject) as WorkspaceFolder

    */
    const apolloConfigFiles: string[] = fg.sync("**/apollo.config.@(js|ts)", {
      cwd: URI.parse(folder.uri).fsPath,
      absolute: true,
      ignore: "**/node_modules/**"
    });

    // only have unique possible folders
    const apolloConfigFolders = new Set<string>(apolloConfigFiles.map(dirname));

    // go from possible folders to known array of configs
    let foundConfigs: ApolloConfig[] = [];

    const projectConfigs = Array.from(apolloConfigFolders).map(configFolder =>
      loadConfig({ configPath: configFolder, requireConfig: true })
        .then(config => {
          if (config) {
            foundConfigs.push(config);
            const projectsForConfig = config.projects.map(projectConfig =>
              this.createProject({ config, folder })
            );

            const existingProjects =
              this.projectsByFolderUri.get(folder.uri) || [];

            this.projectsByFolderUri.set(folder.uri, [
              ...existingProjects,
              ...projectsForConfig
            ]);
          } else {
            Debug.error(
              `Workspace failed to load config from: ${configFolder}/`
            );
          }
        })
        .catch(error => Debug.error(error))
    );

    await Promise.all(projectConfigs);

    if (this._onConfigFilesFound) {
      this._onConfigFilesFound(foundConfigs);
    }
  }

  reloadService() {
    this._projectForFileCache.clear();
    this.projectsByFolderUri.forEach((projects, uri) => {
      this.projectsByFolderUri.set(
        uri,
        projects.map(project => {
          project.clearAllDiagnostics();
          return this.createProject({
            config: project.config,
            folder: { uri } as WorkspaceFolder
          });
        })
      );
    });
  }

  async reloadProjectForConfig(configUri: DocumentUri) {
    const configPath = dirname(URI.parse(configUri).fsPath);

    let config, error;
    try {
      config = await loadConfig({ configPath, requireConfig: true });
    } catch (e) {
      error = e;
    }

    const project = this.projectForFile(configUri);

    if (!config && this._onConfigFilesFound) {
      this._onConfigFilesFound(error);
    }
    // If project exists, update the config
    if (project && config) {
      await Promise.all(project.updateConfig(config));
      this.reloadService();
    }

    // If project doesn't exist (new config file), create the project and add to workspace
    if (!project && config) {
      const folderUri = URI.file(configPath).toString();

      const newProject = this.createProject({
        config,
        folder: { uri: folderUri } as WorkspaceFolder
      });

      const existingProjects = this.projectsByFolderUri.get(folderUri) || [];
      this.projectsByFolderUri.set(folderUri, [
        ...existingProjects,
        newProject
      ]);
      this.reloadService();
    }
  }

  updateSchemaTag(selection: QuickPickItem) {
    const serviceID = selection.detail;
    if (!serviceID) return;

    this.projectsByFolderUri.forEach(projects => {
      projects.forEach(project => {
        if (isClientProject(project) && project.serviceID === serviceID) {
          project.updateSchemaTag(selection.label);
        }
      });
    });
  }

  removeProjectsInFolder(folder: WorkspaceFolder) {
    const projects = this.projectsByFolderUri.get(folder.uri);
    if (projects) {
      projects.forEach(project => project.clearAllDiagnostics());
      this.projectsByFolderUri.delete(folder.uri);
    }
  }

  get projects(): GraphQLProject[] {
    return Array.from(this.projectsByFolderUri.values()).flat();
  }

  projectForFile(uri: DocumentUri): GraphQLProject | undefined {
    const cachedResult = this._projectForFileCache.get(uri);
    if (cachedResult) {
      return cachedResult;
    }

    for (const projects of this.projectsByFolderUri.values()) {
      const project = projects.find(project => project.includesFile(uri));
      if (project) {
        this._projectForFileCache.set(uri, project);
        return project;
      }
    }
    return undefined;
  }
}