@backstage/cli 0.34.1 → 0.34.2-next.2

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 (28) hide show
  1. package/CHANGELOG.md +30 -3
  2. package/dist/lib/version.cjs.js +34 -32
  3. package/dist/packages/backend-defaults/package.json.cjs.js +1 -1
  4. package/dist/packages/backend-plugin-api/package.json.cjs.js +1 -1
  5. package/dist/packages/backend-test-utils/package.json.cjs.js +1 -1
  6. package/dist/packages/catalog-client/package.json.cjs.js +1 -1
  7. package/dist/packages/cli/package.json.cjs.js +1 -1
  8. package/dist/packages/core-components/package.json.cjs.js +1 -1
  9. package/dist/packages/dev-utils/package.json.cjs.js +1 -1
  10. package/dist/packages/frontend-defaults/package.json.cjs.js +1 -1
  11. package/dist/packages/frontend-plugin-api/package.json.cjs.js +1 -1
  12. package/dist/packages/frontend-test-utils/package.json.cjs.js +1 -1
  13. package/dist/packages/types/package.json.cjs.js +6 -0
  14. package/dist/plugins/auth-backend/package.json.cjs.js +1 -1
  15. package/dist/plugins/auth-backend-module-guest-provider/package.json.cjs.js +1 -1
  16. package/dist/plugins/catalog-node/package.json.cjs.js +1 -1
  17. package/dist/plugins/scaffolder-node/package.json.cjs.js +1 -1
  18. package/dist/plugins/scaffolder-node-test-utils/package.json.cjs.js +1 -1
  19. package/package.json +27 -27
  20. package/templates/backend-plugin/package.json.hbs +1 -0
  21. package/templates/backend-plugin/src/plugin.test.ts.hbs +44 -0
  22. package/templates/backend-plugin/src/plugin.ts.hbs +4 -11
  23. package/templates/backend-plugin/src/router.test.ts +6 -6
  24. package/templates/backend-plugin/src/router.ts +6 -6
  25. package/templates/backend-plugin/src/services/TodoListService.ts +155 -0
  26. package/templates/backend-plugin/src/services/TodoListService/createTodoListService.ts +0 -92
  27. package/templates/backend-plugin/src/services/TodoListService/index.ts +0 -1
  28. package/templates/backend-plugin/src/services/TodoListService/types.ts +0 -27
package/CHANGELOG.md CHANGED
@@ -1,11 +1,38 @@
1
1
  # @backstage/cli
2
2
 
3
- ## 0.34.1
3
+ ## 0.34.2-next.2
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - f470192: Fixed the `new-frontend-plugin` template that was incorrectly passing `id` instead of `pluginId` to `createFrontendPlugin` and unnecessarily importing `React`.
8
- - d778acb: Fixed an issue that could cause conflicts of detected modules in workspaces with multiple apps.
7
+ - Updated dependencies
8
+ - @backstage/config-loader@1.10.3-next.0
9
+ - @backstage/integration@1.18.0-next.0
10
+
11
+ ## 0.34.2-next.1
12
+
13
+ ### Patch Changes
14
+
15
+ - e1adce4: Updated the backend plugin template to use a new pattern for the `TodoListService` that reduces boilerplate.
16
+ - Updated dependencies
17
+ - @backstage/integration@1.18.0-next.0
18
+
19
+ ## 0.34.1-next.0
20
+
21
+ ### Patch Changes
22
+
23
+ - 080f252: Fixed the `new-frontend-plugin` template that was incorrectly passing `id` instead of `pluginId` to `createFrontendPlugin` and unnecessarily importing `React`.
24
+ - 275bda8: Fixed an issue that could cause conflicts of detected modules in workspaces with multiple apps.
25
+ - Updated dependencies
26
+ - @backstage/catalog-model@1.7.5
27
+ - @backstage/cli-common@0.1.15
28
+ - @backstage/cli-node@0.2.14
29
+ - @backstage/config@1.3.3
30
+ - @backstage/config-loader@1.10.2
31
+ - @backstage/errors@1.2.7
32
+ - @backstage/eslint-plugin@0.1.11
33
+ - @backstage/integration@1.17.1
34
+ - @backstage/release-manifests@0.0.13
35
+ - @backstage/types@1.2.1
9
36
 
