Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b112c9a
chore: add Backstage yarn plugin to prep for upgrade
buenos-nachos Oct 22, 2025
b222d3c
docs: add comment about new gitignore settings
buenos-nachos Oct 22, 2025
076c1f5
fix: run 'yarn install' and save state to branch
buenos-nachos Oct 22, 2025
7daf373
chore: try updating more stuff
buenos-nachos Oct 23, 2025
dcc9db8
chore: clean up backend package to resemble NBS from fresh install
buenos-nachos Oct 24, 2025
5734e85
docs: add missing comment to app-config
buenos-nachos Oct 24, 2025
036a2a3
chore: clean up backend package further
buenos-nachos Oct 24, 2025
8585e32
fix: remove typo in app-config
buenos-nachos Oct 24, 2025
9143e32
refactor: switch to prop for sign in page
buenos-nachos Oct 24, 2025
9ca210d
chore: get initial GH auth flow working
buenos-nachos Oct 24, 2025
f49d43f
chore: remove deprecated backend packages from package.json
buenos-nachos Oct 24, 2025
1d55634
chore: more config clean up
buenos-nachos Oct 24, 2025
0eca678
fix: update .gitignore
buenos-nachos Oct 24, 2025
7cdd435
chore: add package.json comment
buenos-nachos Oct 24, 2025
9820ad2
chore: add more comments
buenos-nachos Oct 24, 2025
3af5c2d
chore: add note about coder plugin
buenos-nachos Oct 24, 2025
87bc826
docs: add more code snippets
buenos-nachos Oct 24, 2025
a2d7d39
refactor: migrate backend coder plugin to NBS
buenos-nachos Oct 24, 2025
73650f9
chore: add in example data used by newer scaffolded apps
buenos-nachos Oct 24, 2025
78bf0d2
fix: improve logging granularity for coder backend plugin
buenos-nachos Oct 24, 2025
c2f66bf
fix: revert logging change
buenos-nachos Oct 24, 2025
b960770
wip: commit progress on devcontainers-backend conversion
buenos-nachos Oct 25, 2025
fd60848
wip: consolidate temp package back into main one
buenos-nachos Oct 25, 2025
141fdc6
chore: add devcontainers-backend to the sample app
buenos-nachos Oct 25, 2025
d035fa0
fix: resolve version mismatch in package.json
buenos-nachos Oct 25, 2025
e1405db
chore: update all files at root of directory
buenos-nachos Oct 25, 2025
640860a
fix: revert accidental change to devcontainers README
buenos-nachos Oct 25, 2025
ae42291
chore: update basic frontend files
buenos-nachos Oct 25, 2025
f6c7817
chore: update rendering output for app directory
buenos-nachos Oct 25, 2025
afbc8e9
chore: update Root component
buenos-nachos Oct 25, 2025
b24f648
chore: update EntityPage
buenos-nachos Oct 25, 2025
e19aa8f
fix: make sure kubernetes config is null
buenos-nachos Oct 25, 2025
0bf1ff7
fix: update App page to use newer APIs
buenos-nachos Oct 25, 2025
d4d657c
fix: remove fallback values for .env
buenos-nachos Oct 25, 2025
5fbed3a
fix: add back missing themes for legacy MUI code
buenos-nachos Oct 25, 2025
59152e0
chore: update react imports for devcontainers-frontend
buenos-nachos Oct 25, 2025
02eeaad
fix: add missing dependency for devcontainers backend plugin
buenos-nachos Oct 25, 2025
63f1a2c
chore: remove all React imports from project
buenos-nachos Oct 25, 2025
b085eb2
fix: silence warning about package type mismatch
buenos-nachos Oct 25, 2025
9998deb
refactor: clean up kludge
buenos-nachos Oct 26, 2025
030974f
fix: format two yaml files
buenos-nachos Oct 27, 2025
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
18 changes: 11 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,27 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Coverage directory generated when running tests with coverage
coverage

# Dependencies
node_modules/

