@backstage/cli 0.28.0-next.1 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/config/jest.js +9 -1
  3. package/config/jestCacheResultProcessor.cjs +23 -0
  4. package/config/jestSwcTransform.js +4 -1
  5. package/dist/commands/build/buildBackend.cjs.js +65 -0
  6. package/dist/commands/build/buildFrontend.cjs.js +57 -0
  7. package/dist/commands/build/command.cjs.js +75 -0
  8. package/dist/commands/build/index.cjs.js +8 -0
  9. package/dist/commands/buildWorkspace.cjs.js +24 -0
  10. package/dist/commands/clean/clean.cjs.js +19 -0
  11. package/dist/{cjs/docs-BGyA6jwW.cjs.js → commands/config/docs.cjs.js} +4 -12
  12. package/dist/{cjs/print-Dd6aChXU.cjs.js → commands/config/print.cjs.js} +4 -12
  13. package/dist/{cjs/schema-D93FRhBL.cjs.js → commands/config/schema.cjs.js} +4 -12
  14. package/dist/commands/config/validate.cjs.js +19 -0
  15. package/dist/{cjs/index-j193pV_Y.cjs.js → commands/create-github-app/GithubCreateAppServer.cjs.js} +2 -110
  16. package/dist/commands/create-github-app/index.cjs.js +117 -0
  17. package/dist/commands/index.cjs.js +231 -0
  18. package/dist/{cjs/info-DuAv1Tsx.cjs.js → commands/info.cjs.js} +13 -17
  19. package/dist/{cjs/lint-BwiDJkjE.cjs.js → commands/lint.cjs.js} +10 -10
  20. package/dist/commands/migrate/packageExports.cjs.js +17 -0
  21. package/dist/{cjs/packageLintConfigs-DeUGBP17.cjs.js → commands/migrate/packageLintConfigs.cjs.js} +2 -10
  22. package/dist/{cjs/packageRole-Iuv9NRii.cjs.js → commands/migrate/packageRole.cjs.js} +5 -8
  23. package/dist/{cjs/packageScripts-DX6dilK6.cjs.js → commands/migrate/packageScripts.cjs.js} +1 -1
  24. package/dist/{cjs/reactRouterDeps-CR-hjviw.cjs.js → commands/migrate/reactRouterDeps.cjs.js} +1 -1
  25. package/dist/commands/new/new.cjs.js +101 -0
  26. package/dist/commands/pack.cjs.js +29 -0
  27. package/dist/commands/repo/build.cjs.js +113 -0
  28. package/dist/{cjs/clean-a6Q4k9Vm.cjs.js → commands/repo/clean.cjs.js} +5 -10
  29. package/dist/{cjs/fix-COitqgqm.cjs.js → commands/repo/fix.cjs.js} +8 -13
  30. package/dist/commands/repo/lint.cjs.js +205 -0
  31. package/dist/{cjs/list-deprecations-CtUaQgaP.cjs.js → commands/repo/list-deprecations.cjs.js} +7 -12
  32. package/dist/commands/repo/optionsParser.cjs.js +37 -0
  33. package/dist/commands/repo/test.cjs.js +277 -0
  34. package/dist/commands/start/command.cjs.js +48 -0
  35. package/dist/commands/start/index.cjs.js +8 -0
  36. package/dist/commands/start/startBackend.cjs.js +112 -0
  37. package/dist/commands/start/startFrontend.cjs.js +47 -0
  38. package/dist/{cjs/test-COxIko8N.cjs.js → commands/test.cjs.js} +6 -12
  39. package/dist/{cjs/bump-BHEh5ytx.cjs.js → commands/versions/bump.cjs.js} +21 -190
  40. package/dist/commands/versions/migrate.cjs.js +112 -0
  41. package/dist/index.cjs.js +29 -7
  42. package/dist/lib/builder/config.cjs.js +199 -0
  43. package/dist/lib/builder/packager.cjs.js +131 -0
  44. package/dist/lib/builder/plugins.cjs.js +71 -0
  45. package/dist/lib/builder/types.cjs.js +11 -0
  46. package/dist/lib/bundler/LinkedPackageResolvePlugin.cjs.js +47 -0
  47. package/dist/lib/bundler/backend.cjs.js +36 -0
  48. package/dist/{cjs/buildBackend-CkhZWCz1.cjs.js → lib/bundler/bundle.cjs.js} +31 -117
  49. package/dist/lib/bundler/config.cjs.js +491 -0
  50. package/dist/lib/bundler/hasReactDomClient.cjs.js +17 -0
  51. package/dist/lib/bundler/moduleFederation.cjs.js +28 -0
  52. package/dist/lib/bundler/optimization.cjs.js +67 -0
  53. package/dist/lib/bundler/packageDetection.cjs.js +117 -0
  54. package/dist/lib/bundler/paths.cjs.js +60 -0
  55. package/dist/lib/bundler/server.cjs.js +286 -0
  56. package/dist/lib/bundler/transforms.cjs.js +172 -0
  57. package/dist/lib/codeowners/codeowners.cjs.js +92 -0
  58. package/dist/{cjs/config-DBpmZirN.cjs.js → lib/config.cjs.js} +6 -6
  59. package/dist/{cjs/entryPoints-coip0t-x.cjs.js → lib/entryPoints.cjs.js} +1 -1
  60. package/dist/lib/errors.cjs.js +45 -0
  61. package/dist/lib/experimental/IpcServer.cjs.js +60 -0
  62. package/dist/lib/experimental/ServerDataStore.cjs.js +36 -0
  63. package/dist/lib/experimental/startBackendExperimental.cjs.js +128 -0
  64. package/dist/lib/new/FactoryRegistry.cjs.js +96 -0
  65. package/dist/lib/new/factories/backendModule.cjs.js +82 -0
  66. package/dist/lib/new/factories/backendPlugin.cjs.js +78 -0
  67. package/dist/lib/new/factories/common/prompts.cjs.js +57 -0
  68. package/dist/lib/new/factories/common/tasks.cjs.js +66 -0
  69. package/dist/lib/new/factories/common/util.cjs.js +16 -0
  70. package/dist/lib/new/factories/frontendPlugin.cjs.js +107 -0
  71. package/dist/lib/new/factories/index.cjs.js +24 -0
  72. package/dist/lib/new/factories/nodeLibraryPackage.cjs.js +57 -0
  73. package/dist/lib/new/factories/pluginCommon.cjs.js +58 -0
  74. package/dist/lib/new/factories/pluginNode.cjs.js +58 -0
  75. package/dist/lib/new/factories/pluginWeb.cjs.js +58 -0
  76. package/dist/lib/new/factories/scaffolderModule.cjs.js +90 -0
  77. package/dist/lib/new/factories/webLibraryPackage.cjs.js +57 -0
  78. package/dist/lib/new/types.cjs.js +8 -0
  79. package/dist/lib/packager/createDistWorkspace.cjs.js +219 -0
  80. package/dist/{cjs/productionPack-BxoMbBkH.cjs.js → lib/packager/productionPack.cjs.js} +8 -96
  81. package/dist/{cjs/parallel-BszNaKyc.cjs.js → lib/parallel.cjs.js} +2 -1
  82. package/dist/lib/paths.cjs.js +8 -0
  83. package/dist/{cjs/publishing-DQtsKTbc.cjs.js → lib/publishing.cjs.js} +1 -1
  84. package/dist/{cjs/role-BjiBExhi.cjs.js → lib/role.cjs.js} +3 -3
  85. package/dist/{cjs/run-CpZGNJQr.cjs.js → lib/run.cjs.js} +6 -5
  86. package/dist/{cjs/svgrTemplate-BTjBQ3by.cjs.js → lib/svgrTemplate.cjs.js} +1 -1
  87. package/dist/lib/tasks.cjs.js +188 -0
  88. package/dist/lib/typeDistProject.cjs.js +94 -0
  89. package/dist/lib/urls.cjs.js +13 -0
  90. package/dist/lib/version.cjs.js +94 -0
  91. package/dist/{cjs/yarn-6FNAgNBK.cjs.js → lib/versioning/Lockfile.cjs.js} +1 -31
  92. package/dist/lib/versioning/packages.cjs.js +75 -0
  93. package/dist/lib/yarn.cjs.js +34 -0
  94. package/dist/packages/backend-defaults/package.json.cjs.js +6 -0
  95. package/dist/packages/backend-plugin-api/package.json.cjs.js +6 -0
  96. package/dist/packages/backend-test-utils/package.json.cjs.js +6 -0
  97. package/dist/packages/catalog-client/package.json.cjs.js +6 -0
  98. package/dist/packages/cli/package.json.cjs.js +171 -0
  99. package/dist/packages/config/package.json.cjs.js +6 -0
  100. package/dist/packages/core-app-api/package.json.cjs.js +6 -0
  101. package/dist/packages/core-components/package.json.cjs.js +6 -0
  102. package/dist/packages/core-plugin-api/package.json.cjs.js +6 -0
  103. package/dist/packages/dev-utils/package.json.cjs.js +6 -0
  104. package/dist/packages/errors/package.json.cjs.js +6 -0
  105. package/dist/packages/test-utils/package.json.cjs.js +6 -0
  106. package/dist/packages/theme/package.json.cjs.js +6 -0
  107. package/dist/plugins/auth-backend/package.json.cjs.js +6 -0
  108. package/dist/plugins/auth-backend-module-guest-provider/package.json.cjs.js +6 -0
  109. package/dist/plugins/catalog-node/package.json.cjs.js +6 -0
  110. package/dist/plugins/scaffolder-node/package.json.cjs.js +6 -0
  111. package/dist/plugins/scaffolder-node-test-utils/package.json.cjs.js +6 -0
  112. package/package.json +55 -25
  113. package/templates/default-backend-plugin/README.md.hbs +22 -8
  114. package/templates/default-backend-plugin/dev/index.ts.hbs +60 -0
  115. package/templates/default-backend-plugin/package.json.hbs +5 -5
  116. package/templates/default-backend-plugin/src/index.ts.hbs +0 -1
  117. package/templates/default-backend-plugin/src/plugin.test.ts.hbs +85 -0
  118. package/templates/default-backend-plugin/src/plugin.ts.hbs +16 -14
  119. package/templates/default-backend-plugin/src/router.test.ts +67 -0
  120. package/templates/default-backend-plugin/src/router.ts +51 -0
  121. package/templates/default-backend-plugin/src/services/TodoListService/createTodoListService.ts +100 -0
  122. package/templates/default-backend-plugin/src/services/TodoListService/index.ts +1 -0
  123. package/templates/default-backend-plugin/src/services/TodoListService/types.ts +27 -0
  124. package/templates/scaffolder-module/package.json.hbs +3 -1
  125. package/templates/scaffolder-module/src/actions/example.test.ts +24 -0
  126. package/templates/scaffolder-module/src/actions/{example/example.ts → example.ts} +7 -3
  127. package/templates/scaffolder-module/src/index.ts.hbs +1 -1
  128. package/templates/scaffolder-module/src/{actions/example/module.ts → module.ts} +3 -3
  129. package/dist/cjs/build-CQdcGuBr.cjs.js +0 -194
  130. package/dist/cjs/buildWorkspace-CZPp9oRm.cjs.js +0 -53
  131. package/dist/cjs/clean-W6nxsHeK.cjs.js +0 -22
  132. package/dist/cjs/createDistWorkspace-DdHPGSMS.cjs.js +0 -576
  133. package/dist/cjs/index-BXv4Xa2e.cjs.js +0 -625
  134. package/dist/cjs/index-CGuAP7nv.cjs.js +0 -131
  135. package/dist/cjs/index-b1ouG3q6.cjs.js +0 -518
  136. package/dist/cjs/lint-Dsiocf9K.cjs.js +0 -91
  137. package/dist/cjs/moduleFederation-DmStnvEg.cjs.js +0 -910
  138. package/dist/cjs/new-CEnFhTT-.cjs.js +0 -1043
  139. package/dist/cjs/pack-XLRcGJqH.cjs.js +0 -34
  140. package/dist/cjs/packageExports-BJBwdvUH.cjs.js +0 -27
  141. package/dist/cjs/test-JcLI2pPM.cjs.js +0 -126
  142. package/dist/cjs/validate-CELljsEX.cjs.js +0 -28
  143. package/templates/default-backend-plugin/dev/index.ts +0 -9
  144. package/templates/default-backend-plugin/src/service/router.test.ts +0 -30
  145. package/templates/default-backend-plugin/src/service/router.ts +0 -28
  146. package/templates/scaffolder-module/src/actions/example/example.test.ts +0 -32
  147. package/templates/scaffolder-module/src/actions/example/index.ts +0 -7
  148. package/templates/scaffolder-module/src/actions/index.ts +0 -1
  149. /package/templates/default-backend-module/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  150. /package/templates/default-backend-plugin/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  151. /package/templates/default-common-plugin-package/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  152. /package/templates/default-node-plugin-package/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  153. /package/templates/default-plugin/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  154. /package/templates/default-react-plugin-package/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  155. /package/templates/node-library-package/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  156. /package/templates/scaffolder-module/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
  157. /package/templates/web-library-package/{.eslintrc.js → .eslintrc.js.hbs} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/cli",