10
37
  ## 0.34.0
11
38
 
@@ -3,27 +3,28 @@
3
3
  var fs = require('fs-extra');
4
4
  var semver = require('semver');
5
5
  var paths = require('./paths.cjs.js');
6
- var _package$j = require('../packages/backend-plugin-api/package.json.cjs.js');
7
- var _package$i = require('../packages/backend-test-utils/package.json.cjs.js');
8
- var _package$h = require('../packages/catalog-client/package.json.cjs.js');
9
- var _package$g = require('../packages/cli/package.json.cjs.js');
10
- var _package$f = require('../packages/config/package.json.cjs.js');
11
- var _package$e = require('../packages/core-app-api/package.json.cjs.js');
12
- var _package$d = require('../packages/core-components/package.json.cjs.js');
13
- var _package$c = require('../packages/core-plugin-api/package.json.cjs.js');
14
- var _package$b = require('../packages/dev-utils/package.json.cjs.js');
15
- var _package$a = require('../packages/errors/package.json.cjs.js');
16
- var _package$9 = require('../packages/frontend-defaults/package.json.cjs.js');
17
- var _package$8 = require('../packages/frontend-plugin-api/package.json.cjs.js');
18
- var _package$7 = require('../packages/frontend-test-utils/package.json.cjs.js');
19
- var _package$6 = require('../packages/test-utils/package.json.cjs.js');
6
+ var _package$k = require('../packages/backend-plugin-api/package.json.cjs.js');
7
+ var _package$j = require('../packages/backend-test-utils/package.json.cjs.js');
8
+ var _package$i = require('../packages/catalog-client/package.json.cjs.js');
9
+ var _package$h = require('../packages/cli/package.json.cjs.js');
10
+ var _package$g = require('../packages/config/package.json.cjs.js');
11
+ var _package$f = require('../packages/core-app-api/package.json.cjs.js');
12
+ var _package$e = require('../packages/core-components/package.json.cjs.js');
13
+ var _package$d = require('../packages/core-plugin-api/package.json.cjs.js');
14
+ var _package$c = require('../packages/dev-utils/package.json.cjs.js');
15
+ var _package$b = require('../packages/errors/package.json.cjs.js');
16
+ var _package$a = require('../packages/frontend-defaults/package.json.cjs.js');
17
+ var _package$9 = require('../packages/frontend-plugin-api/package.json.cjs.js');
18
+ var _package$8 = require('../packages/frontend-test-utils/package.json.cjs.js');
19
+ var _package$7 = require('../packages/test-utils/package.json.cjs.js');
20
20
  var _package$4 = require('../plugins/scaffolder-node/package.json.cjs.js');
21
21
  var _package$3 = require('../plugins/scaffolder-node-test-utils/package.json.cjs.js');
22
22
  var _package$2 = require('../plugins/auth-backend/package.json.cjs.js');
23
23
  var _package$1 = require('../plugins/auth-backend-module-guest-provider/package.json.cjs.js');
24
24
  var _package = require('../plugins/catalog-node/package.json.cjs.js');
25
- var _package$5 = require('../packages/theme/package.json.cjs.js');
26
- var _package$k = require('../packages/backend-defaults/package.json.cjs.js');
25
+ var _package$6 = require('../packages/theme/package.json.cjs.js');
26
+ var _package$5 = require('../packages/types/package.json.cjs.js');
27
+ var _package$l = require('../packages/backend-defaults/package.json.cjs.js');
27
28
 
28
29
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
29
30
 
@@ -31,22 +32,23 @@ var fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
31
32
  var semver__default = /*#__PURE__*/_interopDefaultCompat(semver);
32
33
 
