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 },
    pluginId: string,

Review comment (suggestion):
Bit of a sidetrack: I think a better shape for listing installed features is probably this:

```ts
getInstalledPlugins(): Array<{
  pluginId: string
  modules: Array<{
    moduleId: string
  }>
}>
```

Couple of reasons:

- `getInstalledFeatures` is lying a bit, it doesn't list service factories, which are technically features
- Wherever I've used the service so far, I've always ended up with a `.filter(f => f.type === 'plugin')...`
- This is closer to the real structure of backend features, modules belong to plugins

Think we could consider ^ for promoting it to a stable core service.

In general I could see stabilizing the instance metadata service and making that information available on an endpoint as a potentially good next incremental step too. I think that might make it easier to ship some things and start experimenting/building with them, which has already happened a bit with the instance metadata.

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) {
      const missing = Array.from(missingRefs).join(', ');