3
- "version": "0.28.0-next.1",
3
+ "version": "0.28.0",
4
4
  "description": "CLI for developing Backstage plugins and apps",
5
5
  "backstage": {
6
6
  "role": "cli"
@@ -42,16 +42,16 @@
42
42
  "watch": "./src"
43
43
  },
44
44
  "dependencies": {
45
- "@backstage/catalog-model": "1.7.0",
46
- "@backstage/cli-common": "0.1.14",
47
- "@backstage/cli-node": "0.2.8",
48
- "@backstage/config": "1.2.0",
49
- "@backstage/config-loader": "1.9.1",
50
- "@backstage/errors": "1.2.4",
51
- "@backstage/eslint-plugin": "0.1.10-next.0",
52
- "@backstage/integration": "1.15.1-next.0",
53
- "@backstage/release-manifests": "0.0.11",
54
- "@backstage/types": "1.1.1",
45
+ "@backstage/catalog-model": "^1.7.0",
46
+ "@backstage/cli-common": "^0.1.14",
47
+ "@backstage/cli-node": "^0.2.9",
48
+ "@backstage/config": "^1.2.0",
49
+ "@backstage/config-loader": "^1.9.1",
50
+ "@backstage/errors": "^1.2.4",
51
+ "@backstage/eslint-plugin": "^0.1.10",
52
+ "@backstage/integration": "^1.15.1",
53
+ "@backstage/release-manifests": "^0.0.11",
54
+ "@backstage/types": "^1.1.1",
55
55
  "@manypkg/get-packages": "^1.1.3",
56
56
  "@module-federation/enhanced": "^0.6.0",
57
57
  "@octokit/graphql": "^5.0.0",
@@ -90,7 +90,7 @@
90
90
  "cross-spawn": "^7.0.3",
91
91
  "css-loader": "^6.5.1",
92
92
  "ctrlc-windows": "^2.1.0",
93
- "esbuild": "^0.23.0",
93
+ "esbuild": "^0.24.0",
94
94
  "esbuild-loader": "^4.0.0",
95
95
  "eslint": "^8.6.0",
96
96
  "eslint-config-prettier": "^9.0.0",
@@ -106,13 +106,15 @@
106
106
  "express": "^4.17.1",
107
107
  "fork-ts-checker-webpack-plugin": "^9.0.0",
108
108
  "fs-extra": "^11.2.0",
109
- "git-url-parse": "^14.0.0",
109
+ "git-url-parse": "^15.0.0",
110
110
  "glob": "^7.1.7",
111
111
  "global-agent": "^3.0.0",
112
+ "globby": "^11.1.0",
112
113
  "handlebars": "^4.7.3",
113
114
  "html-webpack-plugin": "^5.3.1",
114
115
  "inquirer": "^8.2.0",
115
116
  "jest": "^29.7.0",
117
+ "jest-cli": "^29.7.0",
116
118
  "jest-css-modules": "^2.1.0",
117
119
  "jest-environment-jsdom": "^29.0.2",
118
120
  "jest-runtime": "^29.0.2",
@@ -152,21 +154,32 @@
152
154
  "webpack-dev-server": "^5.0.0",
153
155
  "webpack-node-externals": "^3.0.0",
154
156
  "yaml": "^2.0.0",
157
+ "yargs": "^16.2.0",
155
158
  "yml-loader": "^2.1.0",
156
159
  "yn": "^4.0.0",
157
160
  "zod": "^3.22.4"
158
161
  },