33
34
  const packageVersions = {
34
- "@backstage/backend-defaults": _package$k.version,
35
- "@backstage/backend-plugin-api": _package$j.version,
36
- "@backstage/backend-test-utils": _package$i.version,
37
- "@backstage/catalog-client": _package$h.version,
38
- "@backstage/cli": _package$g.version,
39
- "@backstage/config": _package$f.version,
40
- "@backstage/core-app-api": _package$e.version,
41
- "@backstage/core-components": _package$d.version,
42
- "@backstage/core-plugin-api": _package$c.version,
43
- "@backstage/dev-utils": _package$b.version,
44
- "@backstage/errors": _package$a.version,
45
- "@backstage/frontend-defaults": _package$9.version,
46
- "@backstage/frontend-plugin-api": _package$8.version,
47
- "@backstage/frontend-test-utils": _package$7.version,
48
- "@backstage/test-utils": _package$6.version,
49
- "@backstage/theme": _package$5.version,
35
+ "@backstage/backend-defaults": _package$l.version,
36
+ "@backstage/backend-plugin-api": _package$k.version,
37
+ "@backstage/backend-test-utils": _package$j.version,
38
+ "@backstage/catalog-client": _package$i.version,
39
+ "@backstage/cli": _package$h.version,
40
+ "@backstage/config": _package$g.version,
41
+ "@backstage/core-app-api": _package$f.version,
42
+ "@backstage/core-components": _package$e.version,
43
+ "@backstage/core-plugin-api": _package$d.version,
44
+ "@backstage/dev-utils": _package$c.version,
45
+ "@backstage/errors": _package$b.version,
46
+ "@backstage/frontend-defaults": _package$a.version,
47
+ "@backstage/frontend-plugin-api": _package$9.version,
48
+ "@backstage/frontend-test-utils": _package$8.version,
49
+ "@backstage/test-utils": _package$7.version,
50
+ "@backstage/theme": _package$6.version,
51
+ "@backstage/types": _package$5.version,
50
52
  "@backstage/plugin-scaffolder-node": _package$4.version,
51
53
  "@backstage/plugin-scaffolder-node-test-utils": _package$3.version,
52
54
  "@backstage/plugin-auth-backend": _package$2.version,
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.12.0";
3
+ var version = "0.12.1-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.4.2";
3
+ var version = "1.4.3-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.8.0";
3
+ var version = "1.9.0-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.11.0";
3
+ var version = "1.12.0-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.34.1";
3
+ var version = "0.34.2-next.2";
4
4
  var dependencies = {
5
5
  "@backstage/catalog-model": "workspace:^",
6
6
  "@backstage/cli-common": "workspace:^",
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.17.5";
3
+ var version = "0.17.6-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.1.13";
3
+ var version = "1.1.14-next.2";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.3.0";
3
+ var version = "0.3.1-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.11.0";
3
+ var version = "0.11.1-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.3.5";
3
+ var version = "0.3.6-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -0,0 +1,6 @@
1
+ 'use strict';
2
+
3
+ var version = "1.2.1";
4
+
5
+ exports.version = version;
6
+ //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.25.3";
3
+ var version = "0.25.4-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.2.11";
3
+ var version = "0.2.12-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "1.18.0";
3
+ var version = "1.19.0-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.11.0";
3
+ var version = "0.11.1-next.0";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var version = "0.3.2";
3
+ var version = "0.3.3-next.1";
4
4
 
5
5
  exports.version = version;
6
6
  //# sourceMappingURL=package.json.cjs.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/cli",
3
- "version": "0.34.1",
3
+ "version": "0.34.2-next.2",
4
4
  "description": "CLI for developing Backstage plugins and apps",
5
5
  "backstage": {
6
6
  "role": "cli"
@@ -47,16 +47,16 @@
47
47
  ]
48
48
  },
49
49
  "dependencies": {
50
- "@backstage/catalog-model": "^1.7.5",
51
- "@backstage/cli-common": "^0.1.15",
52
- "@backstage/cli-node": "^0.2.14",
53
- "@backstage/config": "^1.3.3",
54
- "@backstage/config-loader": "^1.10.2",
55
- "@backstage/errors": "^1.2.7",
56
- "@backstage/eslint-plugin": "^0.1.11",
57
- "@backstage/integration": "^1.17.1",
58
- "@backstage/release-manifests": "^0.0.13",
59
- "@backstage/types": "^1.2.1",
50
+ "@backstage/catalog-model": "1.7.5",
51
+ "@backstage/cli-common": "0.1.15",
52
+ "@backstage/cli-node": "0.2.14",
53
+ "@backstage/config": "1.3.3",
54
+ "@backstage/config-loader": "1.10.3-next.0",
55
+ "@backstage/errors": "1.2.7",
56
+ "@backstage/eslint-plugin": "0.1.11",
57
+ "@backstage/integration": "1.18.0-next.0",
58
+ "@backstage/release-manifests": "0.0.13",
59
+ "@backstage/types": "1.2.1",
60
60
  "@manypkg/get-packages": "^1.1.3",
61
61
  "@module-federation/enhanced": "^0.9.0",
62
62
  "@octokit/graphql": "^5.0.0",
@@ -154,22 +154,22 @@
154
154
  "zod-validation-error": "^3.4.0"
155
155
  },
156
156
  "devDependencies": {
157
- "@backstage/backend-plugin-api": "^1.4.2",
158
- "@backstage/backend-test-utils": "^1.8.0",
159
- "@backstage/catalog-client": "^1.11.0",
160
- "@backstage/config": "^1.3.3",
161
- "@backstage/core-app-api": "^1.18.0",
162
- "@backstage/core-components": "^0.17.5",
163
- "@backstage/core-plugin-api": "^1.10.9",
164
- "@backstage/dev-utils": "^1.1.13",
165
- "@backstage/errors": "^1.2.7",
166
- "@backstage/plugin-auth-backend": "^0.25.3",
167
- "@backstage/plugin-auth-backend-module-guest-provider": "^0.2.11",
168
- "@backstage/plugin-catalog-node": "^1.18.0",
169
- "@backstage/plugin-scaffolder-node": "^0.11.0",
170
- "@backstage/plugin-scaffolder-node-test-utils": "^0.3.2",
171
- "@backstage/test-utils": "^1.7.11",
172
- "@backstage/theme": "^0.6.8",
157
+ "@backstage/backend-plugin-api": "1.4.3-next.0",
158
+ "@backstage/backend-test-utils": "1.9.0-next.1",
159
+ "@backstage/catalog-client": "1.12.0-next.0",
160
+ "@backstage/config": "1.3.3",
161
+ "@backstage/core-app-api": "1.18.0",
162
+ "@backstage/core-components": "0.17.6-next.1",
163
+ "@backstage/core-plugin-api": "1.10.9",
164
+ "@backstage/dev-utils": "1.1.14-next.2",
165
+ "@backstage/errors": "1.2.7",
166
+ "@backstage/plugin-auth-backend": "0.25.4-next.1",
167
+ "@backstage/plugin-auth-backend-module-guest-provider": "0.2.12-next.0",
168
+ "@backstage/plugin-catalog-node": "1.19.0-next.1",
169
+ "@backstage/plugin-scaffolder-node": "0.11.1-next.0",
170
+ "@backstage/plugin-scaffolder-node-test-utils": "0.3.3-next.1",
171
+ "@backstage/test-utils": "1.7.11",
172
+ "@backstage/theme": "0.6.8",
173
173
  "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
174
174
  "@types/cross-spawn": "^6.0.2",
175
175
  "@types/ejs": "^3.1.3",
@@ -25,6 +25,7 @@
25
25
  "@backstage/backend-plugin-api": "{{versionQuery '@backstage/backend-plugin-api'}}",
26
26
  "@backstage/catalog-client": "{{versionQuery '@backstage/catalog-client'}}",
27
27
  "@backstage/errors": "{{versionQuery '@backstage/errors'}}",
28
+ "@backstage/types": "{{versionQuery '@backstage/types'}}",
28
29
  "@backstage/plugin-catalog-node": "{{versionQuery '@backstage/plugin-catalog-node'}}",
29
30
  "express": "{{versionQuery 'express' '4.17.1'}}",
30
31
  "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}",
@@ -2,9 +2,16 @@ import {
2
2
  mockCredentials,
3
3
  startTestBackend,
4
4
  } from '@backstage/backend-test-utils';
5
+ import { createServiceFactory } from '@backstage/backend-plugin-api';
6
+ import { todoListServiceRef } from './services/TodoListService';
5
7
  import { {{pluginVar}} } from './plugin';
6
8
  import request from 'supertest';
7
9
  import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
10
+ import {
11
+ ConflictError,
12
+ AuthenticationError,
13
+ NotAllowedError,
14
+ } from '@backstage/errors';
8
15
 
9
16
  // TEMPLATE NOTE:
10
17
  // Plugin tests are integration tests for your plugin, ensuring that all pieces
@@ -82,4 +89,41 @@ describe('plugin', () => {
82
89
  createdAt: expect.any(String),
83
90
  });
84
91
  });
