Merge branch 'master' into package-workspaces
Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com> # Conflicts: # packages/cli-node/src/pacman/yarn/Yarn.test.ts # packages/cli-node/src/pacman/yarn/Yarn.ts
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-search-react': patch
|
||||
'@backstage/plugin-search': patch
|
||||
---
|
||||
|
||||
Fixes the search component not registering the first search on navigate to the search page.
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
'@backstage/cli-common': minor
|
||||
---
|
||||
|
||||
Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
|
||||
|
||||
To migrate existing `findPaths` usage:
|
||||
|
||||
```ts
|
||||
// Before
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
const paths = findPaths(__dirname);
|
||||
|
||||
// After — for target project paths (cwd-based):
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
// paths.targetDir → targetPaths.dir
|
||||
// paths.targetRoot → targetPaths.rootDir
|
||||
// paths.resolveTarget('src') → targetPaths.resolve('src')
|
||||
// paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
|
||||
|
||||
// After — for package-relative paths:
|
||||
import { findOwnPaths } from '@backstage/cli-common';
|
||||
const own = findOwnPaths(__dirname);
|
||||
// paths.ownDir → own.dir
|
||||
// paths.ownRoot → own.rootDir
|
||||
// paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
|
||||
// paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
|
||||
```
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Internal refactor to improve module independence.
|
||||
Internal refactor to use new concurrency utilities from `@backstage/cli-node`.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli-node': patch
|
||||
---
|
||||
|
||||
Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Added `translations export` and `translations import` commands for managing translation files.
|
||||
|
||||
The `translations export` command discovers all `TranslationRef` definitions across frontend plugin dependencies and exports their default messages as JSON files. The `translations import` command generates `TranslationResource` wiring code from translated JSON files, ready to be plugged into the app.
|
||||
|
||||
Both commands support a `--pattern` option for controlling the message file layout, for example `--pattern '{lang}/{id}.json'` for language-based directory grouping.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Fixed prettier existence checks in OpenAPI commands to use `fs.pathExists` instead of checking the resolved path string, which was always truthy.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed focus-visible outline styles for Menu and Select components.
|
||||
|
||||
**Affected components:** Menu, Select
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Minor internal optimisation
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
'@backstage/cli-node': patch
|
||||
'@backstage/backend-dynamic-feature-service': patch
|
||||
'@backstage/codemods': patch
|
||||
'@backstage/config-loader': patch
|
||||
'@backstage/create-app': patch
|
||||
'@backstage/repo-tools': patch
|
||||
'@techdocs/cli': patch
|
||||
---
|
||||
|
||||
Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Migrates existing catalog metrics to use the alpha MetricsService. This release is a 1:1 migration with no breaking changes.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend': minor
|
||||
---
|
||||
|
||||
Added opentelemetry metrics for SCM events:
|
||||
|
||||
- `catalog.events.scm.messages` with attribute `eventType`: Counter for the number of SCM events actually received by the catalog backend. The `eventType` is currently either `location` or `repository`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-node': minor
|
||||
---
|
||||
|
||||
Added the ability for SCM events subscribers to mark the fact that they have taken actions based on events, which produces output metrics:
|
||||
|
||||
- `catalog.events.scm.actions` with attribute `action`: Counter for the number of actions actually taken by catalog internals or other subscribers, based on SCM events. The `action` is currently either `create`, `delete`, `refresh`, or `move`.
|
||||
+39
-1
@@ -210,5 +210,43 @@
|
||||
"@backstage/plugin-user-settings-backend": "0.4.0",
|
||||
"@backstage/plugin-user-settings-common": "0.1.0"
|
||||
},
|
||||
"changesets": []
|
||||
"changesets": [
|
||||
"add-masked-and-hidden-option",
|
||||
"blue-moons-crash",
|
||||
"bright-moons-open",
|
||||
"brown-towns-find",
|
||||
"bump-bfj-v9",
|
||||
"cli-common-cached-paths",
|
||||
"cli-internal-refactor",
|
||||
"cli-node-parallel-helpers",
|
||||
"cli-translations-export-import",
|
||||
"dependabot-d2ec7e9",
|
||||
"fancy-ends-turn",
|
||||
"fix-frontend-feature-compat",
|
||||
"fix-prettier-existence-check",
|
||||
"fluffy-owls-act",
|
||||
"long-hairs-throw",
|
||||
"mean-fans-decide",
|
||||
"metal-humans-move",
|
||||
"migrate-to-target-paths",
|
||||
"ninety-corners-flash",
|
||||
"orange-mugs-post-1",
|
||||
"orange-mugs-post-2",
|
||||
"pink-terms-know",
|
||||
"polite-singers-lead",
|
||||
"pretty-days-taste",
|
||||
"rare-adults-attack",
|
||||
"renovate-8b1c21e",
|
||||
"rude-groups-shout",
|
||||
"scaffolder-export-form-fields-api",
|
||||
"silver-pigs-remain",
|
||||
"sixty-pianos-begin",
|
||||
"stable-translation-plugin-app",
|
||||
"stable-translation-test-utils",
|
||||
"stupid-pans-hope",
|
||||
"swift-flowers-grin",
|
||||
"swift-ravens-jog",
|
||||
"tired-bushes-write",
|
||||
"twenty-worlds-create"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Improved type safety in `useDefinition` by centralizing prop resolution and strengthening the `BgPropsConstraint` to require that `bg` provider components declare `children` as a required prop in their OwnProps type.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Added back the `formFieldsApiRef` and `ScaffolderFormFieldsApi` alpha exports that were unintentionally removed.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added proper cursor styles for RadioGroup items.
|
||||
|
||||
**Affected components:** RadioGroup
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Updated `@microsoft/api-extractor` to `7.57.3` and added tests for `getTsDocConfig`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-app': patch
|
||||
---
|
||||
|
||||
Switched translation API imports (`translationApiRef`, `appLanguageApiRef`) from the alpha `@backstage/core-plugin-api/alpha` path to the stable `@backstage/frontend-plugin-api` export. This has no effect on runtime behavior.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-test-utils': patch
|
||||
---
|
||||
|
||||
Switched `MockTranslationApi` and related test utility imports from `@backstage/core-plugin-api/alpha` to the stable `@backstage/frontend-plugin-api` export. The `TranslationApi` type in the API report is now sourced from a single package. This has no effect on runtime behavior.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
---
|
||||
|
||||
Minor update to catalog client docs
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-mcp-actions-backend': patch
|
||||
---
|
||||
|
||||
Adds two new metrics to track MCP server operations and sessions.
|
||||
|
||||
- `mcp.server.operation.duration`: The duration taken to process an individual MCP operation
|
||||
- `mcp.server.session.duration`: The duration of the MCP session from the perspective of the server
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Update migrations to be reversible
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Adds a new metrics service mock to be leveraged in tests
|
||||
@@ -1 +0,0 @@
|
||||
Follow the instructions at /.github/copilot-instructions.md
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../AGENTS.md
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
description: General project guidelines for Backstage development
|
||||
globs:
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
Follow the instructions at /.github/copilot-instructions.md
|
||||
@@ -1,49 +1,3 @@
|
||||
Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces.
|
||||
|
||||
## Key Directories
|
||||
|
||||
- `/packages`: Core framework packages (prefixed `@backstage/`)
|
||||
- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`)
|
||||
- `/packages/app` and `/packages/backend`: Example app for local development
|
||||
- `/packages/app`: Main example app using the new frontend system
|
||||
- `/packages/app-legacy`: Example app using the old frontend system
|
||||
- `/docs`: Documentation files
|
||||
|
||||
Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system (NFS). Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system.
|
||||
|
||||
## Code Standards
|
||||
|
||||
The following files contain guidelines for the project:
|
||||
|
||||
- `/CONTRIBUTING.md`: comprehensive contribution guidelines.
|
||||
- `/STYLE.md`: guidelines for code style.
|
||||
- `/REVIEWING.md`: guidelines for pull requests and writing changesets.
|
||||
- `/SECURITY.md`: guidelines for security.
|
||||
- `/docs/architecture-decisions/`: contains the architecture decisions for the project.
|
||||
|
||||
## Development Flow
|
||||
|
||||
Before any of these commands can be run, you need to run `yarn install` in the project root.
|
||||
|
||||
- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline.
|
||||
- Test: Use `yarn test --no-watch <path>` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests.
|
||||
- Type checking: Use `yarn tsc` in the project root to run the type checker.
|
||||
- Code formatting: Use `yarn prettier --write <path>` to format code.
|
||||
- Lint: Use `yarn lint --fix` in the project root to run the linter.
|
||||
- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages.
|
||||
- Dev server: Use `yarn start` to run the example app locally (frontend on :3000, backend on :7007).
|
||||
- Create: Use `yarn new` to scaffold new plugins, packages, or modules.
|
||||
|
||||
You MUST NOT create a release by running `yarn changesets version` or `yarn release` as part of any changes. Releases are created by separate workflows.
|
||||
|
||||
All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. Only non-private packages require changesets. See the guidelines in `/CONTRIBUTING.md#creating-changesets` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory. Breaking changes must be accompanied by a `minor` version bump for packages below version `1.0.0`, or a `major` version bump for packages at version `1.0.0` or higher.
|
||||
|
||||
Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
See `/docs/contribute/project-structure.md` for a detailed description of the repository structure.
|
||||
|
||||
## Pull Request Reviews
|
||||
|
||||
When reviewing a pull request you MUST calculate a priority score between 0 and 100, where a higher score indicates higher priority. You MUST include the score as an HTML comment at the very beginning of the "PR Overview" section, using the following format: `<!-- priority: <score> -->`.
|
||||
|
||||
@@ -322,6 +322,7 @@ openapi
|
||||
OpenSearch
|
||||
OpenShift
|
||||
openssl
|
||||
opentelemetry
|
||||
orgs
|
||||
overridable
|
||||
padding
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Add missing sharing extensions sidebar item in frontend system architecture docs
|
||||
@@ -1 +0,0 @@
|
||||
Fix type compatibility for older plugins in FrontendFeature type
|
||||
@@ -0,0 +1 @@
|
||||
Fixes the search component to register the first search that happens on initial navigation to the search page.
|
||||
@@ -0,0 +1,53 @@
|
||||
Backstage is an open platform for building developer portals. This is a TypeScript monorepo using Yarn workspaces.
|
||||
|
||||
## Key Directories
|
||||
|
||||
- `/packages`: Core framework packages (prefixed `@backstage/`)
|
||||
- `/plugins`: Plugin packages (prefixed `@backstage/plugin-*`)
|
||||
- `/packages/app` and `/packages/backend`: Example app for local development
|
||||
- `/packages/app`: Main example app using the new frontend system
|
||||
- `/packages/app-legacy`: Example app using the old frontend system
|
||||
- `/docs`: Documentation files
|
||||
|
||||
Packages prefixed with `core-` (e.g., `@backstage/core-plugin-api`) are part of the old frontend system. Packages prefixed with `frontend-` (e.g., `@backstage/frontend-plugin-api`) are part of the new frontend system (NFS). Packages prefixed with `backend-` (e.g., `@backstage/backend-plugin-api`) are part of the backend system.
|
||||
|
||||
## Code Standards
|
||||
|
||||
The following files contain guidelines for the project:
|
||||
|
||||
- `/CONTRIBUTING.md`: comprehensive contribution guidelines.
|
||||
- `/STYLE.md`: guidelines for code style.
|
||||
- `/REVIEWING.md`: guidelines for pull requests and writing changesets.
|
||||
- `/SECURITY.md`: guidelines for security.
|
||||
- `/docs/architecture-decisions/`: contains the architecture decisions for the project.
|
||||
|
||||
When writing or generating code, always match the existing coding style of each individual package and file. Different packages in the monorepo may have different conventions — consistency within a package is more important than consistency across the repo.
|
||||
|
||||
When writing or generating tests, prefer fewer thorough tests with multiple assertions over many small tests. When using React Testing Library, prefer using `screen` and `.findBy*` queries over `waitFor`, and avoid adding test IDs to the implementation.
|
||||
|
||||
## Development Flow
|
||||
|
||||
Before any of these commands can be run, you need to run `yarn install` in the project root.
|
||||
|
||||
- Build: There is no need to build the project during development, and it is verified automatically in the CI pipeline.
|
||||
- Test: Use `CI=1 yarn test <path>` in the project root to run tests. The path can be either a single file or a directory. Always provide a path, avoid running all tests.
|
||||
- Type checking: Use `yarn tsc` in the project root to run the type checker. Do not try to run it somewhere else than the project root and do not supply any options.
|
||||
- Code formatting: Use `yarn prettier --write <...paths>` to format code. Run it explicitly for file paths that you know are changed, not for entire folders - otherwise it may change formatting of unrelated files.
|
||||
- Lint: Use `yarn lint --fix` in the project root to run the linter.
|
||||
- API reports: Before submitting a pull request with changes to any package in the workspace, run `yarn build:api-reports` in the project root to generate API reports for all packages.
|
||||
- Dev server: Use `yarn start` to run the example app locally (frontend on :3000, backend on :7007).
|
||||
- Create: Use `yarn new` to scaffold new plugins, packages, or modules.
|
||||
|
||||
You MUST NOT run builds or create a release by running `yarn build`, `yarn changesets version`, or `yarn release` as part of any changes. Builds and releases are made by separate workflows.
|
||||
|
||||
All changes that affect the published version of packages in the `/packages` and `/plugins` directories must be accompanied by a changeset. Only non-private packages require changesets. See the guidelines in `/CONTRIBUTING.md#creating-changesets` for information on how to write good changesets. Changesets are stored in the `/.changeset` directory and should be created by writing changeset files directly — never use the changeset CLI. Breaking changes must be accompanied by a `minor` version bump for packages below version `1.0.0`, or a `major` version bump for packages at version `1.0.0` or higher. For non-breaking changes that introduce new APIs or features, use `minor` for packages at version `1.0.0` or higher, and `patch` for packages below `1.0.0`. Each changeset message should be relevant to the specific package it targets and written for Backstage adopters as the audience — avoid referencing internal implementation details. If a change spans multiple packages you often need to create separate changesets to make sure they are tailored to each package.
|
||||
|
||||
When creating pull requests, use the template at `/.github/PULL_REQUEST_TEMPLATE.md`.
|
||||
|
||||
Never update ESLint, Prettier, or TypeScript configuration files unless specifically requested.
|
||||
|
||||
Never make changes to the release notes in `/docs/releases` unless explicitly asked. These document past releases and should not be updated based on newer changes.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
See `/docs/contribute/project-structure.md` for a detailed description of the repository structure.
|
||||
@@ -23,7 +23,6 @@ creation-date: 2025-06-23
|
||||
- [Integration with OpenTelemetry Auto-Instrumentation](#integration-with-opentelemetry-auto-instrumentation)
|
||||
- [Configuration](#configuration)
|
||||
- [Interface](#interface)
|
||||
- [Root Metrics Service](#root-metrics-service)
|
||||
- [Plugin Metrics Service](#plugin-metrics-service)
|
||||
- [Example](#example)
|
||||
- [Release Plan](#release-plan)
|
||||
@@ -36,7 +35,7 @@ Add a core `MetricsService` to Backstage's framework to provide a unified interf
|
||||
|
||||
## Motivation
|
||||
|
||||
While individual plugins may implement their own metrics, there's no standardized approach leading to inconsistent metrics patterns across the ecosystem. For example, both `catalog_entities_count` and `catalog.processed.entities.count` are examples of existing metric patterns. Ideally, these would be standardized to `backstage.plugin.catalog.entities.count` and `backstage.plugin.catalog.entities.processed.total` respectively.
|
||||
While individual plugins may implement their own metrics, there's no standardized approach leading to inconsistent metrics patterns across the ecosystem and incompatibility with OpenTelemetry semantic conventions. For example, a plugin implementing MCP functionality might incorrectly namespace metrics as `backstage_mcp_client_duration` when OpenTelemetry semantic conventions explicitly define `mcp.client.operation.duration` as the standard.
|
||||
|
||||
By providing a core metrics service:
|
||||
|
||||
@@ -45,7 +44,7 @@ By providing a core metrics service:
|
||||
|
||||
### Goals
|
||||
|
||||
- Plugin-scoped metric namespacing
|
||||
- Plugin identification via OpenTelemetry Instrumentation Scope
|
||||
- Consistent metrics patterns across all plugins
|
||||
- Aligned with OpenTelemetry industry standards
|
||||
- Provide a familiar interface as other core services
|
||||
@@ -124,9 +123,12 @@ The `MetricsService` **complements** rather than duplicates auto-instrumentation
|
||||
|
||||
// MetricsService provides (manually):
|
||||
const entityMetrics = metricsService.createCounter('entities.processed.total');
|
||||
entityMetrics.add(entities.length, { operation: 'refresh', kind: 'Component' });
|
||||
entityMetrics.add(entities.length, {
|
||||
operation: 'refresh',
|
||||
'entity.kind': 'Component',
|
||||
});
|
||||
|
||||
// Metric is now available as `backstage.plugin.catalog.entities.processed.total`
|
||||
// Metric is now available as `entities.processed.total`
|
||||
```
|
||||
|
||||
### Configuration
|
||||
@@ -162,43 +164,21 @@ interface MetricsService {
|
||||
}
|
||||
```
|
||||
|
||||
#### Root Metrics Service
|
||||
|
||||
The `RootMetricsService` is responsible for providing metrics to other root services and creating both plugin-scoped and core-scoped `MetricsService` instances.
|
||||
|
||||
```ts
|
||||
interface RootMetricsService {
|
||||
// note: no config is provided to the root service.
|
||||
static forRoot(): RootMetricsService;
|
||||
forPlugin(pluginId: string): MetricsService;
|
||||
|
||||
// final implementation will be similar to
|
||||
forService(serviceName: string, scope: 'plugin' | 'core'): MetricsService;
|
||||
}
|
||||
|
||||
export const rootMetricsServiceFactory = createServiceFactory({
|
||||
// depends on as little as possible so that it can be initialized as early as possible.
|
||||
service: rootMetricsServiceRef,
|
||||
deps: {},
|
||||
factory: () => {
|
||||
return DefaultRootMetricsService.forRoot();
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Plugin Metrics Service
|
||||
|
||||
Each plugin receives a metrics service that automatically namespaces all metrics to match the naming conventions.
|
||||
Each plugin receives a metrics service that automatically configures the Instrumentation Scope to identify the plugin. The scope name follows the pattern `backstage-plugin-{pluginId}`.
|
||||
|
||||
```ts
|
||||
const metricsServiceFactory = createServiceFactory({
|
||||
service: metricsServiceRef,
|
||||
export const metricsServiceFactory = createServiceFactory({
|
||||
service: coreServices.metrics,
|
||||
deps: {
|
||||
rootMetrics: coreServices.rootMetrics,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
},
|
||||
factory: ({ rootMetrics, pluginMetadata }) => {
|
||||
return rootMetrics.forPlugin(pluginMetadata.getId());
|
||||
factory: ({ pluginMetadata }) => {
|
||||
const pluginId = pluginMetadata.getId();
|
||||
const scopeName = `backstage-plugin-${pluginId}`;
|
||||
|
||||
return new DefaultMetricsService(scopeName, version, ...);
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -248,3 +228,22 @@ entitiesProcessed.add(100);
|
||||
|
||||
- Plugin authors continue to implement their own metrics as they see fit.
|
||||
- A combined TelemetryService that provides both metrics and tracing.
|
||||
|
||||
### Rejected: Forced Namespace Prefixes
|
||||
|
||||
Prepend `backstage.plugin.{pluginId}.` to all metric names. This was the original proposal but conflicts with OpenTelemetry semantic conventions.
|
||||
|
||||
**Problems:**
|
||||
|
||||
- Makes it impossible to use standard semantic conventions like `mcp.*`, `gen_ai.*`, `http.*`
|
||||
- Breaks compatibility with industry-standard observability tooling
|
||||
- Prevents cross-service metric aggregation
|
||||
- Goes against OpenTelemetry best practices and official guidance
|
||||
|
||||
**Example of conflict:**
|
||||
|
||||
```ts
|
||||
// Plugin wants to emit: mcp.client.operation.duration
|
||||
// Framework forces: backstage.plugin.mcp-actions.mcp.client.operation.duration
|
||||
// This violates the semantic convention and breaks tooling
|
||||
```
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
id: internationalization
|
||||
title: Internationalization (Experimental)
|
||||
description: Documentation on adding internationalization to the plugin
|
||||
title: Internationalization
|
||||
description: Documentation on adding internationalization to plugins and apps
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Backstage core function provides internationalization for plugins. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
|
||||
The Backstage core function provides internationalization for plugins and apps. The underlying library is [`i18next`](https://www.i18next.com/) with some additional Backstage typescript magic for type safety with keys.
|
||||
|
||||
## For a plugin developer
|
||||
|
||||
@@ -183,16 +183,56 @@ return (
|
||||
|
||||
The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message.
|
||||
|
||||
## For an application developer overwrite plugin messages
|
||||
## For an application developer
|
||||
|
||||
Step 1: Create translation resources
|
||||
As an app developer you can both override the default English messages of any plugin, and provide translations for additional languages.
|
||||
|
||||
You should separate different translations to their own files and import them in the main file:
|
||||
### Overriding messages
|
||||
|
||||
To customize specific messages without adding new languages, create a translation resource that overrides the default English messages:
|
||||
|
||||
```ts
|
||||
// packages/app/src/translations/catalog.ts
|
||||
|
||||
import { createTranslationResource } from '@backstage/frontend-plugin-api';
|
||||
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
|
||||
|
||||
export const catalogTranslations = createTranslationResource({
|
||||
ref: catalogTranslationRef,
|
||||
translations: {
|
||||
en: () =>
|
||||
Promise.resolve({
|
||||
default: {
|
||||
'indexPage.title': 'Service directory',
|
||||
'indexPage.createButtonTitle': 'Register new service',
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Then register it in your app:
|
||||
|
||||
```diff
|
||||
+ import { catalogTranslations } from './translations/catalog';
|
||||
|
||||
const app = createApp({
|
||||
+ __experimentalTranslations: {
|
||||
+ resources: [catalogTranslations],
|
||||
+ },
|
||||
})
|
||||
```
|
||||
|
||||
You only need to include the keys you want to override — any missing keys fall back to the plugin's defaults.
|
||||
|
||||
### Adding language translations
|
||||
|
||||
To add support for additional languages, create translation resources with lazy-loaded message files for each language:
|
||||
|
||||
```ts
|
||||
// packages/app/src/translations/userSettings.ts
|
||||
|
||||
import { createTranslationResource } from '@backstage/core-plugin-api/alpha';
|
||||
import { createTranslationResource } from '@backstage/frontend-plugin-api';
|
||||
import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
|
||||
|
||||
export const userSettingsTranslations = createTranslationResource({
|
||||
@@ -203,10 +243,12 @@ export const userSettingsTranslations = createTranslationResource({
|
||||
});
|
||||
```
|
||||
|
||||
The translation messages can be defined using `createTranslationMessages` for type safety:
|
||||
|
||||
```ts
|
||||
// packages/app/src/translations/userSettings-zh.ts
|
||||
|
||||
import { createTranslationMessages } from '@backstage/core-plugin-api/alpha';
|
||||
import { createTranslationMessages } from '@backstage/frontend-plugin-api';
|
||||
import { userSettingsTranslationRef } from '@backstage/plugin-user-settings/alpha';
|
||||
|
||||
const zh = createTranslationMessages({
|
||||
@@ -221,7 +263,7 @@ const zh = createTranslationMessages({
|
||||
export default zh;
|
||||
```
|
||||
|
||||
It's also possible to export the list of messages directly:
|
||||
Or as a plain object export:
|
||||
|
||||
```ts
|
||||
// packages/app/src/translations/userSettings-zh.ts
|
||||
@@ -239,11 +281,7 @@ export default {
|
||||
};
|
||||
```
|
||||
|
||||
You should change `zh` under the translations object to your local language.
|
||||
|
||||
Step 2: Config translations in `packages/app/src/App.tsx`
|
||||
|
||||
In an app you can both override the default messages, as well as register translations for additional languages:
|
||||
Register it with the available languages declared:
|
||||
|
||||
```diff
|
||||
+ import { userSettingsTranslations } from './translations/userSettings';
|
||||
@@ -256,6 +294,116 @@ In an app you can both override the default messages, as well as register transl
|
||||
})
|
||||
```
|
||||
|
||||
Step 3: Check everything is working correctly
|
||||
Go to the Settings page — you should see language switching buttons. Switch languages to verify your translations are loaded correctly.
|
||||
|
||||
Go to `Settings` page, you should see change language buttons just under change theme buttons. And then switch language, you should see language had changed
|
||||
### Using the CLI for full translation workflows
|
||||
|
||||
When translating your app to other languages at scale — especially when working with external translation systems — the Backstage CLI provides `translations export` and `translations import` commands that automate the extraction and wiring of translation messages across all your plugin dependencies.
|
||||
|
||||
#### Exporting default messages
|
||||
|
||||
From your app package directory (e.g. `packages/app`), run:
|
||||
|
||||
```bash
|
||||
yarn backstage-cli translations export
|
||||
```
|
||||
|
||||
This scans all frontend plugin dependencies (including transitive ones) for `TranslationRef` definitions and writes their default English messages as JSON files:
|
||||
|
||||
```text
|
||||
translations/
|
||||
manifest.json
|
||||
messages/
|
||||
catalog.en.json
|
||||
org.en.json
|
||||
scaffolder.en.json
|
||||
...
|
||||
```
|
||||
|
||||
Each `.en.json` file contains the flattened message keys and their default values:
|
||||
|
||||
```json
|
||||
{
|
||||
"indexPage.title": "All your components",
|
||||
"indexPage.createButtonTitle": "Create new component",
|
||||
"entityPage.notFound": "Entity not found"
|
||||
}
|
||||
```
|
||||
|
||||
#### Creating translations
|
||||
|
||||
Copy the exported files and translate them for your target languages:
|
||||
|
||||
```bash
|
||||
cp translations/messages/catalog.en.json translations/messages/catalog.zh.json
|
||||
```
|
||||
|
||||
Then edit `catalog.zh.json` with the translated strings. You only need to include the keys you want to translate — missing keys fall back to the English defaults at runtime.
|
||||
|
||||
#### Generating wiring code
|
||||
|
||||
Once you have translated files in place, run:
|
||||
|
||||
```bash
|
||||
yarn backstage-cli translations import
|
||||
```
|
||||
|
||||
This generates a TypeScript module at `src/translations/resources.ts` that wires everything together:
|
||||
|
||||
```ts
|
||||
// This file is auto-generated by backstage-cli translations import
|
||||
// Do not edit manually.
|
||||
|
||||
import { createTranslationResource } from '@backstage/frontend-plugin-api';
|
||||
import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha';
|
||||
|
||||
export default [
|
||||
createTranslationResource({
|
||||
ref: catalogTranslationRef,
|
||||
translations: {
|
||||
zh: () => import('../../translations/messages/catalog.zh.json'),
|
||||
},
|
||||
}),
|
||||
];
|
||||
```
|
||||
|
||||
Import the generated resources in your app:
|
||||
|
||||
```ts
|
||||
import translationResources from './translations/resources';
|
||||
|
||||
const app = createApp({
|
||||
__experimentalTranslations: {
|
||||
availableLanguages: ['en', 'zh'],
|
||||
resources: translationResources,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### Custom file patterns
|
||||
|
||||
By default, message files use the pattern `messages/{id}.{lang}.json` (e.g. `messages/catalog.en.json`). You can change this with the `--pattern` option:
|
||||
|
||||
```bash
|
||||
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
|
||||
```
|
||||
|
||||
This produces a directory structure grouped by language instead:
|
||||
|
||||
```text
|
||||
translations/en/catalog.json
|
||||
translations/zh/catalog.json
|
||||
```
|
||||
|
||||
The pattern is stored in the manifest, so the `import` command automatically uses the same layout.
|
||||
|
||||
#### Integration with external translation systems
|
||||
|
||||
The exported JSON files are standard key-value pairs compatible with most external translation systems. A typical workflow looks like:
|
||||
|
||||
1. Run `translations export` to generate the source English files
|
||||
2. Upload the `.en.json` files to your translation system
|
||||
3. Download the translated files back into the translations directory
|
||||
4. Run `translations import` to regenerate the wiring code
|
||||
|
||||
For full command reference, see the [CLI commands documentation](../tooling/cli/03-commands.md#translations-export).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,6 +24,7 @@ repo [command] Command that run across an entire
|
||||
package [command] Lifecycle scripts for individual packages
|
||||
migrate [command] Migration utilities
|
||||
versions:bump [options] Bump Backstage packages to the latest versions
|
||||
translations [command] Translation message management
|
||||
clean Delete cache directories [DEPRECATED]
|
||||
build-workspace <workspace-dir> [packages...] Builds a temporary dist workspace from the provided
|
||||
packages
|
||||
@@ -429,6 +430,73 @@ YAML file that can be referenced in the GitHub integration configuration.
|
||||
Usage: backstage-cli create-github-app <github-org>
|
||||
```
|
||||
|
||||
## translations export
|
||||
|
||||
Export translation messages from an app and all of its frontend plugins to JSON
|
||||
files. This command must be run from within a package directory (e.g.
|
||||
`packages/app`), not from the repository root.
|
||||
|
||||
The command discovers all `TranslationRef` definitions in the dependency tree,
|
||||
extracts their default messages using the TypeScript type system, and writes
|
||||
them as JSON files along with a manifest.
|
||||
|
||||
For more details on the translation workflow, see the
|
||||
[Internationalization](../../plugins/internationalization.md) documentation.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli translations export [options]
|
||||
|
||||
Options:
|
||||
--output <dir> Output directory for exported messages and manifest (default: "translations")
|
||||
--pattern <pattern> File path pattern for message files relative to the output
|
||||
directory, with {id} and {lang} placeholders
|
||||
(default: "messages/{id}.{lang}.json")
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Export translations with default settings:
|
||||
|
||||
```bash
|
||||
cd packages/app
|
||||
yarn backstage-cli translations export
|
||||
```
|
||||
|
||||
Export with language-based directory grouping:
|
||||
|
||||
```bash
|
||||
yarn backstage-cli translations export --pattern '{lang}/{id}.json'
|
||||
```
|
||||
|
||||
## translations import
|
||||
|
||||
Generate translation resource wiring code from translated JSON files. Reads the
|
||||
manifest and translated message files produced by `translations export`, and
|
||||
generates a TypeScript module that creates `TranslationResource` objects for each
|
||||
translated ref.
|
||||
|
||||
The file pattern used during export is stored in the manifest and automatically
|
||||
used by the import command.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli translations import [options]
|
||||
|
||||
Options:
|
||||
--input <dir> Input directory containing the manifest and translated message files (default: "translations")
|
||||
--output <path> Output path for the generated wiring module (default: "src/translations/resources.ts")
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
Generate wiring code with default settings:
|
||||
|
||||
```bash
|
||||
cd packages/app
|
||||
yarn backstage-cli translations import
|
||||
```
|
||||
|
||||
## info
|
||||
|
||||
Outputs debug information which is useful when opening an issue. Outputs system
|
||||
|
||||
@@ -142,6 +142,7 @@ nav:
|
||||
- Composability System: 'plugins/composability.md'
|
||||
- Plugin Analytics: 'plugins/analytics.md'
|
||||
- Feature Flags: 'plugins/feature-flags.md'
|
||||
- Internationalization (i18n): 'plugins/internationalization.md'
|
||||
- OpenAPI:
|
||||
- Schema-first plugins with OpenAPI (Experimental): 'openapi/01-getting-started.md'
|
||||
- Generate a client from your OpenAPI spec: 'openapi/generate-client.md'
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "1.48.0",
|
||||
"version": "1.49.0-next.0",
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/app-defaults
|
||||
|
||||
## 1.7.6-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-app-api@1.19.6-next.0
|
||||
- @backstage/core-components@0.18.8-next.0
|
||||
- @backstage/core-plugin-api@1.12.4-next.0
|
||||
- @backstage/theme@0.7.2
|
||||
- @backstage/plugin-permission-react@0.4.41-next.0
|
||||
|
||||
## 1.7.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/app-defaults",
|
||||
"version": "1.7.5",
|
||||
"version": "1.7.6-next.0",
|
||||
"description": "Provides the default wiring of a Backstage App",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# app-example-plugin
|
||||
|
||||
## 0.0.33-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/frontend-plugin-api@0.14.2-next.0
|
||||
- @backstage/core-components@0.18.8-next.0
|
||||
|
||||
## 0.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-example-plugin",
|
||||
"version": "0.0.32",
|
||||
"version": "0.0.33-next.0",
|
||||
"description": "Backstage internal example plugin",
|
||||
"backstage": {
|
||||
"role": "frontend-plugin",
|
||||
|
||||
@@ -1,5 +1,49 @@
|
||||
# example-app-legacy
|
||||
|
||||
## 0.2.119-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/ui@0.12.1-next.0
|
||||
- @backstage/plugin-search-react@1.10.5-next.0
|
||||
- @backstage/plugin-search@1.6.2-next.0
|
||||
- @backstage/plugin-api-docs@0.13.5-next.0
|
||||
- @backstage/cli@0.35.5-next.0
|
||||
- @backstage/plugin-scaffolder@1.35.5-next.0
|
||||
- @backstage/plugin-catalog@1.33.1-next.0
|
||||
- @backstage/plugin-catalog-react@2.0.1-next.0
|
||||
- @backstage/plugin-mui-to-bui@0.2.5-next.0
|
||||
- @backstage/plugin-techdocs@1.17.1-next.0
|
||||
- @backstage/app-defaults@1.7.6-next.0
|
||||
- @backstage/catalog-model@1.7.6
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/core-app-api@1.19.6-next.0
|
||||
- @backstage/core-components@0.18.8-next.0
|
||||
- @backstage/core-plugin-api@1.12.4-next.0
|
||||
- @backstage/frontend-app-api@0.15.1-next.0
|
||||
- @backstage/integration-react@1.2.16-next.0
|
||||
- @backstage/theme@0.7.2
|
||||
- @backstage/plugin-auth-react@0.1.25-next.0
|
||||
- @backstage/plugin-catalog-common@1.1.8
|
||||
- @backstage/plugin-catalog-graph@0.5.8-next.0
|
||||
- @backstage/plugin-catalog-import@0.13.11-next.0
|
||||
- @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
|
||||
- @backstage/plugin-devtools@0.1.37-next.0
|
||||
- @backstage/plugin-home@0.9.3-next.0
|
||||
- @backstage/plugin-home-react@0.1.36-next.0
|
||||
- @backstage/plugin-kubernetes@0.12.17-next.0
|
||||
- @backstage/plugin-kubernetes-cluster@0.0.35-next.0
|
||||
- @backstage/plugin-notifications@0.5.15-next.0
|
||||
- @backstage/plugin-org@0.6.50-next.0
|
||||
- @backstage/plugin-permission-react@0.4.41-next.0
|
||||
- @backstage/plugin-scaffolder-react@1.19.8-next.0
|
||||
- @backstage/plugin-search-common@1.2.22
|
||||
- @backstage/plugin-signals@0.0.29-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
|
||||
- @backstage/plugin-techdocs-react@1.3.9-next.0
|
||||
- @backstage/plugin-user-settings@0.9.1-next.0
|
||||
|
||||
## 0.2.118
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app-legacy",
|
||||
"version": "0.2.118",
|
||||
"version": "0.2.119-next.0",
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,55 @@
|
||||
# example-app
|
||||
|
||||
## 0.0.33-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/ui@0.12.1-next.0
|
||||
- @backstage/plugin-search-react@1.10.5-next.0
|
||||
- @backstage/plugin-search@1.6.2-next.0
|
||||
- @backstage/plugin-api-docs@0.13.5-next.0
|
||||
- @backstage/cli@0.35.5-next.0
|
||||
- @backstage/frontend-plugin-api@0.14.2-next.0
|
||||
- @backstage/plugin-scaffolder@1.35.5-next.0
|
||||
- @backstage/plugin-app@0.4.1-next.0
|
||||
- @backstage/plugin-app-visualizer@0.2.1-next.0
|
||||
- @backstage/plugin-catalog@1.33.1-next.0
|
||||
- @backstage/plugin-catalog-react@2.0.1-next.0
|
||||
- @backstage/plugin-techdocs@1.17.1-next.0
|
||||
- @backstage/app-defaults@1.7.6-next.0
|
||||
- @backstage/catalog-model@1.7.6
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/core-app-api@1.19.6-next.0
|
||||
- @backstage/core-compat-api@0.5.9-next.0
|
||||
- @backstage/core-components@0.18.8-next.0
|
||||
- @backstage/core-plugin-api@1.12.4-next.0
|
||||
- @backstage/frontend-app-api@0.15.1-next.0
|
||||
- @backstage/frontend-defaults@0.4.1-next.0
|
||||
- @backstage/integration-react@1.2.16-next.0
|
||||
- @backstage/theme@0.7.2
|
||||
- @backstage/plugin-app-react@0.2.1-next.0
|
||||
- @backstage/plugin-auth@0.1.6-next.0
|
||||
- @backstage/plugin-auth-react@0.1.25-next.0
|
||||
- @backstage/plugin-catalog-common@1.1.8
|
||||
- @backstage/plugin-catalog-graph@0.5.8-next.0
|
||||
- @backstage/plugin-catalog-import@0.13.11-next.0
|
||||
- @backstage/plugin-catalog-unprocessed-entities@0.2.27-next.0
|
||||
- @backstage/plugin-devtools@0.1.37-next.0
|
||||
- @backstage/plugin-home@0.9.3-next.0
|
||||
- @backstage/plugin-home-react@0.1.36-next.0
|
||||
- @backstage/plugin-kubernetes@0.12.17-next.0
|
||||
- @backstage/plugin-kubernetes-cluster@0.0.35-next.0
|
||||
- @backstage/plugin-notifications@0.5.15-next.0
|
||||
- @backstage/plugin-org@0.6.50-next.0
|
||||
- @backstage/plugin-permission-react@0.4.41-next.0
|
||||
- @backstage/plugin-scaffolder-react@1.19.8-next.0
|
||||
- @backstage/plugin-search-common@1.2.22
|
||||
- @backstage/plugin-signals@0.0.29-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.1.34-next.0
|
||||
- @backstage/plugin-techdocs-react@1.3.9-next.0
|
||||
- @backstage/plugin-user-settings@0.9.1-next.0
|
||||
|
||||
## 0.0.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.0.32",
|
||||
"version": "0.0.33-next.0",
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/backend-app-api
|
||||
|
||||
## 1.5.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/errors@1.2.7
|
||||
|
||||
## 1.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-app-api",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1-next.0",
|
||||
"description": "Core API used by Backstage backend apps",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
# @backstage/backend-defaults
|
||||
|
||||
## 0.15.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 6738cf0: build(deps): bump `minimatch` from 9.0.5 to 10.2.1
|
||||
- d933f62: Add configurable throttling and retry mechanism for GitLab integration.
|
||||
- b99158a: Fixed `yarn backstage-cli config:check --strict --config app-config.yaml` config validation error by adding
|
||||
an optional `default` type discriminator to PostgreSQL connection configuration,
|
||||
allowing `config:check` to properly validate `default` connection configurations.
|
||||
- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
|
||||
- Updated dependencies
|
||||
- @backstage/cli-node@0.2.19-next.0
|
||||
- @backstage/integration@1.21.0-next.0
|
||||
- @backstage/config-loader@1.10.9-next.0
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/backend-app-api@1.5.1-next.0
|
||||
- @backstage/backend-dev-utils@0.1.7
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/integration-aws-node@0.1.20
|
||||
- @backstage/types@1.2.2
|
||||
- @backstage/plugin-auth-node@0.6.14-next.0
|
||||
- @backstage/plugin-events-node@0.4.20-next.0
|
||||
- @backstage/plugin-permission-node@0.10.11-next.0
|
||||
|
||||
## 0.15.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+30
@@ -1127,6 +1127,36 @@ export interface Config {
|
||||
headers?: { [name: string]: string };
|
||||
};
|
||||
|
||||
/**
|
||||
* Options for the metrics service.
|
||||
*/
|
||||
metrics?: {
|
||||
/**
|
||||
* Plugin-specific metrics configuration. Each plugin can override meter metadata.
|
||||
*/
|
||||
plugin?: {
|
||||
[pluginId: string]: {
|
||||
/**
|
||||
* Meter configuration for this plugin.
|
||||
*/
|
||||
meter?: {
|
||||
/**
|
||||
* Custom meter name. If not set, defaults to backstage-plugin-{pluginId}.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Version for the meter.
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Schema URL for the meter.
|
||||
*/
|
||||
schemaUrl?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Options to configure the default RootLoggerService.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-defaults",
|
||||
"version": "0.15.2",
|
||||
"version": "0.15.3-next.0",
|
||||
"description": "Backend defaults used by Backstage backend apps",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
```ts
|
||||
import { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { ActionsService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { MetricsService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { RootSystemMetadataService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
@@ -22,6 +23,13 @@ export const actionsServiceFactory: ServiceFactory<
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @alpha
|
||||
export const metricsServiceFactory: ServiceFactory<
|
||||
MetricsService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @alpha
|
||||
export const rootSystemMetadataServiceFactory: ServiceFactory<
|
||||
RootSystemMetadataService,
|
||||
|
||||
@@ -38,6 +38,7 @@ import { eventsServiceFactory } from '@backstage/plugin-events-node';
|
||||
import {
|
||||
actionsRegistryServiceFactory,
|
||||
actionsServiceFactory,
|
||||
metricsServiceFactory,
|
||||
} from '@backstage/backend-defaults/alpha';
|
||||
import { instanceMetadataServiceFactory } from './alpha/entrypoints/instanceMetadata/instanceMetadataServiceFactory';
|
||||
|
||||
@@ -66,6 +67,7 @@ export const defaultServiceFactories = [
|
||||
// alpha services
|
||||
actionsRegistryServiceFactory,
|
||||
actionsServiceFactory,
|
||||
metricsServiceFactory,
|
||||
|
||||
// Unexported alpha services kept around for compatibility reasons
|
||||
instanceMetadataServiceFactory,
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { metrics } from '@opentelemetry/api';
|
||||
import { DefaultMetricsService } from './DefaultMetricsService';
|
||||
|
||||
const mockGetMeter = jest.spyOn(metrics, 'getMeter');
|
||||
|
||||
describe('DefaultMetricsService', () => {
|
||||
beforeEach(() => {
|
||||
mockGetMeter.mockClear();
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a MetricsService with name only', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test-meter' });
|
||||
|
||||
expect(mockGetMeter).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetMeter).toHaveBeenCalledWith('test-meter', undefined, {
|
||||
schemaUrl: undefined,
|
||||
});
|
||||
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a MetricsService with name, version, and schemaUrl', () => {
|
||||
const service = DefaultMetricsService.create({
|
||||
name: 'test-meter',
|
||||
version: '1.2.3',
|
||||
schemaUrl: 'https://example.com/schema',
|
||||
});
|
||||
|
||||
expect(mockGetMeter).toHaveBeenCalledTimes(1);
|
||||
expect(mockGetMeter).toHaveBeenCalledWith('test-meter', '1.2.3', {
|
||||
schemaUrl: 'https://example.com/schema',
|
||||
});
|
||||
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('metric instruments', () => {
|
||||
it('should create a counter', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const counter = service.createCounter('my_counter', {
|
||||
description: 'A test counter',
|
||||
unit: 'bytes',
|
||||
});
|
||||
|
||||
expect(counter).toBeDefined();
|
||||
expect(counter.add).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create an up-down counter', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const upDownCounter = service.createUpDownCounter('my_updown');
|
||||
|
||||
expect(upDownCounter).toBeDefined();
|
||||
expect(upDownCounter.add).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a histogram', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const histogram = service.createHistogram('my_histogram');
|
||||
|
||||
expect(histogram).toBeDefined();
|
||||
expect(histogram.record).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create a gauge', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const gauge = service.createGauge('my_gauge');
|
||||
|
||||
expect(gauge).toBeDefined();
|
||||
expect(gauge.record).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create an observable counter', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const counter = service.createObservableCounter('my_observable_counter');
|
||||
|
||||
expect(counter).toBeDefined();
|
||||
expect(counter.addCallback).toBeDefined();
|
||||
expect(counter.removeCallback).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create an observable up-down counter', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const counter = service.createObservableUpDownCounter(
|
||||
'my_observable_updown',
|
||||
);
|
||||
|
||||
expect(counter).toBeDefined();
|
||||
expect(counter.addCallback).toBeDefined();
|
||||
});
|
||||
|
||||
it('should create an observable gauge', () => {
|
||||
const service = DefaultMetricsService.create({ name: 'test' });
|
||||
const gauge = service.createObservableGauge('my_observable_gauge');
|
||||
|
||||
expect(gauge).toBeDefined();
|
||||
expect(gauge.addCallback).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Meter, metrics } from '@opentelemetry/api';
|
||||
import {
|
||||
MetricsService,
|
||||
MetricAttributes,
|
||||
MetricOptions,
|
||||
MetricsServiceCounter,
|
||||
MetricsServiceUpDownCounter,
|
||||
MetricsServiceHistogram,
|
||||
MetricsServiceGauge,
|
||||
MetricsServiceObservableCounter,
|
||||
MetricsServiceObservableGauge,
|
||||
MetricsServiceObservableUpDownCounter,
|
||||
} from '@backstage/backend-plugin-api/alpha';
|
||||
|
||||
/**
|
||||
* Options for creating a {@link DefaultMetricsService}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface DefaultMetricsServiceOptions {
|
||||
name: string;
|
||||
version?: string;
|
||||
schemaUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link MetricsService} interface.
|
||||
*
|
||||
* This implementation provides a thin wrapper around the OpenTelemetry Meter API.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export class DefaultMetricsService implements MetricsService {
|
||||
private readonly meter: Meter;
|
||||
|
||||
private constructor(opts: DefaultMetricsServiceOptions) {
|
||||
// The meter name sets the OpenTelemetry Instrumentation Scope which identifies the source of metrics in telemetry backends.
|
||||
this.meter = metrics.getMeter(opts.name, opts.version, {
|
||||
schemaUrl: opts.schemaUrl,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MetricsService} instance.
|
||||
*
|
||||
* @param opts - Options for configuring the meter scope
|
||||
* @returns A new MetricsService instance
|
||||
*/
|
||||
static create(opts: DefaultMetricsServiceOptions): MetricsService {
|
||||
return new DefaultMetricsService(opts);
|
||||
}
|
||||
|
||||
createCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceCounter<TAttributes> {
|
||||
return this.meter.createCounter(name, opts);
|
||||
}
|
||||
|
||||
createUpDownCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceUpDownCounter<TAttributes> {
|
||||
return this.meter.createUpDownCounter(name, opts);
|
||||
}
|
||||
|
||||
createHistogram<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceHistogram<TAttributes> {
|
||||
return this.meter.createHistogram(name, opts);
|
||||
}
|
||||
|
||||
createGauge<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceGauge<TAttributes> {
|
||||
return this.meter.createGauge(name, opts);
|
||||
}
|
||||
|
||||
createObservableCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableCounter<TAttributes> {
|
||||
return this.meter.createObservableCounter(name, opts);
|
||||
}
|
||||
|
||||
createObservableUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableUpDownCounter<TAttributes> {
|
||||
return this.meter.createObservableUpDownCounter(name, opts);
|
||||
}
|
||||
|
||||
createObservableGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableGauge<TAttributes> {
|
||||
return this.meter.createObservableGauge(name, opts);
|
||||
}
|
||||
}
|
||||
+2
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,8 +13,4 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
export const paths = findPaths(__dirname);
|
||||
export { metricsServiceFactory } from './metricsServiceFactory';
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {
|
||||
mockServices,
|
||||
ServiceFactoryTester,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { metricsServiceFactory } from './metricsServiceFactory';
|
||||
import { DefaultMetricsService } from './DefaultMetricsService';
|
||||
|
||||
describe('metricsServiceFactory', () => {
|
||||
let createSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
createSpy = jest.spyOn(DefaultMetricsService, 'create');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const defaultServices = [
|
||||
mockServices.rootConfig.factory(),
|
||||
metricsServiceFactory,
|
||||
];
|
||||
|
||||
it('should use backstage-plugin-{pluginId} as meter name when no config is set', async () => {
|
||||
await ServiceFactoryTester.from(metricsServiceFactory, {
|
||||
dependencies: defaultServices,
|
||||
}).getSubject('my-plugin');
|
||||
|
||||
expect(createSpy).toHaveBeenCalledWith({
|
||||
name: 'backstage-plugin-my-plugin',
|
||||
version: undefined,
|
||||
schemaUrl: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use custom name from config', async () => {
|
||||
await ServiceFactoryTester.from(metricsServiceFactory, {
|
||||
dependencies: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
metrics: {
|
||||
plugin: {
|
||||
'my-plugin': {
|
||||
meter: {
|
||||
name: 'custom-metrics-name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
metricsServiceFactory,
|
||||
],
|
||||
}).getSubject('my-plugin');
|
||||
|
||||
expect(createSpy).toHaveBeenCalledWith({
|
||||
name: 'custom-metrics-name',
|
||||
version: undefined,
|
||||
schemaUrl: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept version and schemaUrl from config', async () => {
|
||||
await ServiceFactoryTester.from(metricsServiceFactory, {
|
||||
dependencies: [
|
||||
mockServices.rootConfig.factory({
|
||||
data: {
|
||||
backend: {
|
||||
metrics: {
|
||||
plugin: {
|
||||
'my-plugin': {
|
||||
meter: {
|
||||
name: 'my-plugin-metrics',
|
||||
version: '1.2.3',
|
||||
schemaUrl: 'https://example.com/schema',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
metricsServiceFactory,
|
||||
],
|
||||
}).getSubject('my-plugin');
|
||||
|
||||
expect(createSpy).toHaveBeenCalledWith({
|
||||
name: 'my-plugin-metrics',
|
||||
version: '1.2.3',
|
||||
schemaUrl: 'https://example.com/schema',
|
||||
});
|
||||
});
|
||||
|
||||
it('should implement the full MetricsService interface', async () => {
|
||||
const subject = await ServiceFactoryTester.from(metricsServiceFactory, {
|
||||
dependencies: defaultServices,
|
||||
}).getSubject('test-plugin');
|
||||
|
||||
expect(createSpy).toHaveBeenCalledWith({
|
||||
name: 'backstage-plugin-test-plugin',
|
||||
version: undefined,
|
||||
schemaUrl: undefined,
|
||||
});
|
||||
|
||||
expect(subject.createCounter).toBeDefined();
|
||||
expect(subject.createUpDownCounter).toBeDefined();
|
||||
expect(subject.createHistogram).toBeDefined();
|
||||
expect(subject.createGauge).toBeDefined();
|
||||
expect(subject.createObservableCounter).toBeDefined();
|
||||
expect(subject.createObservableUpDownCounter).toBeDefined();
|
||||
expect(subject.createObservableGauge).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { metricsServiceRef } from '@backstage/backend-plugin-api/alpha';
|
||||
import {
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { DefaultMetricsService } from './DefaultMetricsService';
|
||||
|
||||
/**
|
||||
* Service factory for collecting plugin-scoped metrics.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const metricsServiceFactory = createServiceFactory({
|
||||
service: metricsServiceRef,
|
||||
deps: {
|
||||
config: coreServices.rootConfig,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
},
|
||||
factory: ({ config, pluginMetadata }) => {
|
||||
const pluginId = pluginMetadata.getId();
|
||||
|
||||
const meterConfig = config.getOptionalConfig(
|
||||
`backend.metrics.plugin.${pluginId}.meter`,
|
||||
);
|
||||
const scopeName = `backstage-plugin-${pluginId}`;
|
||||
const name = meterConfig?.getOptionalString('name') ?? scopeName;
|
||||
const version = meterConfig?.getOptionalString('version');
|
||||
const schemaUrl = meterConfig?.getOptionalString('schemaUrl');
|
||||
|
||||
return DefaultMetricsService.create({ name, version, schemaUrl });
|
||||
},
|
||||
});
|
||||
@@ -16,4 +16,5 @@
|
||||
|
||||
export { actionsRegistryServiceFactory } from './entrypoints/actionsRegistry';
|
||||
export { actionsServiceFactory } from './entrypoints/actions';
|
||||
export { metricsServiceFactory } from './entrypoints/metrics';
|
||||
export { rootSystemMetadataServiceFactory } from './entrypoints/rootSystemMetadata';
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# @backstage/backend-dynamic-feature-service
|
||||
|
||||
## 0.7.10-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
|
||||
- Updated dependencies
|
||||
- @backstage/cli-common@0.2.0-next.0
|
||||
- @backstage/cli-node@0.2.19-next.0
|
||||
- @backstage/backend-defaults@0.15.3-next.0
|
||||
- @backstage/plugin-catalog-backend@3.5.0-next.0
|
||||
- @backstage/config-loader@1.10.9-next.0
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/backend-openapi-utils@0.6.7-next.0
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/types@1.2.2
|
||||
- @backstage/plugin-app-node@0.1.43-next.0
|
||||
- @backstage/plugin-auth-node@0.6.14-next.0
|
||||
- @backstage/plugin-events-backend@0.5.12-next.0
|
||||
- @backstage/plugin-events-node@0.4.20-next.0
|
||||
- @backstage/plugin-permission-common@0.9.6
|
||||
- @backstage/plugin-permission-node@0.10.11-next.0
|
||||
- @backstage/plugin-scaffolder-node@0.12.6-next.0
|
||||
- @backstage/plugin-search-backend-node@1.4.2-next.0
|
||||
- @backstage/plugin-search-common@1.2.22
|
||||
|
||||
## 0.7.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-dynamic-feature-service",
|
||||
"version": "0.7.9",
|
||||
"version": "0.7.10-next.0",
|
||||
"description": "Backstage dynamic feature service",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -38,7 +38,7 @@ import { createSpecializedBackend } from '@backstage/backend-app-api';
|
||||
import { ConfigSources } from '@backstage/config-loader';
|
||||
import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils';
|
||||
import { PluginScanner } from '../scanner/plugin-scanner';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
|
||||
import { BackstagePackageJson, PackageRole } from '@backstage/cli-node';
|
||||
@@ -956,7 +956,7 @@ describe('backend-dynamic-feature-service', () => {
|
||||
|
||||
mockDir.setContent({
|
||||
'package.json': fs.readFileSync(
|
||||
findPaths(__dirname).resolveTargetRoot('package.json'),
|
||||
targetPaths.resolveRoot('package.json'),
|
||||
),
|
||||
'dynamic-plugins-root': {},
|
||||
'dynamic-plugins-root/a-dynamic-plugin': ctx =>
|
||||
@@ -1044,7 +1044,7 @@ describe('backend-dynamic-feature-service', () => {
|
||||
otherMockDir.resolve('a-dynamic-plugin'),
|
||||
);
|
||||
expect(mockedModuleLoader.bootstrap).toHaveBeenCalledWith(
|
||||
findPaths(__dirname).targetRoot,
|
||||
targetPaths.rootDir,
|
||||
[realPath],
|
||||
new Map<string, ScannedPluginManifest>([
|
||||
[
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { PackageRole, PackageRoles } from '@backstage/cli-node';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ export class DynamicPluginManager implements DynamicPluginProvider {
|
||||
options: DynamicPluginManagerOptions,
|
||||
): Promise<DynamicPluginManager> {
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
const backstageRoot = findPaths(__dirname).targetRoot;
|
||||
const backstageRoot = targetPaths.rootDir;
|
||||
const scanner = PluginScanner.create({
|
||||
config: options.config,
|
||||
logger: options.logger,
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
import * as path from 'node:path';
|
||||
@@ -100,7 +100,7 @@ const dynamicPluginsSchemasServiceFactoryWithOptions = (
|
||||
config,
|
||||
logger,
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
backstageRoot: findPaths(__dirname).targetRoot,
|
||||
backstageRoot: targetPaths.rootDir,
|
||||
preferAlpha: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/backend-openapi-utils
|
||||
|
||||
## 0.6.7-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/types@1.2.2
|
||||
|
||||
## 0.6.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-openapi-utils",
|
||||
"version": "0.6.6",
|
||||
"version": "0.6.7-next.0",
|
||||
"description": "OpenAPI typescript support.",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/backend-plugin-api
|
||||
|
||||
## 1.7.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1ee5b28: Adds an alpha `MetricsService` to provide a unified interface for metrics instrumentation across Backstage plugins.
|
||||
- Updated dependencies
|
||||
- @backstage/cli-common@0.2.0-next.0
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/types@1.2.2
|
||||
- @backstage/plugin-auth-node@0.6.14-next.0
|
||||
- @backstage/plugin-permission-common@0.9.6
|
||||
- @backstage/plugin-permission-node@0.10.11-next.0
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-plugin-api",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1-next.0",
|
||||
"description": "Core API used by Backstage backend plugins",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -103,6 +103,150 @@ export const actionsServiceRef: ServiceRef<
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @alpha
|
||||
export interface MetricAdvice {
|
||||
explicitBucketBoundaries?: number[];
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricAttributes {
|
||||
// (undocumented)
|
||||
[attributeKey: string]: MetricAttributeValue | undefined;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export type MetricAttributeValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<null | undefined | string>
|
||||
| Array<null | undefined | number>
|
||||
| Array<null | undefined | boolean>;
|
||||
|
||||
// @alpha
|
||||
export interface MetricOptions {
|
||||
advice?: MetricAdvice;
|
||||
description?: string;
|
||||
unit?: string;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricsService {
|
||||
createCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceCounter<TAttributes>;
|
||||
createGauge<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceGauge<TAttributes>;
|
||||
createHistogram<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceHistogram<TAttributes>;
|
||||
createObservableCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableCounter<TAttributes>;
|
||||
createObservableGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableGauge<TAttributes>;
|
||||
createObservableUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableUpDownCounter<TAttributes>;
|
||||
createUpDownCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceUpDownCounter<TAttributes>;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
add(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
record(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceHistogram<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
record(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceObservable<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
addCallback(callback: MetricsServiceObservableCallback<TAttributes>): void;
|
||||
// (undocumented)
|
||||
removeCallback(callback: MetricsServiceObservableCallback<TAttributes>): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export type MetricsServiceObservableCallback<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = (
|
||||
observableResult: MetricsServiceObservableResult<TAttributes>,
|
||||
) => void | Promise<void>;
|
||||
|
||||
// @alpha
|
||||
export type MetricsServiceObservableCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
// @alpha
|
||||
export type MetricsServiceObservableGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceObservableResult<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
observe(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export type MetricsServiceObservableUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
// @alpha
|
||||
export const metricsServiceRef: ServiceRef<
|
||||
MetricsService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @alpha
|
||||
export interface MetricsServiceUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
// (undocumented)
|
||||
add(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface RootSystemMetadataService {
|
||||
// (undocumented)
|
||||
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* Copyright 2026 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attribute values that can be attached to metric measurements.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type MetricAttributeValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| Array<null | undefined | string>
|
||||
| Array<null | undefined | number>
|
||||
| Array<null | undefined | boolean>;
|
||||
|
||||
/**
|
||||
* A set of key-value pairs that can be attached to metric measurements.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricAttributes {
|
||||
[attributeKey: string]: MetricAttributeValue | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Advisory options that influence aggregation configuration.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricAdvice {
|
||||
/**
|
||||
* Hint the explicit bucket boundaries for histogram aggregation.
|
||||
*/
|
||||
explicitBucketBoundaries?: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a metric instrument.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricOptions {
|
||||
/**
|
||||
* The description of the Metric.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The unit of the Metric values.
|
||||
*/
|
||||
unit?: string;
|
||||
/**
|
||||
* Advisory options that influence aggregation configuration.
|
||||
*/
|
||||
advice?: MetricAdvice;
|
||||
}
|
||||
|
||||
/**
|
||||
* A counter metric that only supports non-negative increments.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
add(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A counter metric that supports both positive and negative increments.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
add(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A histogram metric for recording distributions of values.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceHistogram<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
record(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A gauge metric for recording instantaneous values.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
record(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The result object passed to observable metric callbacks.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceObservableResult<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
observe(value: number, attributes?: TAttributes): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback function for observable metrics. Called whenever a metric
|
||||
* collection is initiated.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type MetricsServiceObservableCallback<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = (
|
||||
observableResult: MetricsServiceObservableResult<TAttributes>,
|
||||
) => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* An observable metric instrument that reports values via callbacks.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsServiceObservable<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> {
|
||||
addCallback(callback: MetricsServiceObservableCallback<TAttributes>): void;
|
||||
removeCallback(callback: MetricsServiceObservableCallback<TAttributes>): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable counter metric that reports non-negative sums via callbacks.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type MetricsServiceObservableCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
/**
|
||||
* An observable counter metric that reports sums that can go up or down
|
||||
* via callbacks.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type MetricsServiceObservableUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
/**
|
||||
* An observable gauge metric that reports instantaneous values via callbacks.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export type MetricsServiceObservableGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
> = MetricsServiceObservable<TAttributes>;
|
||||
|
||||
/**
|
||||
* A service that provides a facility for emitting metrics.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MetricsService {
|
||||
/**
|
||||
* Creates a new counter metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The counter metric.
|
||||
*/
|
||||
createCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceCounter<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new up-down counter metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The up-down counter metric.
|
||||
*/
|
||||
createUpDownCounter<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceUpDownCounter<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new histogram metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The histogram metric.
|
||||
*/
|
||||
createHistogram<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceHistogram<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new gauge metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The gauge metric.
|
||||
*/
|
||||
createGauge<TAttributes extends MetricAttributes = MetricAttributes>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceGauge<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new observable counter metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The observable counter metric.
|
||||
*/
|
||||
createObservableCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableCounter<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new observable up-down counter metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The observable up-down counter metric.
|
||||
*/
|
||||
createObservableUpDownCounter<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableUpDownCounter<TAttributes>;
|
||||
|
||||
/**
|
||||
* Creates a new observable gauge metric.
|
||||
*
|
||||
* @param name - The name of the metric.
|
||||
* @param opts - The options for the metric.
|
||||
* @returns The observable gauge metric.
|
||||
*/
|
||||
createObservableGauge<
|
||||
TAttributes extends MetricAttributes = MetricAttributes,
|
||||
>(
|
||||
name: string,
|
||||
opts?: MetricOptions,
|
||||
): MetricsServiceObservableGauge<TAttributes>;
|
||||
}
|
||||
@@ -27,8 +27,27 @@ export type {
|
||||
|
||||
export type { ActionsService, ActionsServiceAction } from './ActionsService';
|
||||
|
||||
export type {
|
||||
MetricsService,
|
||||
MetricAdvice,
|
||||
MetricAttributes,
|
||||
MetricAttributeValue,
|
||||
MetricOptions,
|
||||
MetricsServiceCounter,
|
||||
MetricsServiceUpDownCounter,
|
||||
MetricsServiceHistogram,
|
||||
MetricsServiceGauge,
|
||||
MetricsServiceObservable,
|
||||
MetricsServiceObservableCallback,
|
||||
MetricsServiceObservableCounter,
|
||||
MetricsServiceObservableGauge,
|
||||
MetricsServiceObservableResult,
|
||||
MetricsServiceObservableUpDownCounter,
|
||||
} from './MetricsService';
|
||||
|
||||
export {
|
||||
actionsRegistryServiceRef,
|
||||
actionsServiceRef,
|
||||
metricsServiceRef,
|
||||
rootSystemMetadataServiceRef,
|
||||
} from './refs';
|
||||
|
||||
@@ -56,3 +56,14 @@ export const rootSystemMetadataServiceRef = createServiceRef<
|
||||
id: 'alpha.core.rootSystemMetadata',
|
||||
scope: 'root',
|
||||
});
|
||||
|
||||
/**
|
||||
* Service for managing metrics.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const metricsServiceRef = createServiceRef<
|
||||
import('./MetricsService').MetricsService
|
||||
>({
|
||||
id: 'alpha.core.metrics',
|
||||
});
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# @backstage/backend-test-utils
|
||||
|
||||
## 1.11.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 1ee5b28: Adds a new metrics service mock to be leveraged in tests
|
||||
- Updated dependencies
|
||||
- @backstage/backend-defaults@0.15.3-next.0
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/backend-app-api@1.5.1-next.0
|
||||
- @backstage/config@1.3.6
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/types@1.2.2
|
||||
- @backstage/plugin-auth-node@0.6.14-next.0
|
||||
- @backstage/plugin-events-node@0.4.20-next.0
|
||||
- @backstage/plugin-permission-common@0.9.6
|
||||
|
||||
## 1.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-test-utils",
|
||||
"version": "1.11.0",
|
||||
"version": "1.11.1-next.0",
|
||||
"description": "Test helpers library for Backstage backends",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -12,6 +12,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { MetricsService } from '@backstage/backend-plugin-api/alpha';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
|
||||
// @alpha (undocumented)
|
||||
@@ -43,6 +44,16 @@ export namespace actionsServiceMock {
|
||||
) => ServiceMock<ActionsService>;
|
||||
}
|
||||
|
||||
// @alpha (undocumented)
|
||||
export namespace metricsServiceMock {
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<MetricsService, 'plugin', 'singleton'>;
|
||||
const // (undocumented)
|
||||
mock: (
|
||||
partialImpl?: Partial<MetricsService> | undefined,
|
||||
) => ServiceMock<MetricsService>;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export class MockActionsRegistry
|
||||
implements ActionsRegistryService, ActionsService
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createServiceMock } from './alphaCreateServiceMock';
|
||||
import {
|
||||
MetricsService,
|
||||
metricsServiceRef,
|
||||
} from '@backstage/backend-plugin-api/alpha';
|
||||
import { metricsServiceFactory } from '@backstage/backend-defaults/alpha';
|
||||
|
||||
/**
|
||||
* @alpha
|
||||
*/
|
||||
export namespace metricsServiceMock {
|
||||
export const factory = () => metricsServiceFactory;
|
||||
|
||||
export const mock = createServiceMock<MetricsService>(
|
||||
metricsServiceRef,
|
||||
() => ({
|
||||
createCounter: jest.fn().mockImplementation(() => ({
|
||||
add: jest.fn(),
|
||||
})),
|
||||
createUpDownCounter: jest.fn().mockImplementation(() => ({
|
||||
add: jest.fn(),
|
||||
})),
|
||||
createHistogram: jest.fn().mockImplementation(() => ({
|
||||
record: jest.fn(),
|
||||
})),
|
||||
createGauge: jest.fn().mockImplementation(() => ({
|
||||
record: jest.fn(),
|
||||
})),
|
||||
createObservableCounter: jest.fn().mockImplementation(() => ({
|
||||
addCallback: jest.fn(),
|
||||
removeCallback: jest.fn(),
|
||||
})),
|
||||
createObservableUpDownCounter: jest.fn().mockImplementation(() => ({
|
||||
addCallback: jest.fn(),
|
||||
removeCallback: jest.fn(),
|
||||
})),
|
||||
createObservableGauge: jest.fn().mockImplementation(() => ({
|
||||
addCallback: jest.fn(),
|
||||
removeCallback: jest.fn(),
|
||||
})),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -17,4 +17,5 @@
|
||||
export { actionsRegistryServiceMock } from './ActionsRegistryServiceMock';
|
||||
export { MockActionsRegistry } from './MockActionsRegistry';
|
||||
export { actionsServiceMock } from './ActionsServiceMock';
|
||||
export { metricsServiceMock } from './MetricsServiceMock';
|
||||
export { type ServiceMock } from './alphaCreateServiceMock';
|
||||
|
||||
@@ -43,6 +43,7 @@ import { HostDiscovery } from '@backstage/backend-defaults/discovery';
|
||||
import {
|
||||
actionsRegistryServiceMock,
|
||||
actionsServiceMock,
|
||||
metricsServiceMock,
|
||||
} from '../alpha/services';
|
||||
|
||||
/** @public */
|
||||
@@ -92,6 +93,7 @@ export const defaultServiceFactories = [
|
||||
// Alpha services
|
||||
actionsRegistryServiceMock.factory(),
|
||||
actionsServiceMock.factory(),
|
||||
metricsServiceMock.factory(),
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# example-backend
|
||||
|
||||
## 0.0.48-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-defaults@0.15.3-next.0
|
||||
- @backstage/plugin-auth-backend@0.27.1-next.0
|
||||
- @backstage/plugin-catalog-backend@3.5.0-next.0
|
||||
- @backstage/plugin-scaffolder-backend@3.1.4-next.0
|
||||
- @backstage/backend-plugin-api@1.7.1-next.0
|
||||
- @backstage/plugin-mcp-actions-backend@0.1.10-next.0
|
||||
- @backstage/catalog-model@1.7.6
|
||||
- @backstage/plugin-app-backend@0.5.12-next.0
|
||||
- @backstage/plugin-auth-backend-module-github-provider@0.5.1-next.0
|
||||
- @backstage/plugin-auth-backend-module-guest-provider@0.2.17-next.0
|
||||
- @backstage/plugin-auth-backend-module-openshift-provider@0.1.5-next.0
|
||||
- @backstage/plugin-auth-node@0.6.14-next.0
|
||||
- @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.12-next.0
|
||||
- @backstage/plugin-catalog-backend-module-openapi@0.2.20-next.0
|
||||
- @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.18-next.0
|
||||
- @backstage/plugin-catalog-backend-module-unprocessed@0.6.9-next.0
|
||||
- @backstage/plugin-devtools-backend@0.5.15-next.0
|
||||
- @backstage/plugin-events-backend@0.5.12-next.0
|
||||
- @backstage/plugin-events-backend-module-google-pubsub@0.2.1-next.0
|
||||
- @backstage/plugin-kubernetes-backend@0.21.2-next.0
|
||||
- @backstage/plugin-notifications-backend@0.6.3-next.0
|
||||
- @backstage/plugin-permission-backend@0.7.10-next.0
|
||||
- @backstage/plugin-permission-backend-module-allow-all-policy@0.2.17-next.0
|
||||
- @backstage/plugin-permission-common@0.9.6
|
||||
- @backstage/plugin-permission-node@0.10.11-next.0
|
||||
- @backstage/plugin-proxy-backend@0.6.11-next.0
|
||||
- @backstage/plugin-scaffolder-backend-module-github@0.9.7-next.0
|
||||
- @backstage/plugin-scaffolder-backend-module-notifications@0.1.20-next.0
|
||||
- @backstage/plugin-search-backend@2.0.13-next.0
|
||||
- @backstage/plugin-search-backend-module-catalog@0.3.13-next.0
|
||||
- @backstage/plugin-search-backend-module-elasticsearch@1.8.1-next.0
|
||||
- @backstage/plugin-search-backend-module-explore@0.3.12-next.0
|
||||
- @backstage/plugin-search-backend-module-techdocs@0.4.12-next.0
|
||||
- @backstage/plugin-search-backend-node@1.4.2-next.0
|
||||
- @backstage/plugin-signals-backend@0.3.13-next.0
|
||||
- @backstage/plugin-techdocs-backend@2.1.6-next.0
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.0.47",
|
||||
"version": "0.0.48-next.0",
|
||||
"backstage": {
|
||||
"role": "backend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/catalog-client
|
||||
|
||||
## 1.13.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- d2494d6: Minor update to catalog client docs
|
||||
- Updated dependencies
|
||||
- @backstage/catalog-model@1.7.6
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/filter-predicates@0.1.0
|
||||
|
||||
## 1.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-client",
|
||||
"version": "1.13.0",
|
||||
"version": "1.13.1-next.0",
|
||||
"description": "An isomorphic client for the catalog backend",
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
|
||||
@@ -535,9 +535,7 @@ export class CatalogClient implements CatalogApi {
|
||||
} while (cursor);
|
||||
}
|
||||
|
||||
//
|
||||
// Private methods
|
||||
//
|
||||
// #region Private methods
|
||||
|
||||
private async requestIgnored(response: Response): Promise<void> {
|
||||
if (!response.ok) {
|
||||
@@ -588,4 +586,6 @@ export class CatalogClient implements CatalogApi {
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import { FilterPredicate } from '@backstage/filter-predicates';
|
||||
|
||||
/**
|
||||
* This symbol can be used in place of a value when passed to filters in e.g.
|
||||
* {@link CatalogClient.getEntities}, to signify that you want to filter on the
|
||||
* {@link CatalogApi.getEntities}, to signify that you want to filter on the
|
||||
* presence of that key no matter what its value is.
|
||||
*
|
||||
* @public
|
||||
@@ -146,7 +146,7 @@ export type EntityOrderQuery =
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.getEntities}.
|
||||
* The request type for {@link CatalogApi.getEntities}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -180,7 +180,7 @@ export interface GetEntitiesRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.getEntities}.
|
||||
* The response type for {@link CatalogApi.getEntities}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -189,7 +189,7 @@ export interface GetEntitiesResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.getEntitiesByRefs}.
|
||||
* The request type for {@link CatalogApi.getEntitiesByRefs}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -200,7 +200,7 @@ export interface GetEntitiesByRefsRequest {
|
||||
* @remarks
|
||||
*
|
||||
* The returned list of entities will be in the same order as the refs, and
|
||||
* null will be returned in those positions that were not found.
|
||||
* undefined will be returned in those positions that were not found.
|
||||
*/
|
||||
entityRefs: string[];
|
||||
/**
|
||||
@@ -215,7 +215,7 @@ export interface GetEntitiesByRefsRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.getEntitiesByRefs}.
|
||||
* The response type for {@link CatalogApi.getEntitiesByRefs}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -226,13 +226,13 @@ export interface GetEntitiesByRefsResponse {
|
||||
* @remarks
|
||||
*
|
||||
* The list will be in the same order as the refs given in the request, and
|
||||
* null will be returned in those positions that were not found.
|
||||
* undefined will be returned in those positions that were not found.
|
||||
*/
|
||||
items: Array<Entity | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.getEntityAncestors}.
|
||||
* The request type for {@link CatalogApi.getEntityAncestors}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -241,7 +241,7 @@ export interface GetEntityAncestorsRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.getEntityAncestors}.
|
||||
* The response type for {@link CatalogApi.getEntityAncestors}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -254,7 +254,7 @@ export interface GetEntityAncestorsResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.getEntityFacets}.
|
||||
* The request type for {@link CatalogApi.getEntityFacets}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -323,7 +323,7 @@ export interface GetEntityFacetsRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.getEntityFacets}.
|
||||
* The response type for {@link CatalogApi.getEntityFacets}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -355,7 +355,7 @@ export type Location = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.getLocations}
|
||||
* The response type for {@link CatalogApi.getLocations}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -364,7 +364,7 @@ export interface GetLocationsResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.addLocation}.
|
||||
* The request type for {@link CatalogApi.addLocation}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -379,7 +379,7 @@ export type AddLocationRequest = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.addLocation}.
|
||||
* The response type for {@link CatalogApi.addLocation}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -396,7 +396,7 @@ export type AddLocationResponse = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.validateEntity}
|
||||
* The response type for {@link CatalogApi.validateEntity}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -405,7 +405,7 @@ export type ValidateEntityResponse =
|
||||
| { valid: false; errors: SerializedError[] };
|
||||
|
||||
/**
|
||||
* The request type for {@link CatalogClient.queryEntities}.
|
||||
* The request type for {@link CatalogApi.queryEntities}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -414,7 +414,7 @@ export type QueryEntitiesRequest =
|
||||
| QueryEntitiesCursorRequest;
|
||||
|
||||
/**
|
||||
* A request type for {@link CatalogClient.queryEntities}.
|
||||
* A request type for {@link CatalogApi.queryEntities}.
|
||||
* The method takes this type in an initial pagination request,
|
||||
* when requesting the first batch of entities.
|
||||
*
|
||||
@@ -436,7 +436,7 @@ export type QueryEntitiesInitialRequest = {
|
||||
};
|
||||
|
||||
/**
|
||||
* A request type for {@link CatalogClient.queryEntities}.
|
||||
* A request type for {@link CatalogApi.queryEntities}.
|
||||
* The method takes this type in a pagination request, following
|
||||
* the initial request.
|
||||
*
|
||||
@@ -449,7 +449,7 @@ export type QueryEntitiesCursorRequest = {
|
||||
};
|
||||
|
||||
/**
|
||||
* The response type for {@link CatalogClient.queryEntities}.
|
||||
* The response type for {@link CatalogApi.queryEntities}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -467,7 +467,7 @@ export type QueryEntitiesResponse = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream entities request for {@link CatalogClient.streamEntities}.
|
||||
* Stream entities request for {@link CatalogApi.streamEntities}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
@@ -546,7 +546,7 @@ export interface CatalogApi {
|
||||
*
|
||||
* The output list of entities is of the same size and in the same order as
|
||||
* the requested list of entity refs. Entries that are not found are returned
|
||||
* as null.
|
||||
* as undefined.
|
||||
*
|
||||
* @param request - Request parameters
|
||||
* @param options - Additional options
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of the DiscoveryApi, to avoid importing core-plugin-api.
|
||||
* This is a structurally similar version of `DiscoveryApi` /
|
||||
* `DiscoveryService`, used here to avoid dependencies on the frontend or
|
||||
* backend plugin API packages and allowing both of those forms to be passed in.
|
||||
*/
|
||||
export type DiscoveryApi = {
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of FetchApi, to avoid importing core-plugin-api.
|
||||
* This is a structurally similar version of `FetchApi`, used here to avoid
|
||||
* dependencies on the frontend or backend plugin API packages and allowing both
|
||||
* of those forms to be passed in.
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# @backstage/cli-common
|
||||
|
||||
## 0.2.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 56bd494: Added `targetPaths` and `findOwnPaths` as replacements for `findPaths`, with a cleaner separation between target project paths and package-relative paths.
|
||||
|
||||
To migrate existing `findPaths` usage:
|
||||
|
||||
```ts
|
||||
// Before
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
const paths = findPaths(__dirname);
|
||||
|
||||
// After — for target project paths (cwd-based):
|
||||
import { targetPaths } from '@backstage/cli-common';
|
||||
// paths.targetDir → targetPaths.dir
|
||||
// paths.targetRoot → targetPaths.rootDir
|
||||
// paths.resolveTarget('src') → targetPaths.resolve('src')
|
||||
// paths.resolveTargetRoot('yarn.lock') → targetPaths.resolveRoot('yarn.lock')
|
||||
|
||||
// After — for package-relative paths:
|
||||
import { findOwnPaths } from '@backstage/cli-common';
|
||||
const own = findOwnPaths(__dirname);
|
||||
// paths.ownDir → own.dir
|
||||
// paths.ownRoot → own.rootDir
|
||||
// paths.resolveOwn('config/jest.js') → own.resolve('config/jest.js')
|
||||
// paths.resolveOwnRoot('tsconfig.json') → own.resolveRoot('tsconfig.json')
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/errors@1.2.7
|
||||
|
||||
## 0.1.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{
|
||||
"name": "@backstage/cli-common",
|
||||
"version": "0.1.18",
|
||||
"version": "0.2.0-next.0",
|
||||
"description": "Common functionality used by cli, backend, and create-app",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
@@ -20,8 +18,23 @@
|
||||
"directory": "packages/cli-common"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./testUtils": "./src/testUtils.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"testUtils": [
|
||||
"src/testUtils.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
## API Report File for "@backstage/cli-common"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
// @public
|
||||
export function overrideTargetPaths(
|
||||
dirOrOptions: string | OverrideTargetPathsOptions,
|
||||
): TargetPathsOverride;
|
||||
|
||||
// @public
|
||||
export interface OverrideTargetPathsOptions {
|
||||
dir: string;
|
||||
rootDir?: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface TargetPathsOverride {
|
||||
restore(): void;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -16,17 +16,27 @@ export function bootstrapEnvProxyAgents(): void;
|
||||
// @public
|
||||
export class ExitCodeError extends CustomErrorBase {
|
||||
constructor(code: number, command?: string);
|
||||
// (undocumented)
|
||||
readonly code: number;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function findOwnPaths(searchDir: string): OwnPaths;
|
||||
|
||||
// @public @deprecated
|
||||
export function findPaths(searchDir: string): Paths;
|
||||
|
||||
// @public
|
||||
export function isChildPath(base: string, path: string): boolean;
|
||||
|
||||
// @public
|
||||
export type OwnPaths = {
|
||||
dir: string;
|
||||
rootDir: string;
|
||||
resolve: ResolveFunc;
|
||||
resolveRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
// @public @deprecated
|
||||
export type Paths = {
|
||||
ownDir: string;
|
||||
ownRoot: string;
|
||||
@@ -68,4 +78,15 @@ export function runOutput(
|
||||
args: string[],
|
||||
options?: RunOptions,
|
||||
): Promise<string>;
|
||||
|
||||
// @public
|
||||
export type TargetPaths = {
|
||||
dir: string;
|
||||
rootDir: string;
|
||||
resolve: ResolveFunc;
|
||||
resolveRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
// @public
|
||||
export const targetPaths: TargetPaths;
|
||||
```
|
||||
|
||||
@@ -21,6 +21,7 @@ import { CustomErrorBase } from '@backstage/errors';
|
||||
* @public
|
||||
*/
|
||||
export class ExitCodeError extends CustomErrorBase {
|
||||
/** The exit code of the child process. */
|
||||
readonly code: number;
|
||||
|
||||
constructor(code: number, command?: string) {
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { findPaths, BACKSTAGE_JSON } from './paths';
|
||||
export { findPaths, findOwnPaths, targetPaths, BACKSTAGE_JSON } from './paths';
|
||||
export { isChildPath } from './isChildPath';
|
||||
export type { Paths, ResolveFunc } from './paths';
|
||||
export type { Paths, TargetPaths, OwnPaths, ResolveFunc } from './paths';
|
||||
export { bootstrapEnvProxyAgents } from './proxyBootstrap';
|
||||
export {
|
||||
run,
|
||||
|
||||
@@ -25,35 +25,61 @@ import { dirname, resolve as resolvePath } from 'node:path';
|
||||
*/
|
||||
export type ResolveFunc = (...paths: string[]) => string;
|
||||
|
||||
/**
|
||||
* Resolved paths relative to the target project, based on `process.cwd()`.
|
||||
* Lazily initialized on first property access. Re-resolves automatically
|
||||
* when `process.cwd()` changes.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type TargetPaths = {
|
||||
/** The target package directory. */
|
||||
dir: string;
|
||||
|
||||
/** The target monorepo root directory. */
|
||||
rootDir: string;
|
||||
|
||||
/** Resolve a path relative to the target package directory. */
|
||||
resolve: ResolveFunc;
|
||||
|
||||
/** Resolve a path relative to the target repo root. */
|
||||
resolveRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolved paths relative to a specific package in the repository.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type OwnPaths = {
|
||||
/** The package root directory. */
|
||||
dir: string;
|
||||
|
||||
/** The monorepo root directory containing the package. */
|
||||
rootDir: string;
|
||||
|
||||
/** Resolve a path relative to the package root. */
|
||||
resolve: ResolveFunc;
|
||||
|
||||
/** Resolve a path relative to the monorepo root containing the package. */
|
||||
resolveRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Common paths and resolve functions used by the cli.
|
||||
* Currently assumes it is being executed within a monorepo.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link targetPaths} and {@link findOwnPaths} instead.
|
||||
*/
|
||||
export type Paths = {
|
||||
// Root dir of the cli itself, containing package.json
|
||||
ownDir: string;
|
||||
|
||||
// Monorepo root dir of the cli itself. Only accessible when running inside Backstage repo.
|
||||
ownRoot: string;
|
||||
|
||||
// The location of the app that the cli is being executed in
|
||||
targetDir: string;
|
||||
|
||||
// The monorepo root package of the app that the cli is being executed in.
|
||||
targetRoot: string;
|
||||
|
||||
// Resolve a path relative to own repo
|
||||
resolveOwn: ResolveFunc;
|
||||
|
||||
// Resolve a path relative to own monorepo root. Only accessible when running inside Backstage repo.
|
||||
resolveOwnRoot: ResolveFunc;
|
||||
|
||||
// Resolve a path relative to the app
|
||||
resolveTarget: ResolveFunc;
|
||||
|
||||
// Resolve a path relative to the app repo root
|
||||
resolveTargetRoot: ResolveFunc;
|
||||
};
|
||||
|
||||
@@ -84,18 +110,7 @@ export function findRootPath(
|
||||
);
|
||||
}
|
||||
|
||||
// Finds the root of a given package
|
||||
export function findOwnDir(searchDir: string) {
|
||||
const path = findRootPath(searchDir, () => true);
|
||||
if (!path) {
|
||||
throw new Error(
|
||||
`No package.json found while searching for package root of ${searchDir}`,
|
||||
);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// Finds the root of the monorepo that the package exists in. Only accessible when running inside Backstage repo.
|
||||
// Finds the root of the monorepo that the package exists in.
|
||||
export function findOwnRootDir(ownDir: string) {
|
||||
const isLocal = fs.existsSync(resolvePath(ownDir, 'src'));
|
||||
if (!isLocal) {
|
||||
@@ -107,64 +122,209 @@ export function findOwnRootDir(ownDir: string) {
|
||||
return resolvePath(ownDir, '../..');
|
||||
}
|
||||
|
||||
// Hierarchical directory cache shared across all OwnPathsImpl instances.
|
||||
// When we resolve a searchDir to its package root, we also cache every
|
||||
// intermediate directory, so sibling directories share work.
|
||||
const dirCache = new Map<string, string>();
|
||||
|
||||
class OwnPathsImpl implements OwnPaths {
|
||||
static #instanceCache = new Map<string, OwnPathsImpl>();
|
||||
|
||||
static find(searchDir: string): OwnPathsImpl {
|
||||
const dir = OwnPathsImpl.findDir(searchDir);
|
||||
let instance = OwnPathsImpl.#instanceCache.get(dir);
|
||||
if (!instance) {
|
||||
instance = new OwnPathsImpl(dir);
|
||||
OwnPathsImpl.#instanceCache.set(dir, instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
static findDir(searchDir: string): string {
|
||||
const visited: string[] = [];
|
||||
let dir = searchDir;
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const cached = dirCache.get(dir);
|
||||
if (cached !== undefined) {
|
||||
for (const d of visited) {
|
||||
dirCache.set(d, cached);
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
visited.push(dir);
|
||||
|
||||
if (fs.existsSync(resolvePath(dir, 'package.json'))) {
|
||||
for (const d of visited) {
|
||||
dirCache.set(d, dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
const newDir = dirname(dir);
|
||||
if (newDir === dir) {
|
||||
break;
|
||||
}
|
||||
dir = newDir;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`No package.json found while searching for package root of ${searchDir}`,
|
||||
);
|
||||
}
|
||||
|
||||
#dir: string;
|
||||
#rootDir: string | undefined;
|
||||
|
||||
private constructor(dir: string) {
|
||||
this.#dir = dir;
|
||||
}
|
||||
|
||||
get dir(): string {
|
||||
return this.#dir;
|
||||
}
|
||||
|
||||
get rootDir(): string {
|
||||
this.#rootDir ??= findOwnRootDir(this.#dir);
|
||||
return this.#rootDir;
|
||||
}
|
||||
|
||||
resolve = (...paths: string[]): string => {
|
||||
return resolvePath(this.#dir, ...paths);
|
||||
};
|
||||
|
||||
resolveRoot = (...paths: string[]): string => {
|
||||
return resolvePath(this.rootDir, ...paths);
|
||||
};
|
||||
}
|
||||
|
||||
// Finds the root of a given package
|
||||
export function findOwnDir(searchDir: string) {
|
||||
return OwnPathsImpl.findDir(searchDir);
|
||||
}
|
||||
|
||||
// Used by the test utility in testUtils.ts to override targetPaths
|
||||
export let targetPathsOverride: TargetPaths | undefined;
|
||||
|
||||
/** @internal */
|
||||
export function setTargetPathsOverride(override: TargetPaths | undefined) {
|
||||
targetPathsOverride = override;
|
||||
}
|
||||
|
||||
class TargetPathsImpl implements TargetPaths {
|
||||
#cwd: string | undefined;
|
||||
#dir: string | undefined;
|
||||
#rootDir: string | undefined;
|
||||
|
||||
get dir(): string {
|
||||
if (targetPathsOverride) {
|
||||
return targetPathsOverride.dir;
|
||||
}
|
||||
const cwd = process.cwd();
|
||||
if (this.#dir !== undefined && this.#cwd === cwd) {
|
||||
return this.#dir;
|
||||
}
|
||||
this.#cwd = cwd;
|
||||
this.#rootDir = undefined;
|
||||
// Drive letter can end up being lowercased here on Windows, bring back to uppercase for consistency
|
||||
this.#dir = fs
|
||||
.realpathSync(cwd)
|
||||
.replace(/^[a-z]:/, str => str.toLocaleUpperCase('en-US'));
|
||||
return this.#dir;
|
||||
}
|
||||
|
||||
get rootDir(): string {
|
||||
if (targetPathsOverride) {
|
||||
return targetPathsOverride.rootDir;
|
||||
}
|
||||
// Access dir first to ensure cwd is fresh, which also invalidates rootDir on cwd change
|
||||
const dir = this.dir;
|
||||
if (this.#rootDir !== undefined) {
|
||||
return this.#rootDir;
|
||||
}
|
||||
// Lazy init to only crash commands that require a monorepo when we're not in one
|
||||
this.#rootDir =
|
||||
findRootPath(dir, path => {
|
||||
try {
|
||||
const content = fs.readFileSync(path, 'utf8');
|
||||
const data = JSON.parse(content);
|
||||
return Boolean(data.workspaces);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse package.json file while searching for root, ${error}`,
|
||||
);
|
||||
}
|
||||
}) ?? dir;
|
||||
return this.#rootDir;
|
||||
}
|
||||
|
||||
resolve = (...paths: string[]): string => {
|
||||
if (targetPathsOverride) {
|
||||
return targetPathsOverride.resolve(...paths);
|
||||
}
|
||||
return resolvePath(this.dir, ...paths);
|
||||
};
|
||||
|
||||
resolveRoot = (...paths: string[]): string => {
|
||||
if (targetPathsOverride) {
|
||||
return targetPathsOverride.resolveRoot(...paths);
|
||||
}
|
||||
return resolvePath(this.rootDir, ...paths);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily resolved paths relative to the target project. Import this directly
|
||||
* for cwd-based path resolution without needing `__dirname`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const targetPaths: TargetPaths = new TargetPathsImpl();
|
||||
|
||||
/**
|
||||
* Find paths relative to the package that the calling code lives in.
|
||||
*
|
||||
* Results are cached per package root, and the package root lookup uses a
|
||||
* hierarchical directory cache so that multiple calls from different
|
||||
* subdirectories within the same package share work.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function findOwnPaths(searchDir: string): OwnPaths {
|
||||
return OwnPathsImpl.find(searchDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find paths related to a package and its execution context.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link targetPaths} for cwd-based paths and
|
||||
* {@link findOwnPaths} for package-relative paths instead.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const paths = findPaths(__dirname)
|
||||
*/
|
||||
export function findPaths(searchDir: string): Paths {
|
||||
const ownDir = findOwnDir(searchDir);
|
||||
// Drive letter can end up being lowercased here on Windows, bring back to uppercase for consistency
|
||||
const targetDir = fs
|
||||
.realpathSync(process.cwd())
|
||||
.replace(/^[a-z]:/, str => str.toLocaleUpperCase('en-US'));
|
||||
|
||||
// Lazy load this as it will throw an error if we're not inside the Backstage repo.
|
||||
let ownRoot = '';
|
||||
const getOwnRoot = () => {
|
||||
if (!ownRoot) {
|
||||
ownRoot = findOwnRootDir(ownDir);
|
||||
}
|
||||
return ownRoot;
|
||||
};
|
||||
|
||||
// We're not always running in a monorepo, so we lazy init this to only crash commands
|
||||
// that require a monorepo when we're not in one.
|
||||
let targetRoot = '';
|
||||
const getTargetRoot = () => {
|
||||
if (!targetRoot) {
|
||||
targetRoot =
|
||||
findRootPath(targetDir, path => {
|
||||
try {
|
||||
const content = fs.readFileSync(path, 'utf8');
|
||||
const data = JSON.parse(content);
|
||||
return Boolean(data.workspaces);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse package.json file while searching for root, ${error}`,
|
||||
);
|
||||
}
|
||||
}) ?? targetDir; // We didn't find any root package.json, assume we're not in a monorepo
|
||||
}
|
||||
return targetRoot;
|
||||
};
|
||||
|
||||
const own = findOwnPaths(searchDir);
|
||||
return {
|
||||
ownDir,
|
||||
get ownDir() {
|
||||
return own.dir;
|
||||
},
|
||||
get ownRoot() {
|
||||
return getOwnRoot();
|
||||
return own.rootDir;
|
||||
},
|
||||
get targetDir() {
|
||||
return targetPaths.dir;
|
||||
},
|
||||
targetDir,
|
||||
get targetRoot() {
|
||||
return getTargetRoot();
|
||||
return targetPaths.rootDir;
|
||||
},
|
||||
resolveOwn: (...paths) => resolvePath(ownDir, ...paths),
|
||||
resolveOwnRoot: (...paths) => resolvePath(getOwnRoot(), ...paths),
|
||||
resolveTarget: (...paths) => resolvePath(targetDir, ...paths),
|
||||
resolveTargetRoot: (...paths) => resolvePath(getTargetRoot(), ...paths),
|
||||
resolveOwn: own.resolve,
|
||||
resolveOwnRoot: own.resolveRoot,
|
||||
resolveTarget: targetPaths.resolve,
|
||||
resolveTargetRoot: targetPaths.resolveRoot,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { resolve as resolvePath } from 'node:path';
|
||||
import { setTargetPathsOverride } from './paths';
|
||||
|
||||
/**
|
||||
* Options for {@link overrideTargetPaths}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface OverrideTargetPathsOptions {
|
||||
/** The target package directory. */
|
||||
dir: string;
|
||||
/** The target monorepo root directory. Defaults to `dir` if not provided. */
|
||||
rootDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of {@link overrideTargetPaths}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface TargetPathsOverride {
|
||||
/** Restores `targetPaths` to its normal behavior. */
|
||||
restore(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the `targetPaths` singleton to resolve from the given directory
|
||||
* instead of `process.cwd()`.
|
||||
*
|
||||
* When called with a string, that value is used as both `dir` and `rootDir`.
|
||||
* Pass an options object to set them independently.
|
||||
*
|
||||
* Calling `restore()` on the return value reverts to normal behavior.
|
||||
* Restoration is only needed if you want to change the override within a
|
||||
* test file; each Jest worker starts with a clean module state.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function overrideTargetPaths(
|
||||
dirOrOptions: string | OverrideTargetPathsOptions,
|
||||
): TargetPathsOverride {
|
||||
const { dir, rootDir } =
|
||||
typeof dirOrOptions === 'string'
|
||||
? { dir: dirOrOptions, rootDir: dirOrOptions }
|
||||
: {
|
||||
dir: dirOrOptions.dir,
|
||||
rootDir: dirOrOptions.rootDir ?? dirOrOptions.dir,
|
||||
};
|
||||
|
||||
setTargetPathsOverride({
|
||||
dir,
|
||||
rootDir,
|
||||
resolve: (...paths: string[]) => resolvePath(dir, ...paths),
|
||||
resolveRoot: (...paths: string[]) => resolvePath(rootDir, ...paths),
|
||||
});
|
||||
|
||||
return {
|
||||
restore() {
|
||||
setTargetPathsOverride(undefined);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/cli-node
|
||||
|
||||
## 0.2.19-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 06c2015: Added `runConcurrentTasks` and `runWorkerQueueThreads` utilities, moved from the `@backstage/cli` internal code.
|
||||
- 70fc178: Migrated from deprecated `findPaths` to `targetPaths` and `findOwnPaths` from `@backstage/cli-common`.
|
||||
- Updated dependencies
|
||||
- @backstage/cli-common@0.2.0-next.0
|
||||
- @backstage/errors@1.2.7
|
||||
- @backstage/types@1.2.2
|
||||
|
||||
## 0.2.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/cli-node",
|
||||
"version": "0.2.18",
|
||||
"version": "0.2.19-next.0",
|
||||
"description": "Node.js library for Backstage CLIs",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -86,6 +86,13 @@ export interface BackstagePackageJson {
|
||||
version: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ConcurrentTasksOptions<TItem> = {
|
||||
concurrencyFactor?: number;
|
||||
items: Iterable<TItem>;
|
||||
worker: (item: TItem) => Promise<void>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class GitUtils {
|
||||
static listChangedFiles(ref: string): Promise<string[]>;
|
||||
@@ -200,4 +207,27 @@ export class PackageRoles {
|
||||
static getRoleFromPackage(pkgJson: unknown): PackageRole | undefined;
|
||||
static getRoleInfo(role: string): PackageRoleInfo;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function runConcurrentTasks<TItem>(
|
||||
options: ConcurrentTasksOptions<TItem>,
|
||||
): Promise<void>;
|
||||
|
||||
// @public
|
||||
export function runWorkerQueueThreads<TItem, TResult, TContext>(
|
||||
options: WorkerQueueThreadsOptions<TItem, TResult, TContext>,
|
||||
): Promise<{
|
||||
results: TResult[];
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export type WorkerQueueThreadsOptions<TItem, TResult, TContext> = {
|
||||
items: Iterable<TItem>;
|
||||
workerFactory: (
|
||||
context: TContext,
|
||||
) =>
|
||||
| ((item: TItem) => Promise<TResult>)
|
||||
| Promise<(item: TItem) => Promise<TResult>>;
|
||||
context?: TContext;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import os from 'node:os';
|
||||
|
||||
const defaultConcurrency = Math.max(Math.ceil(os.cpus().length / 2), 1);
|
||||
|
||||
const CONCURRENCY_ENV_VAR = 'BACKSTAGE_CLI_CONCURRENCY';
|
||||
const DEPRECATED_CONCURRENCY_ENV_VAR = 'BACKSTAGE_CLI_BUILD_PARALLEL';
|
||||
|
||||
type ConcurrencyOption = boolean | string | number | null | undefined;
|
||||
|
||||
function parseConcurrencyOption(value: ConcurrencyOption): number {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultConcurrency;
|
||||
} else if (typeof value === 'boolean') {
|
||||
return value ? defaultConcurrency : 1;
|
||||
} else if (typeof value === 'number' && Number.isInteger(value)) {
|
||||
if (value < 1) {
|
||||
return 1;
|
||||
}
|
||||
return value;
|
||||
} else if (typeof value === 'string') {
|
||||
if (value === 'true') {
|
||||
return parseConcurrencyOption(true);
|
||||
} else if (value === 'false') {
|
||||
return parseConcurrencyOption(false);
|
||||
}
|
||||
const parsed = Number(value);
|
||||
if (Number.isInteger(parsed)) {
|
||||
return parseConcurrencyOption(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(
|
||||
`Concurrency option value '${value}' is not a boolean or integer`,
|
||||
);
|
||||
}
|
||||
|
||||
let hasWarnedDeprecation = false;
|
||||
|
||||
/** @internal */
|
||||
export function getEnvironmentConcurrency() {
|
||||
if (process.env[CONCURRENCY_ENV_VAR] !== undefined) {
|
||||
return parseConcurrencyOption(process.env[CONCURRENCY_ENV_VAR]);
|
||||
}
|
||||
if (process.env[DEPRECATED_CONCURRENCY_ENV_VAR] !== undefined) {
|
||||
if (!hasWarnedDeprecation) {
|
||||
hasWarnedDeprecation = true;
|
||||
console.warn(
|
||||
`The ${DEPRECATED_CONCURRENCY_ENV_VAR} environment variable is deprecated, use ${CONCURRENCY_ENV_VAR} instead`,
|
||||
);
|
||||
}
|
||||
return parseConcurrencyOption(process.env[DEPRECATED_CONCURRENCY_ENV_VAR]);
|
||||
}
|
||||
return defaultConcurrency;
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findPaths } from '@backstage/cli-common';
|
||||
|
||||
/* eslint-disable-next-line no-restricted-syntax */
|
||||
export const paths = findPaths(__dirname);
|
||||
export type { ConcurrentTasksOptions } from './runConcurrentTasks';
|
||||
export { runConcurrentTasks } from './runConcurrentTasks';
|
||||
export type { WorkerQueueThreadsOptions } from './runWorkerQueueThreads';
|
||||
export { runWorkerQueueThreads } from './runWorkerQueueThreads';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user