This file provides authoritative guidance for AI agents working on this repository. Read it fully before making any changes.
This is a standalone Backstage plugin monorepo providing GitLab integration for Backstage. It is published to npm as two separate packages:
| Package | npm name | Role |
|---|---|---|
packages/gitlab |
@immobiliarelabs/backstage-plugin-gitlab |
Frontend plugin |
packages/gitlab-backend |
@immobiliarelabs/backstage-plugin-gitlab-backend |
Backend plugin |
packages/dev-sandbox |
(private, not published) | Local dev/test app |
This is NOT a Backstage app — it is a plugin that gets installed into other people's Backstage apps. Never use backstage-cli versions:bump; bump dependencies manually.
- License: Apache 2.0
- Current version: 6.13.0 (both packages versioned in sync)
- Backstage compatibility: Tracks the latest stable Backstage release (
backstage.json) - Package manager: Yarn 4 (Berry) with
node-moduleslinker — useyarn, notnpm
.
├── packages/
│ ├── gitlab/ # Frontend plugin
│ │ ├── src/
│ │ │ ├── index.ts # Classic frontend system exports (PRIMARY)
│ │ │ ├── plugin.ts # Plugin definition + component extensions
│ │ │ ├── routes.ts # Route reference
│ │ │ ├── translation.ts # i18n messages
│ │ │ ├── api/ # GitlabCIApi interface + GitlabCIClient implementation
│ │ │ ├── components/ # React components and widgets
│ │ │ │ ├── GitlabCI/ # Main content page (grid layout)
│ │ │ │ ├── gitlabAppData.tsx # Entity annotation hooks
│ │ │ │ └── widgets/ # Individual cards and tables
│ │ │ └── alpha/ # New frontend system (EXPERIMENTAL)
│ │ │ ├── index.ts # New system default export
│ │ │ ├── plugin.ts # createFrontendPlugin definition
│ │ │ ├── extensions.tsx # EntityContentBlueprint, ApiBlueprint
│ │ │ └── cards.tsx # EntityCardBlueprint definitions
│ │ ├── config.d.ts # Frontend config schema
│ │ └── package.json
│ ├── gitlab-backend/ # Backend plugin
│ │ ├── src/
│ │ │ ├── index.ts # Public exports
│ │ │ ├── plugin.ts # Backend plugin + catalog module
│ │ │ ├── annotations.ts # Entity annotation constants
│ │ │ ├── processor/ # GitlabFillerProcessor (catalog)
│ │ │ └── service/ # Express router (API proxy)
│ │ ├── config.d.ts # Backend config schema
│ │ └── package.json
│ └── dev-sandbox/ # Local dev app (private, not published)
│ └── src/App.tsx # Uses new frontend system (createApp from @backstage/frontend-defaults)
├── .github/workflows/ # CI/CD pipelines
├── biome.json # Biome config (formatting, linting)
├── lefthook.yml # Pre-commit hooks and automation
├── tsconfig.json # Root TypeScript config (covers all packages)
├── backstage.json # Backstage version compatibility marker
├── commitlint.config.cjs # Conventional commits enforcement
├── lint-staged.config.cjs # Pre-commit hook runners
└── .releaserc.json # Semantic release config
All commands must be run from the repository root unless noted otherwise.
yarn start # Start dev-sandbox app (new frontend system)yarn build # tsc (type declarations) + backstage-cli repo build --allThe build runs tsc first (generates dist-types/), then backstage-cli repo build for each workspace package.
yarn type # tsc --noEmit (validates all packages via root tsconfig.json)The root tsconfig.json includes packages/*/src and packages/*/dev. Type errors in any package fail this check.
yarn test # Runs lerna run test:ci across all packages (no watch)Per-package (from the package directory):
yarn test # backstage-cli package test (watch mode)
yarn test:ci # backstage-cli package test --watch falseTests use Jest configured by @backstage/cli. There are 5 test suites total:
packages/gitlab/src/api/GitlabCIClient.test.tspackages/gitlab/src/plugin.test.tspackages/gitlab-backend/src/plugin.test.tspackages/gitlab-backend/src/processor/processor.test.tspackages/gitlab-backend/src/service/router.test.ts
yarn style:lint # biome check packages
yarn style:lint-fix # biome check --write packages
yarn style:format # biome format --write packagesLefthook runs on every commit. The lefthook.yml runs:
yarn biome check --writeon all staged filesyarn markdown-toc -ion.mdfilesyarn type(full type check via typescript) on.tsfiles
Important: The pre-commit hook runs the full type check — a TypeScript error anywhere in the repo will block commits.
Publishing is automated via semantic release. Do not publish manually.
yarn version:release # Conventional version bump for main branch
yarn version:prerelease # Prerelease bump (other branches)
yarn publish:ci # Lerna publish from-package (CI only)The frontend plugin supports two Backstage frontend systems simultaneously:
| System | Entry point | API style |
|---|---|---|
| Classic (old) | src/index.ts |
createPlugin, createComponentExtension |
| New (alpha/experimental) | src/alpha/index.ts |
createFrontendPlugin, blueprints |
Both must remain working. Do not remove the classic system.
In package.json exports:
{
".": "./src/index.ts",
"./alpha": "./src/alpha/index.ts"
}New frontend system pattern (src/alpha/): Components from the classic system are wrapped using compatWrapper from @backstage/core-compat-api:
import { compatWrapper } from "@backstage/core-compat-api";
export const gitlabCard = EntityCardBlueprint.make({
name: "my-card",
params: {
loader: async () =>
import("../components/widgets/MyCard").then((m) =>
compatWrapper(<m.MyCard />),
),
},
});Never remove compatWrapper calls — they bridge the theming and API contexts between the two systems.
GitlabCIClient (src/api/GitlabCIClient.ts) implements GitlabCIApi:
- Communicates with the backend proxy (via
discoveryApi) - Supports multi-GitLab instance (reads
gitlab.com/instanceannotation) - Built-in localStorage caching (default TTL: 300s, configurable)
- Two auth modes: token-based (default) or OAuth/OIDC (
gitlab.useOAuth: true) - REST API calls go through
/api/gitlab/rest/{host}/... - GraphQL calls go through
/api/gitlab/graphql/{host}
Two exports from packages/gitlab-backend:
gitlabPlugin — HTTP router plugin:
- Mounts at
/api/gitlab - Proxies
/rest/{host}/*→ GitLab REST API - Proxies
/graphql/{host}→ GitLab GraphQL API - Strips
Authorizationheaders; forwardsgitlab-authorizationasAuthorization - Blocks GraphQL mutations (read-only proxy)
- SSL verification configurable via
gitlab.proxySecure
catalogPluginGitlabFillerProcessorModule — Catalog processor module:
- Auto-fills
gitlab.com/project-slugannotation from entity location URL - Only processes entity kinds in
gitlab.allowedKinds(default:['Component']) - Registered as a Backstage backend module for the
catalogplugin
Defined in packages/gitlab-backend/src/annotations.ts:
| Annotation | Description |
|---|---|
gitlab.com/project-slug |
GitLab project path (e.g. group/subgroup/project) |
gitlab.com/project-id |
GitLab numeric project ID |
gitlab.com/instance |
GitLab instance hostname (for multi-instance) |
Frontend hooks for reading these are in src/components/gitlabAppData.tsx.
gitlab:
defaultCodeOwnersPath: "CODEOWNERS" # default
defaultReadmePath: "README.md" # default
useOAuth: false # enable OAuth/OIDC auth
cache:
enabled: false
ttl: 300 # secondsgitlab:
allowedKinds: # catalog kinds to auto-annotate
- Component
proxySecure: true # verify SSL certificates
useOAuth: false # OAuth mode| Tool | Version | Notes |
|---|---|---|
| Node.js | >=24.0.0 (pinned 24.14.1 via Volta) | See engines and volta in root package.json |
| Yarn | 4.3.1 (Berry) | node-modules linker — not PnP |
| TypeScript | ~6.0.2 | Strict mode; noUnusedLocals, noUnusedParameters enforced |
| React | ^18 (peer dep) | |
| Material UI | v4 (@material-ui/core ^4.12.2) |
Backstage itself uses MUI v4 — see important note below |
| Backstage | tracks backstage.json version |
|
| Jest | via @backstage/cli |
Config injected by CLI; jest must be explicit devDep |
| MSW | see per-package | Backend tests use MSW; check the specific version per package |
| Express | ^4.x | Backend router |
The plugin uses @material-ui/* (MUI v4), which matches what Backstage's own components (@backstage/core-components) use. As of Backstage 1.48, Backstage has not yet migrated to @mui/* (MUI v5). Migrating this plugin's components to @mui/* while Backstage is still on v4 would break theme integration — the Backstage theme context would not reach the plugin's components.
Do not replace @material-ui/* imports with @mui/* until Backstage itself ships its MUI v5 migration.
The only MUI v5 package in use is @mui/x-charts (charts component) — this is intentional since no compatible MUI v4 chart library exists. MUI v4 and v5 can technically coexist in separate React subtrees.
- Strict mode is on. All flags (
strictNullChecks,noUnusedLocals,noUnusedParameters, etc.) are enforced. - Prefer
// @ts-expect-errorover// @ts-ignorewhen suppressing type errors. If// @ts-ignoreis ever used, it must be accompanied by a comment explaining why (lint rule:@typescript-eslint/ban-ts-commentiswarn). Any suppression requires an explanatory comment. @typescript-eslint/no-non-null-assertionis off — non-null assertions (!) are allowed but discourage them in new code.
Config (biome.json):
- 4-space tab width
- Single quotes
- Trailing commas (
es5) - End of line:
auto(LF)
Biome runs automatically on every commit via the pre-commit hook (lefthook.yml). Use yarn style:lint-fix to fix formatting and linting issues manually, or yarn style:format to format files only.
Conventional Commits are enforced by commitlint. Format:
<type>(<scope>): <subject>
Allowed types: feat, fix, chore, docs, refactor, test, ci, style, perf, build, revert
Breaking changes: add ! after type or BREAKING CHANGE: footer.
Examples:
feat(frontend): add coverage card to alpha system
fix(backend): resolve SSL verification bypass
chore(deps): bump @backstage/core-components to 0.18.0
Use the PR template (.github/pull_request_template.md). Link related issues. CI runs type check, format check, build, and tests — all must pass.
Use @testing-library/react and @backstage/test-utils. Tests use renderInTestApp or renderWithEffects from Backstage test utils.
import { renderInTestApp } from "@backstage/test-utils";Use @backstage/backend-test-utils and MSW for HTTP mocking. Check which MSW version a file uses before editing:
- MSW v1 syntax:
rest.get(...),res(ctx.json(...)) - MSW v2 syntax:
http.get(...),HttpResponse.json(...)
Do not mix MSW v1 and v2 syntax in the same file.
When testing HTTP routes, MSW v2 does not support the TRACE method — remove it from any method arrays if present.
# From a package directory:
yarn test --testPathPattern="router.test"
# Or from root:
yarn workspace @immobiliarelabs/backstage-plugin-gitlab-backend test --testPathPattern="router.test"| Workflow | Trigger | Actions |
|---|---|---|
merge.yml |
Pull request | Runs test.yml |
test.yml |
workflow_call |
Conventional commits, type check, format, build, tests |
release.yml |
Manual | Build → version bump → publish to npm → GitHub release |
backmerge.yml |
Push to main |
Auto-merges main → next |
merge-dependabot.yml |
Dependabot PR | Auto-merges patch bumps |
stale.yml |
Schedule | Closes stale issues (30d) / PRs (45d) |
CI uses Node 24.x (GitHub Actions actions/setup-node). Update test.yml when the Node version changes.
- Update the version in
backstage.json - Manually bump all
@backstage/*dependencies in eachpackages/*/package.json - Do not use
backstage-cli versions:bump— this tool targets full Backstage apps, not standalone plugins - Run
yarn install, then verify withyarn type,yarn build,yarn test
- Runtime deps: go in
dependenciesof the specific package - Dev/test deps: go in
devDependencies - Peer deps: React and react-router go in
peerDependencies+devDependencies(for local dev) @backstage/climust always appear indevDependenciesof every publishable package
| Package | Reason |
|---|---|
@material-ui/lab |
Pinned to exact 4.0.0-alpha.61 (no caret) — do not change |
@mui/x-charts |
Only MUI v5 package; coexists with v4 |
react-router / react-router-dom |
Must match Backstage's peer dep version range |
This repository may be worked on inside a git worktree. The biome.json configuration is isolated and does not require special root settings like ESLint's "root": true; Biome's configuration scope is limited to the workspace root, so no additional configuration is needed when working in worktrees.
-
catalogProcessingExtensionPointimport path: In older Backstage versions this was at@backstage/plugin-catalog-node/alpha. After Backstage graduated it to stable, it moved to@backstage/plugin-catalog-node(no/alpha). Check the installed version when this import fails. -
jestmust be an explicit devDependency:@backstage/cli≥0.36.0 requiresjestto be listed explicitly in each package'sdevDependencies. Do not rely on transitive resolution. -
SignInPageBlueprint: In Backstage ≥1.48, this moved from@backstage/frontend-plugin-apito@backstage/plugin-app-react. Import it from there indev-sandbox/src/App.tsx. -
makeStylestype compatibility:theme.typography.fontWeightMediumreturnsFontWeight | undefined, which MUI v4's JSS type system does not accept directly asfontWeight. Use a numeric literal (e.g.500) or cast withas number. -
@backstage/cliand@backstage/cli-defaults: The root package needs@backstage/cli-defaultsas a devDependency to suppress a CLI warning about missing defaults configuration. -
Dev sandbox uses new frontend system:
packages/dev-sandboxusescreateAppfrom@backstage/frontend-defaults(new system), notcreateAppfrom@backstage/core-app-api(classic). Do not confuse the two. -
Volta pins per-package: Each package has its own
voltafield. The root volta pin takes precedence for repo-wide tooling. -
lint-stagedcallsnpm run: Thelint-staged.config.cjsusesnpm run style:lint(notyarn). This is intentional and works correctly even with Yarn 4.