92
+
93
+ it('should forward errors from the TodoListService', async () => {
94
+ const { server } = await startTestBackend({
95
+ features: [
96
+ {{pluginVar}},
97
+ createServiceFactory({
98
+ service: todoListServiceRef,
99
+ deps: {},
100
+ factory: () => ({
101
+ createTodo: jest.fn().mockRejectedValue(new ConflictError()),
102
+ listTodos: jest.fn().mockRejectedValue(new AuthenticationError()),
103
+ getTodo: jest.fn().mockRejectedValue(new NotAllowedError()),
104
+ }),
105
+ })
106
+ ],
107
+ });
108
+
109
+ const createRes = await request(server)
110
+ .post('/api/{{pluginId}}/todos')
111
+ .send({ title: 'My Todo', entityRef: 'component:default/my-component' });
112
+ expect(createRes.status).toBe(409);
113
+ expect(createRes.body).toMatchObject({
114
+ error: { name: 'ConflictError' },
115
+ });
116
+
117
+ const listRes = await request(server).get('/api/{{pluginId}}/todos');
118
+ expect(listRes.status).toBe(401);
119
+ expect(listRes.body).toMatchObject({
120
+ error: { name: 'AuthenticationError' },
121
+ });
122
+
123
+ const getRes = await request(server).get('/api/{{pluginId}}/todos/123');
124
+ expect(getRes.status).toBe(403);
125
+ expect(getRes.body).toMatchObject({
126
+ error: { name: 'NotAllowedError' },
127
+ });
128
+ });
85
129
  });