# Yarn 3 files
# Yarn files
.pnp.*
.yarn/*
!.yarn/patches
# This is skipped because we specify the Yarn version in the root package.json
.yarn/releases
# Important that we re-include the plugins directory because we need Backstage's
# official Yarn plugin to simplify managing dependencies for each Backstage
# release. The package.json files are updated to include the special
# "backstage:^" value for version numbers, which will break without the plugin
!.yarn/plugins
!.yarn/releases
!.yarn/patches
!.yarn/sdks
!.yarn/versions

# Node version directives
.nvmrc

# dotenv environment variables file
.env
.env.test
Expand All @@ -52,3 +53,6 @@ site

# E2E test reports
e2e-test-report/

# Cache
.cache/
2 changes: 1 addition & 1 deletion .node-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.19.0
20.19.0
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.19.0
3 changes: 1 addition & 2 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@ dist
dist-types
coverage
.vscode
.coder.yaml
app-config.local.yaml
.coder.yaml
9 changes: 9 additions & 0 deletions .yarn/plugins/@yarnpkg/plugin-backstage.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable */
//prettier-ignore
module.exports = {
name: "@yarnpkg/plugin-backstage",
factory: function (require) {
"use strict";var plugin=(()=>{var F=Object.create;var v=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var G=Object.getPrototypeOf,I=Object.prototype.hasOwnProperty;var p=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var J=(e,t)=>{for(var r in t)v(e,r,{get:t[r],enumerable:!0})},S=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of N(t))!I.call(e,o)&&o!==r&&v(e,o,{get:()=>t[o],enumerable:!(n=_(t,o))||n.enumerable});return e};var L=(e,t,r)=>(r=e!=null?F(G(e)):{},S(t||!e||!e.__esModule?v(r,"default",{value:e,enumerable:!0}):r,e)),z=e=>S(v({},"__esModule",{value:!0}),e);var ae={};J(ae,{default:()=>se});var P=p("@yarnpkg/core");var w=p("@yarnpkg/core");var W=L(p("assert")),j=p("semver"),y=p("@yarnpkg/fslib");var u=L(p("fs")),g=p("path");function A(e,t){let r=e;for(let n=0;n<1e3;n++){let o=(0,g.resolve)(r,"package.json");if(u.default.existsSync(o)&&t(o))return r;let i=(0,g.dirname)(r);if(i===r)return;r=i}throw new Error(`Iteration limit reached when searching for root package.json at ${e}`)}function K(e){let t=A(e,()=>!0);if(!t)throw new Error(`No package.json found while searching for package root of ${e}`);return t}function Y(e){if(!u.default.existsSync((0,g.resolve)(e,"src")))throw new Error("Tried to access monorepo package root dir outside of Backstage repository");return(0,g.resolve)(e,"../..")}function b(e){let t=K(e),r=u.default.realpathSync(process.cwd()).replace(/^[a-z]:/,s=>s.toLocaleUpperCase("en-US")),n="",o=()=>(n||(n=Y(t)),n),a="",i=()=>(a||(a=A(r,s=>{try{let m=u.default.readFileSync(s,"utf8");return!!JSON.parse(m).workspaces}catch(m){throw new Error(`Failed to parse package.json file while searching for root, ${m}`)}})??r),a);return{ownDir:t,get ownRoot(){return o()},targetDir:r,get targetRoot(){return i()},resolveOwn:(...s)=>(0,g.resolve)(t,...s),resolveOwnRoot:(...s)=>(0,g.resolve)(o(),...s),resolveTarget:(...s)=>(0,g.resolve)(r,...s),resolveTargetRoot:(...s)=>(0,g.resolve)(i(),...s)}}var x="backstage.json";var V=e=>{let t=!1,r;return()=>(t||(r=e(),t=!0),r)};var h=p("@yarnpkg/fslib");var C=()=>h.npath.toPortablePath(b(h.npath.fromPortablePath(h.ppath.cwd())).targetRoot);var k=V(()=>{let e=y.ppath.join(C(),x),t=null;try{t=(0,j.valid)(y.xfs.readJsonSync(e).version),(0,W.default)(t!==null)}catch{throw new Error("Valid version string not found in backstage.json")}return t});var d=p("@yarnpkg/core");var q="https://versions.backstage.io",Q="https://raw.githubusercontent.com/backstage/versions/main";function X(e,t){return new Promise((r,n)=>{let o=setTimeout(()=>{t.aborted||r()},e);t.addEventListener("abort",()=>{clearTimeout(o),n(new Error("Aborted"))})})}async function Z(e,t,r){let n=new AbortController,o=new AbortController,a=e(n.signal).then(s=>(o.abort(),s)),i=X(r,o.signal).then(()=>t(o.signal)).then(s=>(n.abort(),s));return Promise.any([a,i]).catch(()=>a)}async function D(e){let t=encodeURIComponent(e.version),r=e.fetch??fetch,n=e.versionsBaseUrl??q,o=e.gitHubRawBaseUrl??Q,a=await Z(i=>r(`${n}/v1/releases/${t}/manifest.json`,{signal:i}),i=>r(`${o}/v1/releases/${t}/manifest.json`,{signal:i}),500);if(a.status===404)throw new Error(`No release found for ${e.version} version`);if(a.status!==200)throw new Error(`Unexpected response status ${a.status} when fetching release from ${a.url}.`);return a.json()}var c="backstage:";var f=async(e,t)=>{let r=d.structUtils.stringifyIdent(e),n=d.structUtils.parseRange(e.range);if(n.protocol!==c)throw new Error(`Unsupported version protocol in version range "${e.range}" for package ${r}`);if(n.selector!=="^")throw new Error(`Unexpected version selector "${n.selector}" for package ${r}`);let o=k(),i=(await D({version:o,fetch:async s=>{let m=await d.httpUtils.get(s,{configuration:t,jsonResponse:!0});return{status:200,url:s,json:()=>m}}})).packages.find(s=>s.name===r);if(!i)throw new Error(`Package ${r} not found in manifest for Backstage v${o}. This means the specified package is not included in this Backstage release. This may imply the package has been replaced with an alternative - please review the documentation for the package. If you need to continue using this package, it will be necessary to switch to manually managing its version.`);return i.version};var ee=e=>w.structUtils.parseRange(e).protocol===c,te=(e,t,r)=>e!=="dependencies"?e:r.manifest.ensureDependencyMeta(w.structUtils.makeDescriptor(t,"unknown")).optional?"optionalDependencies":e,B=async(e,t)=>{for(let r of["dependencies","devDependencies"]){let n=Array.from(e.manifest.getForScope(r).values()).filter(o=>o.range.startsWith(c));for(let o of n){let a=w.structUtils.stringifyIdent(o);if(w.structUtils.parseRange(o.range).selector!=="^")throw new Error(`Unexpected version range "${o.range}" for dependency on "${a}"`);let s=te(r,o,e);t[s][a]=`^${await f(o,e.project.configuration)}`}}if(["dependencies","devDependencies","optionalDependencies"].some(r=>Object.values(t[r]??{}).some(ee)))throw new Error(`Failed to replace all "backstage:" ranges in manifest for ${t.name}`)};var O=p("@yarnpkg/core");var $=async(e,t)=>{let r=O.structUtils.parseRange(e.range);if(r.protocol!==c)return e;if(r.selector!=="^")throw new Error(`Invalid backstage: version range found: ${e.range}`);return O.structUtils.bindDescriptor(e,{backstage:k(),npm:await f(e,t.configuration)})};var H=p("@yarnpkg/core");var U=async(e,t,r,n)=>{let o=H.structUtils.parseRange(r.range);if(r.scope==="backstage"&&o.protocol!==c){let a=r.range;try{r.range=`${c}^`,await f(r,e.project.configuration),console.info(`Setting ${r.scope}/${r.name} to ${c}^`)}catch{r.range=a}}};var M=p("@yarnpkg/core");var E=async(e,t,r,n)=>{let o=M.structUtils.parseRange(n.range);n.scope==="backstage"&&o.protocol!==c&&console.warn(`${n.name} should be set to "${c}^" instead of "${n.range}". Make sure this change is intentional and not a mistake.`)};var l=p("@yarnpkg/core"),T=p("@yarnpkg/plugin-npm");var R=class e{static protocol=c;supportsDescriptor=t=>t.range.startsWith(e.protocol);async getCandidates(t,r,n){let o=l.structUtils.parseRange(t.range).params?.npm;if(!o||Array.isArray(o))throw new Error(`Missing npm parameter on backstage: range "${t.range}"`);return new T.NpmSemverResolver().getCandidates(l.structUtils.makeDescriptor(t,`npm:^${o}`),r,n)}getResolutionDependencies(t){let r=l.structUtils.parseRange(t.range).params?.npm;if(!r)throw new Error(`Missing npm parameter on backstage: range "${t.range}".`);return{[l.structUtils.stringifyIdent(t)]:l.structUtils.makeDescriptor(t,`npm:^${r}`)}}async getSatisfying(t,r,n,o){let a=t,i=l.structUtils.parseRange(a.range);if(i.protocol===c){let s=i.params?.npm;a=l.structUtils.makeDescriptor(t,`npm:^${s}`)}return new T.NpmSemverResolver().getSatisfying(a,r,n,o)}bindDescriptor=t=>t;supportsLocator=()=>!1;shouldPersistResolution=()=>{throw new Error("Unreachable: BackstageNpmResolver should never persist resolution as it uses npm: protocol")};resolve=async()=>{throw new Error("Unreachable: BackstageNpmResolver should never resolve as it uses npm: protocol")}};var re="\x1B[31;1m",oe="\x1B[0m";P.semverUtils.satisfiesWithPrereleases(P.YarnVersion,"^4.1.1")||(console.error(),console.error(`${re}Unsupported yarn version${oe}: The Backstage yarn plugin only works with yarn ^4.1.1. Please upgrade yarn, or remove this plugin with "yarn plugin remove @yarnpkg/plugin-backstage".`),console.error());var ne={hooks:{afterWorkspaceDependencyAddition:U,afterWorkspaceDependencyReplacement:E,reduceDependency:$,beforeWorkspacePacking:B},resolvers:[R]},se=ne;return z(ae);})();
return plugin;
}
};
8 changes: 8 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
nodeLinker: node-modules