159
162
  "devDependencies": {
160
163
  "@backstage/backend-common": "^0.25.0",
161
- "@backstage/backend-plugin-api": "1.0.1-next.0",
162
- "@backstage/backend-test-utils": "1.0.1-next.1",
163
- "@backstage/config": "1.2.0",
164
- "@backstage/core-app-api": "1.15.1-next.1",
165
- "@backstage/core-components": "0.15.1-next.1",
166
- "@backstage/core-plugin-api": "1.10.0-next.1",
167
- "@backstage/dev-utils": "1.1.2-next.1",
168
- "@backstage/test-utils": "1.6.1-next.1",
169
- "@backstage/theme": "0.5.8-next.0",
164
+ "@backstage/backend-plugin-api": "^1.0.1",
165
+ "@backstage/backend-test-utils": "^1.0.1",
166
+ "@backstage/catalog-client": "^1.7.1",
167
+ "@backstage/config": "^1.2.0",
168
+ "@backstage/core-app-api": "^1.15.1",
169
+ "@backstage/core-components": "^0.15.1",
170
+ "@backstage/core-plugin-api": "^1.10.0",
171
+ "@backstage/dev-utils": "^1.1.2",
172
+ "@backstage/errors": "^1.2.4",
173
+ "@backstage/plugin-auth-backend": "^0.23.1",
174
+ "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.1",
175
+ "@backstage/plugin-catalog-node": "^1.13.1",
176
+ "@backstage/plugin-scaffolder-node": "^0.5.0",
177
+ "@backstage/plugin-scaffolder-node-test-utils": "^0.1.13",
178
+ "@backstage/test-utils": "^1.7.0",
179
+ "@backstage/theme": "^0.6.0",
180
+ "@rspack/core": "^1.0.10",
181
+ "@rspack/dev-server": "^1.0.9",
182
+ "@rspack/plugin-react-refresh": "^1.0.0",
170
183
  "@types/cross-spawn": "^6.0.2",
171
184
  "@types/ejs": "^3.1.3",
172
185
  "@types/express": "^4.17.6",
@@ -181,6 +194,7 @@
181
194
  "@types/svgo": "^2.6.2",
182
195
  "@types/tar": "^6.1.1",
183
196
  "@types/terser-webpack-plugin": "^5.0.4",
197
+ "@types/webpack-sources": "^3.2.3",
184
198
  "@types/yarnpkg__lockfile": "^1.1.4",
185
199
  "@vitejs/plugin-react": "^4.3.1",
186
200
  "del": "^7.0.0",
@@ -191,12 +205,28 @@
191
205
  "vite-plugin-node-polyfills": "^0.22.0"
192
206
  },