@@ -3,8 +3,7 @@ import {
3
3
  createBackendPlugin,
4
4
  } from '@backstage/backend-plugin-api';
5
5
  import { createRouter } from './router';
6
- import { catalogServiceRef } from '@backstage/plugin-catalog-node';
7
- import { createTodoListService } from './services/TodoListService';
6
+ import { todoListServiceRef } from './services/TodoListService';
8
7
 
9
8
  /**
10
9
  * {{pluginVar}} backend plugin
@@ -16,21 +15,15 @@ export const {{pluginVar}} = createBackendPlugin({
16
15
  register(env) {
17
16
  env.registerInit({
18
17
  deps: {
19
- logger: coreServices.logger,
20
18
  httpAuth: coreServices.httpAuth,
21
19
  httpRouter: coreServices.httpRouter,
22
- catalog: catalogServiceRef,
20
+ todoList: todoListServiceRef,
23
21
  },
24
- async init({ logger, httpAuth, httpRouter, catalog }) {
25
- const todoListService = await createTodoListService({
26
- logger,
27
- catalog,
28
- });
29
-
22
+ async init({ httpAuth, httpRouter, todoList }) {
30
23
  httpRouter.use(
31
24
  await createRouter({
32
25
  httpAuth,
33
- todoListService,
26
+ todoList,
34
27
  }),
35
28
  );
36
29
  },
@@ -7,7 +7,7 @@ import express from 'express';
7
7
  import request from 'supertest';
8
8
 
9
9
  import { createRouter } from './router';
10
- import { TodoListService } from './services/TodoListService/types';
10
+ import { todoListServiceRef } from './services/TodoListService';
11
11
 
12
12
  const mockTodoItem = {
13
13
  title: 'Do the thing',
@@ -20,17 +20,17 @@ const mockTodoItem = {
20
20
  // Testing the router directly allows you to write a unit test that mocks the provided options.
21
21
  describe('createRouter', () => {
22
22
  let app: express.Express;
23
- let todoListService: jest.Mocked<TodoListService>;
23
+ let todoList: jest.Mocked<typeof todoListServiceRef.T>;
24
24
 
25
25
  beforeEach(async () => {
26
- todoListService = {
26
+ todoList = {
27
27
  createTodo: jest.fn(),
28
28
  listTodos: jest.fn(),
29
29
  getTodo: jest.fn(),
30
30
  };
31
31
  const router = await createRouter({
32
32
  httpAuth: mockServices.httpAuth(),
33
- todoListService,
33
+ todoList,
34
34
  });
35
35
  app = express();
36
36
  app.use(router);
@@ -38,7 +38,7 @@ describe('createRouter', () => {
38
38
  });
39
39
 
40
40
  it('should create a TODO', async () => {
41
- todoListService.createTodo.mockResolvedValue(mockTodoItem);
41
+ todoList.createTodo.mockResolvedValue(mockTodoItem);
42
42
 
43
43
  const response = await request(app).post('/todos').send({
44
44
  title: 'Do the thing',
@@ -49,7 +49,7 @@ describe('createRouter', () => {
49
49
  });
50
50
 
51
51
  it('should not allow unauthenticated requests to create a TODO', async () => {
52
- todoListService.createTodo.mockResolvedValue(mockTodoItem);
52
+ todoList.createTodo.mockResolvedValue(mockTodoItem);
53
53
 
54
54
  // TEMPLATE NOTE:
55
55
  // The HttpAuth mock service considers all requests to be authenticated as a
@@ -3,14 +3,14 @@ import { InputError } from '@backstage/errors';
3
3
  import { z } from 'zod';
4
4
  import express from 'express';
5
5
  import Router from 'express-promise-router';
6
- import { TodoListService } from './services/TodoListService/types';
6
+ import { todoListServiceRef } from './services/TodoListService';
7
7
 
8
8
  export async function createRouter({
9
9
  httpAuth,
10
- todoListService,
10
+ todoList,
11
11
  }: {
12
12
  httpAuth: HttpAuthService;
13
- todoListService: TodoListService;
13
+ todoList: typeof todoListServiceRef.T;
14
14
  }): Promise<express.Router> {
15
15
  const router = Router();
16
16
  router.use(express.json());
@@ -32,7 +32,7 @@ export async function createRouter({
32
32
  throw new InputError(parsed.error.toString());
33
33
  }
34
34
 
35
- const result = await todoListService.createTodo(parsed.data, {
35
+ const result = await todoList.createTodo(parsed.data, {
36
36
  credentials: await httpAuth.credentials(req, { allow: ['user'] }),
37
37
  });
38
38
 
@@ -40,11 +40,11 @@ export async function createRouter({
40
40
  });
41
41
 
42
42
  router.get('/todos', async (_req, res) => {
43
- res.json(await todoListService.listTodos());
43
+ res.json(await todoList.listTodos());
44
44
  });
45
45
 
46
46
  router.get('/todos/:id', async (req, res) => {
47
- res.json(await todoListService.getTodo({ id: req.params.id }));
47
+ res.json(await todoList.getTodo({ id: req.params.id }));
48
48
  });
49
49
 
50
50
  return router;
@@ -0,0 +1,155 @@
1
+ /*
2
+ * Copyright 2025 The Backstage Authors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import crypto from 'node:crypto';
17
+ import {
18
+ coreServices,
19
+ createServiceFactory,
20
+ createServiceRef,
21
+ LoggerService,
22
+ } from '@backstage/backend-plugin-api';
23
+ import { NotFoundError } from '@backstage/errors';
24
+ import { catalogServiceRef } from '@backstage/plugin-catalog-node';
25
+ import {
26
+ BackstageCredentials,
27
+ BackstageUserPrincipal,
28
+ } from '@backstage/backend-plugin-api';
29
+ import { Expand } from '@backstage/types';
30
+
31
+ export interface TodoItem {
32
+ title: string;
33
+ id: string;
34
+ createdBy: string;
35
+ createdAt: string;
36
+ }
37
+
38
+ // TEMPLATE NOTE:
39
+ // This is a simple in-memory todo list store. It is recommended to use a
40
+ // database to store data in a real application. See the database service
41
+ // documentation for more information on how to do this:
42
+ // https://backstage.io/docs/backend-system/core-services/database
43
+ export class TodoListService {
44
+ readonly #logger: LoggerService;
45
+ readonly #catalog: typeof catalogServiceRef.T;
46
+
47
+ readonly #storedTodos = new Array<TodoItem>();
48
+
49
+ static create(options: {
50
+ logger: LoggerService;
51
+ catalog: typeof catalogServiceRef.T;
52
+ }) {
53
+ return new TodoListService(options.logger, options.catalog);
54
+ }
55
+
56
+ private constructor(
57
+ logger: LoggerService,
58
+ catalog: typeof catalogServiceRef.T,
59
+ ) {
60
+ this.#logger = logger;
61
+ this.#catalog = catalog;
62
+ }
63
+
64
+ async createTodo(
65
+ input: {
66
+ title: string;
67
+ entityRef?: string;
68
+ },
69
+ options: {
70
+ credentials: BackstageCredentials<BackstageUserPrincipal>;
71
+ },
72
+ ): Promise<TodoItem> {
73
+ let title = input.title;
74
+
75
+ // TEMPLATE NOTE:
76
+ // A common pattern for Backstage plugins is to pass an entity reference
77
+ // from the frontend to then fetch the entire entity from the catalog in the
78
+ // backend plugin.
79
+ if (input.entityRef) {
80
+ // TEMPLATE NOTE:
81
+ // Cross-plugin communication uses service-to-service authentication. The
82
+ // `AuthService` lets you generate a token that is valid for communication
83
+ // with the target plugin only. You must also provide credentials for the
84
+ // identity that you are making the request on behalf of.
85
+ //
86
+ // If you want to make a request using the plugin backend's own identity,
87
+ // you can access it via the `auth.getOwnServiceCredentials()` method.
88
+ // Beware that this bypasses any user permission checks.
89
+ const entity = await this.#catalog.getEntityByRef(
90
+ input.entityRef,
91
+ options,
92
+ );
93
+ if (!entity) {
94
+ throw new NotFoundError(`No entity found for ref '${input.entityRef}'`);
95
+ }
96
+
97
+ // TEMPLATE NOTE:
98
+ // Here you could read any form of data from the entity. A common use case
99
+ // is to read the value of a custom annotation for your plugin. You can
100
+ // read more about how to add custom annotations here:
101
+ // https://backstage.io/docs/features/software-catalog/extending-the-model#adding-a-new-annotation
102
+ //
103
+ // In this example we just use the entity title to decorate the todo item.
104
+
105
+ const entityDisplay = entity.metadata.title ?? input.entityRef;
106
+ title = `[${entityDisplay}] ${input.title}`;
107
+ }
108
+
109
+ const id = crypto.randomUUID();
110
+ const createdBy = options.credentials.principal.userEntityRef;
111
+ const newTodo = {
112
+ title,
113
+ id,
114
+ createdBy,
115
+ createdAt: new Date().toISOString(),
116
+ };
117
+
118
+ this.#storedTodos.push(newTodo);
119
+
120
+ // TEMPLATE NOTE:
121
+ // The second argument of the logger methods can be used to pass
122
+ // structured metadata. You can read more about the logger service here:
123
+ // https://backstage.io/docs/backend-system/core-services/logger
124
+ this.#logger.info('Created new todo item', { id, title, createdBy });
125
+
126
+ return newTodo;
127
+ }
128
+
129
+ async listTodos(): Promise<{ items: TodoItem[] }> {
130
+ return { items: Array.from(this.#storedTodos) };
131
+ }
132
+
133
+ async getTodo(request: { id: string }): Promise<TodoItem> {
134
+ const todo = this.#storedTodos.find(item => item.id === request.id);
135
+ if (!todo) {
136
+ throw new NotFoundError(`No todo found with id '${request.id}'`);
137
+ }
138
+ return todo;
139
+ }
140
+ }
141
+
142
+ export const todoListServiceRef = createServiceRef<Expand<TodoListService>>({
143
+ id: 'todo.list',
144
+ defaultFactory: async service =>
145
+ createServiceFactory({
146
+ service,
147
+ deps: {
148
+ logger: coreServices.logger,
149
+ catalog: catalogServiceRef,
150
+ },
151
+ async factory(deps) {
152
+ return TodoListService.create(deps);
153
+ },
154
+ }),
155
+ });
@@ -1,92 +0,0 @@
1
- import { LoggerService } from '@backstage/backend-plugin-api';
2
- import { NotFoundError } from '@backstage/errors';
3
- import { catalogServiceRef } from '@backstage/plugin-catalog-node';
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
- logger,
14
- catalog,
15
- }: {
16
- logger: LoggerService;
17
- catalog: typeof catalogServiceRef.T;
18
- }): Promise<TodoListService> {
19
- logger.info('Initializing TodoListService');
20
-
21
- const storedTodos = new Array<TodoItem>();
22
-
23
- return {
24
- async createTodo(input, options) {
25
- let title = input.title;
26
-
27
- // TEMPLATE NOTE:
28
- // A common pattern for Backstage plugins is to pass an entity reference
29
- // from the frontend to then fetch the entire entity from the catalog in the
30
- // backend plugin.
31
- if (input.entityRef) {
32
- // TEMPLATE NOTE:
33
- // Cross-plugin communication uses service-to-service authentication. The
34
- // `AuthService` lets you generate a token that is valid for communication
35
- // with the target plugin only. You must also provide credentials for the
36
- // identity that you are making the request on behalf of.
37
- //
38
- // If you want to make a request using the plugin backend's own identity,
39
- // you can access it via the `auth.getOwnServiceCredentials()` method.
40
- // Beware that this bypasses any user permission checks.
41
- const entity = await catalog.getEntityByRef(input.entityRef, options);
42
- if (!entity) {
43
- throw new NotFoundError(
44
- `No entity found for ref '${input.entityRef}'`,
45
- );
46
- }
47
-
48
- // TEMPLATE NOTE:
49
- // Here you could read any form of data from the entity. A common use case
50
- // is to read the value of a custom annotation for your plugin. You can
51
- // read more about how to add custom annotations here:
52
- // https://backstage.io/docs/features/software-catalog/extending-the-model#adding-a-new-annotation
53
- //
54
- // In this example we just use the entity title to decorate the todo item.
55
-
56
- const entityDisplay = entity.metadata.title ?? input.entityRef;
57
- title = `[${entityDisplay}] ${input.title}`;
58
- }
59
-
60
- const id = crypto.randomUUID();
61
- const createdBy = options.credentials.principal.userEntityRef;
62
- const newTodo = {
63
- title,
64
- id,
65
- createdBy,
66
- createdAt: new Date().toISOString(),
67
- };
68
-
69
- storedTodos.push(newTodo);
70
-
71
- // TEMPLATE NOTE:
72
- // The second argument of the logger methods can be used to pass
73
- // structured metadata. You can read more about the logger service here:
74
- // https://backstage.io/docs/backend-system/core-services/logger
75
- logger.info('Created new todo item', { id, title, createdBy });
76
-
77
- return newTodo;
78
- },
79
-
80
- async listTodos() {
81
- return { items: Array.from(storedTodos) };
82
- },
83
-
84
- async getTodo(request: { id: string }) {
85
- const todo = storedTodos.find(item => item.id === request.id);
86
- if (!todo) {
87
- throw new NotFoundError(`No todo found with id '${request.id}'`);
88
- }
89
- return todo;
90
- },
91
- };
92
- }
@@ -1 +0,0 @@
1
- export { createTodoListService } from './createTodoListService';
@@ -1,27 +0,0 @@
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
- }