plugins:
- checksum: 8af7b3f2d7d19cacc7a3712f871efcb6208ba283a1f532260b0cba80c2cb66ed772b207b5ba41b8c5d64dd8d5e0c0e15bbb445bd14afac491712965211ba027c
path: .yarn/plugins/@yarnpkg/plugin-backstage.cjs
spec: 'https://versions.backstage.io/v1/releases/1.42.5/yarn-plugin'

yarnPath: .yarn/releases/yarn-4.9.4.cjs
28 changes: 24 additions & 4 deletions app-config.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ app:
auth:
environment: development
providers:
guest: {}
github:
development:
clientId: ${GITHUB_CLIENT_ID}
Expand All @@ -19,13 +20,16 @@ backend:
baseUrl: ${BASE_URL}
# The listener can also be expressed as a single <host>:<port> string. In this case we bind to
# all interfaces, the most permissive setting. The right value depends on your specific deployment.
listen: ':8080'
listen: ':7007'

# config options: https://node-postgres.com/api/client
# config options: https://node-postgres.com/apis/client
database:
client: pg
connection:
${DATABASE_URL}
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
# https://node-postgres.com/features/ssl
# you can set the sslmode configuration option via the `PGSSLMODE` environment variable
# see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require)
Expand All @@ -37,4 +41,20 @@ catalog:
# Overrides the default list locations from app-config.yaml as these contain example data.
# See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details
# on how to get entities into the catalog.
locations: []
locations:
# Local example data, replace this with your production config, these are intended for demo use only.
# File locations are relative to the backend process, typically in a deployed context, such as in a Docker container, this will be the root
- type: file
target: ./examples/entities.yaml

