Repository: spotify/backstage
File: packages/backend-app-api/src/wiring/BackendInitializer.ts (TypeScript)

Code before:
          })
          .filter(Boolean) as BackendFeatureMeta[];
      }
      return [];
    })
    .flat();
  return createServiceFactory({
    service: instanceMetadataServiceRef,
    deps: {
      httpRouter: coreServices.rootHttpRouter,
      logger: coreServices.rootLogger,
    },
    factory: async ({ logger, httpRouter }) => {
      const instanceMetadata = {
        getInstalledFeatures: () => installedFeatures,
      };

      logger.info(
        `Installed features on this instance: ${instanceMetadata
          .getInstalledFeatures()
          .join(', ')}`,
      );

      const router = Router();

      router.get('/features/installed', (_, res) => {
        res.json({ items: instanceMetadata.getInstalledFeatures() });
      });

      httpRouter.use('/.backstage/instanceMetadata/v1', router);

      return instanceMetadata;
    },
  });
}

export class BackendInitializer {
  #startPromise?: Promise<void>;
  #stopPromise?: Promise<void>;
  #registrations = new Array<InternalBackendRegistrations>();
  #extensionPoints = new Map<string, { impl: unknown; pluginId: string }>();
  #serviceRegistry: ServiceRegistry;
  #registeredFeatures = new Array<Promise<BackendFeature>>();
  #registeredFeatureLoaders = new Array<InternalBackendFeatureLoader>();

  constructor(defaultApiFactories: ServiceFactory[]) {
    this.#serviceRegistry = ServiceRegistry.create([...defaultApiFactories]);
  }

  async #getInitDeps(
    deps: { [name: string]: ServiceOrExtensionPoint },

Review comment (security):
Starting to get a bit worried about the security of this, especially if we start adding more information. There are all public and unauthenticated atm? Even with something like the gateway plugin the main instance will still have this exposed. I think we may need to move these to a separate port so that we can add information here a bit more freely.

Code after:
      };

      return instanceMetadata;
    },
  });
}

export class BackendInitializer {
  #startPromise?: Promise<{ result: BackendStartupResult }>;
  #stopPromise?: Promise<void>;
  #registrations = new Array<InternalBackendRegistrations>();
  #extensionPoints = new Map<string, { impl: unknown; pluginId: string }>();
  #serviceRegistry: ServiceRegistry;
  #registeredFeatures = new Array<Promise<BackendFeature>>();
  #registeredFeatureLoaders = new Array<InternalBackendFeatureLoader>();

  constructor(defaultApiFactories: ServiceFactory[]) {
    this.#serviceRegistry = ServiceRegistry.create([...defaultApiFactories]);
  }

  async #getInitDeps(
    deps: { [name: string]: ServiceOrExtensionPoint },
    pluginId: string,
    moduleId?: string,
  ) {
    const result = new Map<string, unknown>();
    const missingRefs = new Set<ServiceOrExtensionPoint>();

    for (const [name, ref] of Object.entries(deps)) {
      const ep = this.#extensionPoints.get(ref.id);
      if (ep) {
        if (ep.pluginId !== pluginId) {
          throw new Error(
            `Illegal dependency: Module '${moduleId}' for plugin '${pluginId}' attempted to depend on extension point '${ref.id}' for plugin '${ep.pluginId}'. Extension points can only be used within their plugin's scope.`,
          );
        }
        result.set(name, ep.impl);
      } else {
        const impl = await this.#serviceRegistry.get(
          ref as ServiceRef<unknown>,
          pluginId,
        );
        if (impl) {
          result.set(name, impl);
        } else {
          missingRefs.add(ref);
        }
      }
    }

    if (missingRefs.size > 0) {