Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Coverage directory generated when running tests with coverage
coverage
Expand Down Expand Up @@ -49,3 +48,6 @@ site

# E2E test reports
e2e-test-report/

# Cache
.cache/
95 changes: 95 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

This is the Open edX Backstage instance — a monorepo-based [Backstage](https://backstage.io) deployment serving as the internal developer portal for the Open edX project. It is deployed to Heroku at https://backstage.openedx.org.

## Development Commands

### Setup (one-time)
```bash
corepack enable
yarn install
```

Requires Node 20 or 22 (see `.nvmrc`), and the following environment variables for GitHub auth:
- `AUTH_GITHUB_CLIENT_APP_ID`
- `AUTH_GITHUB_CLIENT_ID`
- `AUTH_GITHUB_CLIENT_SECRET`
- `AUTH_GITHUB_CLIENT_PRIVATE_KEY`

### Daily Development
```bash
yarn dev # Start both frontend (port 3000) and backend (port 7007) together
yarn start # Frontend only
yarn start-backend # Backend only
```

### Building
```bash
yarn tsc # Type-check
yarn build:all # Build all packages
yarn build:backend # Build backend only (for Docker)
yarn build-image # Build Docker image
```

### Testing & Linting
```bash
yarn test # Run tests
yarn test:all # Run tests with coverage
yarn test:e2e # Run Playwright E2E tests
yarn lint:all # Lint all packages
yarn prettier:check # Check formatting
```

## Architecture

### Monorepo Structure
- **`packages/app/`** — Frontend React application (Backstage frontend)
- **`packages/backend/`** — Node.js backend service (Backstage backend)
- **`plugins/`** — Custom Backstage plugins (currently empty, ready for development)
- **`catalog-extra/`** — Additional catalog YAML files (e.g., LTI plugin)
- **`examples/`** — Sample entities and scaffolder templates

### Frontend (`packages/app`)
Standard Backstage React app with these plugins enabled:
- Catalog, Scaffolder, TechDocs, API Docs, Tech Radar, Search, Catalog Graph
- GitHub Actions (community plugin)
- GitHub authentication with openedx org requirement

Key files: `src/App.tsx` (routes and plugin bindings), `src/components/catalog/EntityPage.tsx` (entity display), `src/apis.ts`.

### Backend (`packages/backend`)
Single entry point at `src/index.ts` that registers all backend plugins:
- **Auth**: GitHub provider + guest (development only)
- **Catalog**: GitHub org discovery for the `openedx` GitHub organization (main/master branches)
- **Search**: PostgreSQL engine in production, collating catalog and TechDocs
- **TechDocs**: Local build/generate/publish in development
- **Scaffolder**: Software templates
- **Events**: GitHub event routing

### Configuration
- `app-config.yaml` — Development config (SQLite in-memory, localhost URLs)
- `app-config.production.yaml` — Production config (PostgreSQL via env vars, https://backstage.openedx.org)

### Database
- Development: SQLite3 (better-sqlite3, in-memory)
- Production: PostgreSQL (connection via `PGSSLMODE`, `PG*` environment variables)

### Deployment
Docker image built from `packages/backend/Dockerfile` and deployed to Heroku. The image bundles the compiled frontend and serves it from the backend.

## Adding Plugins

New plugins go in `plugins/` directory. Scaffold with:
```bash
yarn new
```

Then register the plugin in `packages/app/src/App.tsx` (frontend) and/or `packages/backend/src/index.ts` (backend).

## Catalog Discovery

The backend automatically discovers and ingests entities from the `openedx` GitHub organization. Additional catalog files can be added to `catalog-extra/` and referenced in `app-config.yaml` under `catalog.locations`.
9 changes: 9 additions & 0 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ app:
title: Open edX Backstage
baseUrl: http://localhost:3000

# Enable all packages by default, this will discover packages from packages/app/package.json
packages: all

extensions:
# Configure the catalog index page to be the root page, this is normally mounted on /catalog
- page:catalog:
config:
path: /

organization:
name: Open edX Project

Expand Down
2 changes: 1 addition & 1 deletion backstage.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "1.36.1"
"version": "1.49.2"
}
2 changes: 2 additions & 0 deletions examples/entities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example-website
annotations:
backstage.io/techdocs-ref: dir:./example-docs
spec:
type: website
lifecycle: experimental
Expand Down
5 changes: 5 additions & 0 deletions examples/example-docs/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Example TechDocs page

This is an example TechDocs page!

The source of this documentation for the `example-website` component is found in the `examples/example-docs` directory, which is referenced from `examples/entities.yaml`. Typically the `docs` directory would live alongside the `catalog-info.yaml` file of a component instead, but in this case it is separated out to avoid cluttering the examples directory.
4 changes: 4 additions & 0 deletions examples/example-docs/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
site_name: example-website
docs_dir: docs
plugins:
- techdocs-core
16 changes: 15 additions & 1 deletion examples/template/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ spec:
name: ${{ parameters.name }}

# This step publishes the contents of the working directory to GitHub.
# If you or your organization prefer another default branch name over 'main'
# you can change that here.
- id: publish
name: Publish
action: publish:github
input:
allowedHosts: ['github.com']
description: This is ${{ parameters.name }}
repoUrl: ${{ parameters.repoUrl }}
defaultBranch: 'main'

# The final step is to register our new component in the catalog.
- id: register
Expand All @@ -64,6 +66,18 @@ spec:
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'

# Let's notify the user that the template has completed using the Notification action
- id: notify
name: Notify
action: notification:send
input:
recipients: entity
entityRefs:
- user:default/guest
title: 'Template executed'
info: 'Your template has been executed'
severity: 'normal'

# Outputs are displayed to the user after a successful execution of the template.
output:
links:
Expand Down
34 changes: 22 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
"version": "1.0.0",
"private": true,
"engines": {
"node": "20 || 22"
"node": "22 || 24"
},
"scripts": {
"dev": "yarn workspaces foreach -A --include backend --include app --parallel --jobs unlimited -v -i run start",
"start": "yarn workspace app start",
"start-backend": "yarn workspace backend start",
"start": "backstage-cli repo start",
"build:backend": "yarn workspace backend build",
"build:all": "backstage-cli repo build --all",
"build-image": "yarn workspace backend build-image",
Expand All @@ -25,19 +23,31 @@
"create-plugin": "backstage-cli create-plugin",
"new": "backstage-cli new --scope internal"
},
"workspaces": {
"packages": [
"packages/*",
"plugins/*"
]
"backstage": {
"cli": {
"new": {
"globals": {
"license": "UNLICENSED"
}
}
}
},
"workspaces": [
"packages/*",
"plugins/*"
],
"devDependencies": {
"@backstage/cli": "^0.30.0",
"@backstage/e2e-test-utils": "^0.1.1",
"@backstage/cli": "^0.36.0",
"@backstage/cli-defaults": "^0.1.0",
"@backstage/e2e-test-utils": "^0.1.2",
"@jest/environment-jsdom-abstract": "^30.0.0",
"@playwright/test": "^1.32.3",
"@types/jest": "^30.0.0",
"jest": "^30.2.0",
"jsdom": "^27.1.0",
"node-gyp": "^10.0.0",
"prettier": "^2.3.2",
"typescript": "~5.4.0"
"typescript": "~5.8.0"
},
"resolutions": {
"@types/react": "^18",
Expand Down
4 changes: 3 additions & 1 deletion packages/app/e2e-tests/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ test('App should render the welcome page', async ({ page }) => {
await expect(enterButton).toBeVisible();
await enterButton.click();

await expect(page.getByText('My Company Catalog')).toBeVisible();
const nav = page.getByRole('navigation');
await expect(nav.getByRole('link', { name: 'Catalog' })).toBeVisible();
await expect(page.getByRole('link', { name: 'APIs' })).toBeVisible();
});

50 changes: 24 additions & 26 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,36 @@
"dependencies": {
"@backstage-community/plugin-github-actions": "^0.6.19",
"@backstage-community/plugin-tech-radar": "^0.7.6",
"@backstage/app-defaults": "^1.5.17",
"@backstage/catalog-model": "^1.7.3",
"@backstage/cli": "^0.30.0",
"@backstage/core-app-api": "^1.15.5",
"@backstage/core-components": "^0.16.4",
"@backstage/core-plugin-api": "^1.10.4",
"@backstage/integration-react": "^1.2.4",
"@backstage/plugin-api-docs": "^0.12.4",
"@backstage/plugin-catalog": "^1.27.0",
"@backstage/plugin-catalog-common": "^1.1.3",
"@backstage/plugin-catalog-graph": "^0.4.16",
"@backstage/plugin-catalog-import": "^0.12.10",
"@backstage/plugin-catalog-react": "^1.15.2",
"@backstage/plugin-org": "^0.6.36",
"@backstage/plugin-permission-react": "^0.4.31",
"@backstage/plugin-scaffolder": "^1.28.0",
"@backstage/plugin-search": "^1.4.23",
"@backstage/plugin-search-react": "^1.8.6",
"@backstage/plugin-techdocs": "^1.12.3",
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.21",
"@backstage/plugin-techdocs-react": "^1.2.14",
"@backstage/plugin-user-settings": "^0.8.19",
"@backstage/theme": "^0.6.4",
"@backstage/cli": "^0.36.0",
"@backstage/core-components": "^0.18.8",
"@backstage/core-plugin-api": "^1.12.4",
"@backstage/frontend-defaults": "^0.5.0",
"@backstage/frontend-plugin-api": "^0.15.1",
"@backstage/integration-react": "^1.2.16",
"@backstage/plugin-api-docs": "^0.13.5",
"@backstage/plugin-app-react": "^0.2.1",
"@backstage/plugin-app-visualizer": "^0.2.1",
"@backstage/plugin-catalog": "^2.0.1",
"@backstage/plugin-catalog-graph": "^0.6.0",
"@backstage/plugin-catalog-import": "^0.13.11",
"@backstage/plugin-notifications": "^0.5.15",
"@backstage/plugin-org": "^0.7.0",
"@backstage/plugin-scaffolder": "^1.36.1",
"@backstage/plugin-search": "^1.7.0",
"@backstage/plugin-signals": "^0.0.29",
"@backstage/plugin-techdocs": "^1.17.2",
"@backstage/plugin-techdocs-module-addons-contrib": "^1.1.34",
"@backstage/plugin-user-settings": "^0.9.1",
"@backstage/ui": "^0.13.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"react": "18.1.0",
"react-dom": "18.1.0",
"react-router": "^6.3.0",
"react-router-dom": "^6.3.0"
"react-router": "^6.30.2",
"react-router-dom": "^6.30.2"
},
"devDependencies": {
"@backstage/test-utils": "^1.7.5",
"@backstage/frontend-test-utils": "^0.5.1",
"@playwright/test": "^1.32.3",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^6.0.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import App from './App';

Expand All @@ -20,7 +19,7 @@ describe('App', () => {
] as any,
};

const rendered = render(<App />);
const rendered = render(App.createRoot());

await waitFor(() => {
expect(rendered.baseElement).toBeInTheDocument();
Expand Down
Loading