# Local example template
- type: file
target: ./examples/template/template.yaml
rules:
- allow: [Template]

# Local example organizational data
- type: file
target: ./examples/org.yaml
rules:
- allow: [User, Group]
38 changes: 32 additions & 6 deletions app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ coder:
deployment:
accessUrl: https://dev.coder.com
oauth:
clientId: ${CODER_OAUTH_CLIENT_ID:-backstage}
clientSecret: ${CODER_OAUTH_CLIENT_SECRET:-change-me}
clientId: ${CODER_OAUTH_CLIENT_ID}
clientSecret: ${CODER_OAUTH_CLIENT_SECRET}

backend:
# Used for enabling authentication, secret is shared by all backend plugins
Expand Down Expand Up @@ -76,11 +76,20 @@ techdocs:

auth:
# see https://backstage.io/docs/auth/ to learn about auth providers
providers: {}
providers:
github:
development:
# It's expected that these will be shadowed via an
# app-config.local.yaml file
clientId: ${AUTH_GITHUB_CLIENT_ID}
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
signIn:
resolvers:
- resolver: usernameMatchingUserEntityName

# see https://backstage.io/docs/features/software-templates/configuration for software template options
# scaffolder:

scaffolder:
# see https://backstage.io/docs/features/software-templates/configuration for software template options
{}
catalog:
import:
entityFilename: catalog-info.yaml
Expand Down Expand Up @@ -113,3 +122,20 @@ catalog:
# target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
# rules:
# - allow: [User, Group]
providers:
github:
coderDemoAppConfiguration:
organization: coder
schedule:
frequency:
minutes: 60
timeout:
minutes: 15