193
207
  "peerDependencies": {
194
- "@vitejs/plugin-react": "^4.3.1",
195
- "vite": "^5.0.0",
196
- "vite-plugin-html": "^3.2.2",
208
+ "@modyfi/vite-plugin-yaml": "^1.1.0",
209
+ "@rspack/core": "^1.0.10",
210
+ "@rspack/dev-server": "^1.0.9",
211
+ "@rspack/plugin-react-refresh": "^1.0.0",
212
+ "@vitejs/plugin-react": "^4.0.4",
213
+ "vite": "^4.4.9",
214
+ "vite-plugin-html": "^3.2.0",
197
215
  "vite-plugin-node-polyfills": "^0.22.0"
198
216
  },
199
217
  "peerDependenciesMeta": {
218
+ "@modyfi/vite-plugin-yaml": {
219
+ "optional": true
220
+ },
221
+ "@rspack/core": {
222
+ "optional": true
223
+ },
224
+ "@rspack/dev-server": {
225
+ "optional": true
226
+ },
227
+ "@rspack/plugin-react-refresh": {
228
+ "optional": true
229
+ },
200
230
  "@vitejs/plugin-react": {
201
231
  "optional": true
202
232
  },
@@ -1,14 +1,28 @@
1
1
  # {{id}}
2
2
 
3
- Welcome to the {{id}} backend plugin!
3
+ This plugin backend was templated using the Backstage CLI. You should replace this text with a description of your plugin backend.
4
4
 
5
- _This plugin was created through the Backstage CLI_
5
+ ## Installation
6
6
 
7
- ## Getting started
7
+ This plugin is installed via the `{{name}}` package. To install it to your backend package, run the following command:
8
8
 
9
- Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn
10
- start` in the root directory, and then navigating to [/{{id}}/health](http://localhost:7007/api/{{id}}/health).
9
+ ```bash
10
+ # From your root directory
11
+ yarn --cwd packages/backend add {{name}}
12
+ ```
11
13
 
12
- You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
13
- This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
14
- It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
14
+ Then add the plugin to your backend in `packages/backend/src/index.ts`:
15
+
16
+ ```ts
17
+ const backend = createBackend();
18
+ // ...
19
+ backend.add(import('{{name}}'));
20
+ ```
21
+
22
+ ## Development
23
+
24
+ This plugin backend can be started in a standalone mode from directly in this
25
+ package with `yarn start`. It is a limited setup that is most convenient when
26
+ developing the plugin backend itself.
27
+
28
+ If you want to run the entire project, including the frontend, run `yarn dev` from the root directory.
@@ -0,0 +1,60 @@
1
+ import { createBackend } from '@backstage/backend-defaults';
2
+ import { mockServices } from '@backstage/backend-test-utils';
3
+ import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
4
+
5
+ // TEMPLATE NOTE:
6
+ // This is the development setup for your plugin that wires up a
7
+ // minimal backend that can use both real and mocked plugins and services.
8
+ //
9
+ // Start up the backend by running `yarn start` in the package directory.
10
+ // Once it's up and running, try out the following requests:
11
+ //
12
+ // Create a new todo item, standalone or for the sample component:
13
+ //
14
+ // curl http://localhost:7007/api/{{id}}/todos -H 'Content-Type: application/json' -d '{"title": "My Todo"}'
15
+ // curl http://localhost:7007/api/{{id}}/todos -H 'Content-Type: application/json' -d '{"title": "My Todo", "entityRef": "component:default/sample"}'
16
+ //
17
+ // List TODOs:
18
+ //
19
+ // curl http://localhost:7007/api/{{id}}/todos
20
+ //
21
+ // Explicitly make an unauthenticated request, or with service auth:
22
+ //
23
+ // curl http://localhost:7007/api/{{id}}/todos -H 'Authorization: Bearer mock-none-token'
24
+ // curl http://localhost:7007/api/{{id}}/todos -H 'Authorization: Bearer mock-service-token'
25
+
26
+ const backend = createBackend();
27
+
28
+ // TEMPLATE NOTE:
29
+ // Mocking the auth and httpAuth service allows you to call your plugin API without
30
+ // having to authenticate.
31
+ //
32
+ // If you want to use real auth, you can install the following instead:
33
+ // backend.add(import('@backstage/plugin-auth-backend'));
34
+ // backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
35
+ backend.add(mockServices.auth.factory());
36
+ backend.add(mockServices.httpAuth.factory());
37
+
38
+ // TEMPLATE NOTE:
39
+ // Rather than using a real catalog you can use a mock with a fixed set of entities.
40
+ backend.add(
41
+ catalogServiceMock.factory({
42
+ entities: [
43
+ {
44
+ apiVersion: 'backstage.io/v1alpha1',
45
+ kind: 'Component',
46
+ metadata: {
47
+ name: 'sample',
48
+ title: 'Sample Component',
49
+ },
50
+ spec: {
51
+ type: 'service',
52
+ },
53
+ },
54
+ ],
55
+ }),
56
+ );
57
+
58
+ backend.add(import('../src'));
59
+
60
+ backend.start();
@@ -30,19 +30,19 @@
30
30
  "dependencies": {
31
31
  "@backstage/backend-defaults": "{{versionQuery '@backstage/backend-defaults'}}",
32
32
  "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}",
33
+ "@backstage/catalog-client": "{{versionQuery '@backstage/catalog-client'}}",
34
+ "@backstage/errors": "{{versionQuery '@backstage/errors'}}",
35
+ "@backstage/plugin-catalog-node": "{{versionQuery '@backstage/plugin-catalog-node'}}",
33
36
  "express": "{{versionQuery 'express' '4.17.1'}}",
34
37
  "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}",
35
- "node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}"
38
+ "zod": "{{versionQuery 'zod' '3.22.4'}}"
36
39
  },
37
40
  "devDependencies": {
38
41
  "@backstage/backend-test-utils": "{{versionQuery '@backstage/backend-test-utils'}}",
39
42
  "@backstage/cli": "{{versionQuery '@backstage/cli'}}",
40
- "@backstage/plugin-auth-backend": "{{versionQuery '@backstage/plugin-auth-backend'}}",
41
- "@backstage/plugin-auth-backend-module-guest-provider": "{{versionQuery '@backstage/plugin-auth-backend-module-guest-provider'}}",
42
43
  "@types/express": "{{versionQuery '@types/express' '4.17.6'}}",
43
44
  "@types/supertest": "{{versionQuery '@types/supertest' '2.0.12'}}",
44
- "supertest": "{{versionQuery 'supertest' '6.2.4'}}",
45
- "msw": "{{versionQuery 'msw' '2.3.1'}}"
45
+ "supertest": "{{versionQuery 'supertest' '6.2.4'}}"
46
46
  },
47
47
  "files": [
48
48
  "dist"
@@ -1,2 +1 @@
1
- export * from './service/router';
2
1
  export { {{pluginVar}} as default } from './plugin';
@@ -0,0 +1,85 @@
1
+ import {
2
+ mockCredentials,
3
+ startTestBackend,
4
+ } from '@backstage/backend-test-utils';
5
+ import { {{pluginVar}} } from './plugin';
6
+ import request from 'supertest';
7
+ import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
8
+
9
+ // TEMPLATE NOTE:
10
+ // Plugin tests are integration tests for your plugin, ensuring that all pieces
11
+ // work together end-to-end. You can still mock injected backend services
12
+ // however, just like anyone who installs your plugin might replace the
13
+ // services with their own implementations.
14
+ describe('plugin', () => {
15
+ it('should create and read TODO items', async () => {
16
+ const { server } = await startTestBackend({
17
+ features: [{{pluginVar}}],
18
+ });
19
+
20
+ await request(server).get('/api/{{id}}/todos').expect(200, {
21
+ items: [],
22
+ });
23
+
24
+ const createRes = await request(server)
25
+ .post('/api/{{id}}/todos')
26
+ .send({ title: 'My Todo' });
27
+
28
+ expect(createRes.status).toBe(201);
29
+ expect(createRes.body).toEqual({
30
+ id: expect.any(String),
31
+ title: 'My Todo',
32
+ createdBy: mockCredentials.user().principal.userEntityRef,
33
+ createdAt: expect.any(String),
34
+ });
35
+
36
+ const createdTodoItem = createRes.body;
37
+
38
+ await request(server)
39
+ .get('/api/{{id}}/todos')
40
+ .expect(200, {
41
+ items: [createdTodoItem],
42
+ });
43
+
44
+ await request(server)
45
+ .get(`/api/{{id}}/todos/${createdTodoItem.id}`)
46
+ .expect(200, createdTodoItem);
47
+ });
48
+
49
+ it('should create TODO item with catalog information', async () => {
50
+ const { server } = await startTestBackend({
51
+ features: [
52
+ {{pluginVar}},
53
+ catalogServiceMock.factory({
54
+ entities: [
55
+ {
56
+ apiVersion: 'backstage.io/v1alpha1',
57
+ kind: 'Component',
58
+ metadata: {
59
+ name: 'my-component',
60
+ namespace: 'default',
61
+ title: 'My Component',
62
+ },
63
+ spec: {
64
+ type: 'service',
65
+ owner: 'me',
66
+ },
67
+ },
68
+ ],
69
+ }),
70
+ ],
71
+ });
72
+
73
+ const createRes = await request(server)
74
+ .post('/api/{{id}}/todos')
75
+ .send({ title: 'My Todo', entityRef: 'component:default/my-component' });
76
+
77
+ expect(createRes.status).toBe(201);
78
+ expect(createRes.body).toEqual({
79
+ id: expect.any(String),
80
+ title: '[My Component] My Todo',
81
+ createdBy: mockCredentials.user().principal.userEntityRef,
82
+ createdAt: expect.any(String),
83
+ });
84
+ });
85
+ });
@@ -2,7 +2,9 @@ import {
2
2
  coreServices,
3
3
  createBackendPlugin,
4
4
  } from '@backstage/backend-plugin-api';
5
- import { createRouter } from './service/router';
5
+ import { createRouter } from './router';
6
+ import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
7
+ import { createTodoListService } from './services/TodoListService';
6
8
 
7
9
  /**
8
10
  * {{pluginVar}} backend plugin
@@ -14,25 +16,25 @@ export const {{pluginVar}} = createBackendPlugin({
14
16
  register(env) {
15
17
  env.registerInit({
16
18
  deps: {
17
- httpRouter: coreServices.httpRouter,
18
19
  logger: coreServices.logger,
19
- config: coreServices.rootConfig,
20
+ auth: coreServices.auth,
21
+ httpAuth: coreServices.httpAuth,
22
+ httpRouter: coreServices.httpRouter,
23
+ catalog: catalogServiceRef,
20
24
  },
21
- async init({
22
- httpRouter,
23
- logger,
24
- config,
25
- }) {
25
+ async init({ logger, auth, httpAuth, httpRouter, catalog }) {
26
+ const todoListService = await createTodoListService({
27
+ logger,
28
+ auth,
29
+ catalog,
30
+ });
31
+
26
32
  httpRouter.use(
27
33
  await createRouter({
28
- logger,
29
- config,
34
+ httpAuth,
35
+ todoListService,
30
36
  }),
31
37
  );
32
- httpRouter.addAuthPolicy({
33
- path: '/health',
34
- allow: 'unauthenticated',
35
- });
36
38
  },
37
39
  });
38
40
  },
@@ -0,0 +1,67 @@
1
+ import {
2
+ mockCredentials,
3
+ mockErrorHandler,
4
+ mockServices,
5
+ } from '@backstage/backend-test-utils';
6
+ import express from 'express';
7
+ import request from 'supertest';
8
+
9
+ import { createRouter } from './router';
10
+ import { TodoListService } from './services/TodoListService/types';
11
+
12
+ const mockTodoItem = {
13
+ title: 'Do the thing',
14
+ id: '123',
15
+ createdBy: mockCredentials.user().principal.userEntityRef,
16
+ createdAt: new Date().toISOString(),
17
+ };
18
+
19
+ // TEMPLATE NOTE:
20
+ // Testing the router directly allows you to write a unit test that mocks the provided options.
21
+ describe('createRouter', () => {
22
+ let app: express.Express;
23
+ let todoListService: jest.Mocked<TodoListService>;
24
+
25
+ beforeEach(async () => {
26
+ todoListService = {
27
+ createTodo: jest.fn(),
28
+ listTodos: jest.fn(),
29
+ getTodo: jest.fn(),
30
+ };
31
+ const router = await createRouter({
32
+ httpAuth: mockServices.httpAuth(),
33
+ todoListService,
34
+ });
35
+ app = express();
36
+ app.use(router);
37
+ app.use(mockErrorHandler());
38
+ });
39
+
40
+ it('should create a TODO', async () => {
41
+ todoListService.createTodo.mockResolvedValue(mockTodoItem);
42
+
43
+ const response = await request(app).post('/todos').send({
44
+ title: 'Do the thing',
45
+ });
46
+
47
+ expect(response.status).toBe(201);
48
+ expect(response.body).toEqual(mockTodoItem);
49
+ });
50
+
51
+ it('should not allow unauthenticated requests to create a TODO', async () => {
52
+ todoListService.createTodo.mockResolvedValue(mockTodoItem);
53
+
54
+ // TEMPLATE NOTE:
55
+ // The HttpAuth mock service considers all requests to be authenticated as a
56
+ // mock user by default. In order to test other cases we need to explicitly
57
+ // pass an authorization header with mock credentials.
58
+ const response = await request(app)
59
+ .post('/todos')
60
+ .set('Authorization', mockCredentials.none.header())
61
+ .send({
62
+ title: 'Do the thing',
63
+ });
64
+
65
+ expect(response.status).toBe(401);
66
+ });
67
+ });
@@ -0,0 +1,51 @@
1
+ import { HttpAuthService } from '@backstage/backend-plugin-api';
2
+ import { InputError } from '@backstage/errors';
3
+ import { z } from 'zod';
4
+ import express from 'express';
5
+ import Router from 'express-promise-router';
6
+ import { TodoListService } from './services/TodoListService/types';
7
+
8
+ export async function createRouter({
9
+ httpAuth,
10
+ todoListService,
11
+ }: {
12
+ httpAuth: HttpAuthService;
13
+ todoListService: TodoListService;
14
+ }): Promise<express.Router> {
15
+ const router = Router();
16
+ router.use(express.json());
17
+
18
+ // TEMPLATE NOTE:
19
+ // Zod is a powerful library for data validation and recommended in particular
20
+ // for user-defined schemas. In this case we use it for input validation too.
21
+ //
22
+ // If you want to define a schema for your API we recommend using Backstage's
23
+ // OpenAPI tooling: https://backstage.io/docs/next/openapi/01-getting-started
24
+ const todoSchema = z.object({
25
+ title: z.string(),
26
+ entityRef: z.string().optional(),
27
+ });
28
+
29
+ router.post('/todos', async (req, res) => {
30
+ const parsed = todoSchema.safeParse(req.body);
31
+ if (!parsed.success) {
32
+ throw new InputError(parsed.error.toString());
33
+ }
34
+
35
+ const result = await todoListService.createTodo(parsed.data, {
36
+ credentials: await httpAuth.credentials(req, { allow: ['user'] }),
37
+ });
38
+
39
+ res.status(201).json(result);
40
+ });
41
+
42
+ router.get('/todos', async (_req, res) => {
43
+ res.json(await todoListService.listTodos());
44
+ });
45
+
46
+ router.get('/todos/:id', async (req, res) => {
47
+ res.json(await todoListService.getTodo({ id: req.params.id }));
48
+ });
49
+
50
+ return router;
51
+ }
@@ -0,0 +1,100 @@
1
+ import { AuthService, LoggerService } from '@backstage/backend-plugin-api';
2
+ import { NotFoundError } from '@backstage/errors';
3
+ import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
4
+ import crypto from 'node:crypto';
5
+ import { TodoItem, TodoListService } from './types';
6
+
7
+ // TEMPLATE NOTE:
8
+ // This is a simple in-memory todo list store. It is recommended to use a
9
+ // database to store data in a real application. See the database service
10
+ // documentation for more information on how to do this:
11
+ // https://backstage.io/docs/backend-system/core-services/database
12
+ export async function createTodoListService({
13
+ auth,
14
+ logger,
15
+ catalog,
16
+ }: {
17
+ auth: AuthService;
18
+ logger: LoggerService;
19
+ catalog: typeof catalogServiceRef.T;
20
+ }): Promise<TodoListService> {
21
+ logger.info('Initializing TodoListService');
22
+
23
+ const storedTodos = new Array<TodoItem>();
24
+
25
+ return {
26
+ async createTodo(input, options) {
27
+ let title = input.title;
28
+
29
+ // TEMPLATE NOTE:
30
+ // A common pattern for Backstage plugins is to pass an entity reference
31
+ // from the frontend to then fetch the entire entity from the catalog in the
32
+ // backend plugin.
33
+ if (input.entityRef) {
34
+ // TEMPLATE NOTE:
35
+ // Cross-plugin communication uses service-to-service authentication. The
36
+ // `AuthService` lets you generate a token that is valid for communication
37
+ // with the target plugin only. You must also provide credentials for the
38
+ // identity that you are making the request on behalf of.
39
+ //
40
+ // If you want to make a request using the plugin backend's own identity,
41
+ // you can access it via the `auth.getOwnServiceCredentials()` method.
42
+ // Beware that this bypasses any user permission checks.
43
+ const { token } = await auth.getPluginRequestToken({
44
+ onBehalfOf: options.credentials,
45
+ targetPluginId: 'catalog',
46
+ });
47
+ const entity = await catalog.getEntityByRef(input.entityRef, {
48
+ token,
49
+ });
50
+ if (!entity) {
51
+ throw new NotFoundError(
52
+ `No entity found for ref '${input.entityRef}'`,
53
+ );
54
+ }
55
+
56
+ // TEMPLATE NOTE:
57
+ // Here you could read any form of data from the entity. A common use case
58
+ // is to read the value of a custom annotation for your plugin. You can
59
+ // read more about how to add custom annotations here:
60
+ // https://backstage.io/docs/features/software-catalog/extending-the-model#adding-a-new-annotation
61
+ //
62
+ // In this example we just use the entity title to decorate the todo item.
63
+
64
+ const entityDisplay = entity.metadata.title ?? input.entityRef;
65
+ title = `[${entityDisplay}] ${input.title}`;
66
+ }
67
+
68
+ const id = crypto.randomUUID();
69
+ const createdBy = options.credentials.principal.userEntityRef;
70
+ const newTodo = {
71
+ title,
72
+ id,
73
+ createdBy,
74
+ createdAt: new Date().toISOString(),
75
+ };
76
+
77
+ storedTodos.push(newTodo);
78
+
79
+ // TEMPLATE NOTE:
80
+ // The second argument of the logger methods can be used to pass
81
+ // structured metadata. You can read more about the logger service here:
82
+ // https://backstage.io/docs/backend-system/core-services/logger
83
+ logger.info('Created new todo item', { id, title, createdBy });
84
+
85
+ return newTodo;
86
+ },
87
+
88
+ async listTodos() {
89
+ return { items: Array.from(storedTodos) };
90
+ },
91
+
92
+ async getTodo(request: { id: string }) {
93
+ const todo = storedTodos.find(item => item.id === request.id);
94
+ if (!todo) {
95
+ throw new NotFoundError(`No todo found with id '${request.id}'`);
96
+ }
97
+ return todo;
98
+ },
99
+ };
100
+ }
@@ -0,0 +1 @@
1
+ export { createTodoListService } from './createTodoListService';
@@ -0,0 +1,27 @@
1
+ import {
2
+ BackstageCredentials,
3
+ BackstageUserPrincipal,
4
+ } from '@backstage/backend-plugin-api';
5
+
6
+ export interface TodoItem {
7
+ title: string;
8
+ id: string;
9
+ createdBy: string;
10
+ createdAt: string;
11
+ }
12
+
13
+ export interface TodoListService {
14
+ createTodo(
15
+ input: {
16
+ title: string;
17
+ entityRef?: string;
18
+ },
19
+ options: {
20
+ credentials: BackstageCredentials<BackstageUserPrincipal>;
21
+ },
22
+ ): Promise<TodoItem>;
23
+
24
+ listTodos(): Promise<{ items: TodoItem[] }>;
25
+
26
+ getTodo(request: { id: string }): Promise<TodoItem>;
27
+ }
@@ -29,10 +29,12 @@
29
29
  "postpack": "backstage-cli package postpack"
30
30
  },
31
31
  "dependencies": {
32
+ "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}",
32
33
  "@backstage/plugin-scaffolder-node": "{{versionQuery '@backstage/plugin-scaffolder-node'}}"
33
34
  },
34
35
  "devDependencies": {
35
- "@backstage/cli": "{{versionQuery '@backstage/cli'}}"
36
+ "@backstage/cli": "{{versionQuery '@backstage/cli'}}",
37
+ "@backstage/plugin-scaffolder-node-test-utils": "{{versionQuery '@backstage/plugin-scaffolder-node-test-utils'}}"
36
38
  },
37
39
  "files": [
38
40
  "dist"