# see https://backstage.io/docs/features/kubernetes/configuration for kubernetes configuration options
# kubernetes:

# see https://backstage.io/docs/permissions/getting-started for more on the permission framework
permission:
# setting this to `false` will disable permissions
enabled: true
2 changes: 1 addition & 1 deletion backstage.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "1.22.1"
"version": "1.42.5"
}
2 changes: 1 addition & 1 deletion catalog-info.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: backstage-shell
name: backstage
description: An example of a Backstage application.
# Example for optional annotations
# annotations:
Expand Down
41 changes: 41 additions & 0 deletions examples/entities.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: examples
spec:
owner: guests
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: example-website
spec:
type: website
lifecycle: experimental
owner: guests
system: examples
providesApis: [example-grpc-api]
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: example-grpc-api
spec:
type: grpc
lifecycle: experimental
owner: guests
system: examples
definition: |
syntax = "proto3";

service Exampler {
rpc Example (ExampleMessage) returns (ExampleMessage) {};
}

message ExampleMessage {
string example = 1;
};
31 changes: 31 additions & 0 deletions examples/org.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This YAML follows the format from Backstage's guide on setting up GitHub for
# authentication
# https://backstage.io/docs/getting-started/config/authentication/#adding-a-user
#
# Make sure that the `name` field matches your actual GitHub username, and that
# the `memberOf` field matches actual organizations that have been added to the
# app-config.yaml file.
---
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: Parkreiner
spec:
memberOf: [coder]
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: guest
spec:
memberOf: [guests]
---
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: guests
spec:
type: team
children: []
8 changes: 8 additions & 0 deletions examples/template/content/catalog-info.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: ${{ values.name | dump }}
spec:
type: service
owner: user:guest
lifecycle: experimental
88 changes: 88 additions & 0 deletions examples/template/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
apiVersion: scaffolder.backstage.io/v1beta3
# https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
kind: Template
metadata:
name: example-nodejs-template
title: Example Node.js Template
description: An example template for the scaffolder that creates a simple Node.js service
spec:
owner: user:guest
type: service

# These parameters are used to generate the input form in the frontend, and are
# used to gather input data for the execution of the template.
parameters:
- title: Fill in some steps
required:
- name
properties:
name:
title: Name
type: string
description: Unique name of the component
ui:autofocus: true
ui:options:
rows: 5
- title: Choose a location
required:
- repoUrl
properties:
repoUrl:
title: Repository Location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- github.com

# These steps are executed in the scaffolder backend, using data that we gathered
# via the parameters above.
steps:
# Each step executes an action, in this case one templates files into the working directory.
- id: fetch-base
name: Fetch Base
action: fetch:template
input:
url: ./content
values:
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:
description: This is ${{ parameters.name }}
repoUrl: ${{ parameters.repoUrl }}
defaultBranch: 'main'

# The final step is to register our new component in the catalog.
- id: register
name: Register
action: catalog:register
input:
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:
- title: Repository
url: ${{ steps['publish'].output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}
6 changes: 0 additions & 6 deletions lerna.json

This file was deleted.

Loading
Loading