@backstage/create-app 0.4.12 → 0.4.16

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,545 @@
1
1
  # @backstage/create-app
2
2
 
3
+ ## 0.4.16
4
+
5
+ ### Patch Changes
6
+
7
+ - c945cd9f7e: Adds missing `/catalog-graph` route to `<CatalogGraphPage/>`.
8
+
9
+ To fix this problem for a recently created app please update your `app/src/App.tsx`
10
+
11
+ ```diff
12
+ + import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
13
+
14
+ ... omitted ...
15
+
16
+ </Route>
17
+ <Route path="/settings" element={<UserSettingsPage />} />
18
+ + <Route path="/catalog-graph" element={<CatalogGraphPage />} />
19
+ </FlatRoutes>
20
+ ```
21
+
22
+ ## 0.4.15
23
+
24
+ ### Patch Changes
25
+
26
+ - 01b27d547c: Added three additional required properties to the search-backend `createRouter` function to support filtering search results based on permissions. To make this change to an existing app, add the required parameters to the `createRouter` call in `packages/backend/src/plugins/search.ts`:
27
+
28
+ ```diff
29
+ export default async function createPlugin({
30
+ logger,
31
+ + permissions,
32
+ discovery,
33
+ config,
34
+ tokenManager,
35
+ }: PluginEnvironment) {
36
+ /* ... */
37
+
38
+ return await createRouter({
39
+ engine: indexBuilder.getSearchEngine(),
40
+ + types: indexBuilder.getDocumentTypes(),
41
+ + permissions,
42
+ + config,
43
+ logger,
44
+ });
45
+ }
46
+ ```
47
+
48
+ - a0d446c8ec: Replaced EntitySystemDiagramCard with EntityCatalogGraphCard
49
+
50
+ To make this change to an existing app:
51
+
52
+ Add `@backstage/catalog-graph-plugin` as a `dependency` in `packages/app/package.json`
53
+
54
+ Apply the following changes to the `packages/app/src/components/catalog/EntityPage.tsx` file:
55
+
56
+ ```diff
57
+ + import {
58
+ + Direction,
59
+ + EntityCatalogGraphCard,
60
+ + } from '@backstage/plugin-catalog-graph';
61
+ + import {
62
+ + RELATION_API_CONSUMED_BY,
63
+ + RELATION_API_PROVIDED_BY,
64
+ + RELATION_CONSUMES_API,
65
+ + RELATION_DEPENDENCY_OF,
66
+ + RELATION_DEPENDS_ON,
67
+ + RELATION_HAS_PART,
68
+ + RELATION_PART_OF,
69
+ + RELATION_PROVIDES_API,
70
+ + } from '@backstage/catalog-model';
71
+ ```
72
+
73
+ ```diff
74
+ <EntityLayout.Route path="/diagram" title="Diagram">
75
+ - <EntitySystemDiagramCard />
76
+ + <EntityCatalogGraphCard
77
+ + variant="gridItem"
78
+ + direction={Direction.TOP_BOTTOM}
79
+ + title="System Diagram"
80
+ + height={700}
81
+ + relations={[
82
+ + RELATION_PART_OF,
83
+ + RELATION_HAS_PART,
84
+ + RELATION_API_CONSUMED_BY,
85
+ + RELATION_API_PROVIDED_BY,
86
+ + RELATION_CONSUMES_API,
87
+ + RELATION_PROVIDES_API,
88
+ + RELATION_DEPENDENCY_OF,
89
+ + RELATION_DEPENDS_ON,
90
+ + ]}
91
+ + unidirectional={false}
92
+ + />
93
+ </EntityLayout.Route>
94
+ ```
95
+
96
+ ```diff
97
+ const cicdContent = (
98
+ <Grid item md={6}>
99
+ <EntityAboutCard variant="gridItem" />
100
+ </Grid>
101
+ + <Grid item md={6} xs={12}>
102
+ + <EntityCatalogGraphCard variant="gridItem" height={400} />
103
+ + </Grid>
104
+ ```
105
+
106
+ Add the above component in `overviewContent`, `apiPage` , `systemPage` and domainPage` as well.
107
+
108
+ - 4aca2a5307: An example instance of a `<SearchFilter.Select />` with asynchronously loaded values was added to the composed `SearchPage.tsx`, allowing searches bound to the `techdocs` type to be filtered by entity name.
109
+
110
+ This is an entirely optional change; if you wish to adopt it, you can make the following (or similar) changes to your search page layout:
111
+
112
+ ```diff
113
+ --- a/packages/app/src/components/search/SearchPage.tsx
114
+ +++ b/packages/app/src/components/search/SearchPage.tsx
115
+ @@ -2,6 +2,10 @@ import React from 'react';
116
+ import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core';
117
+
118
+ import { CatalogResultListItem } from '@backstage/plugin-catalog';
119
+ +import {
120
+ + catalogApiRef,
121
+ + CATALOG_FILTER_EXISTS,
122
+ +} from '@backstage/plugin-catalog-react';
123
+ import { DocsResultListItem } from '@backstage/plugin-techdocs';
124
+
125
+ import {
126
+ @@ -10,6 +14,7 @@ import {
127
+ SearchResult,
128
+ SearchType,
129
+ DefaultResultListItem,
130
+ + useSearch,
131
+ } from '@backstage/plugin-search';
132
+ import {
133
+ CatalogIcon,
134
+ @@ -18,6 +23,7 @@ import {
135
+ Header,
136
+ Page,
137
+ } from '@backstage/core-components';
138
+ +import { useApi } from '@backstage/core-plugin-api';
139
+
140
+ const useStyles = makeStyles((theme: Theme) => ({
141
+ bar: {
142
+ @@ -36,6 +42,8 @@ const useStyles = makeStyles((theme: Theme) => ({
143
+
144
+ const SearchPage = () => {
145
+ const classes = useStyles();
146
+ + const { types } = useSearch();
147
+ + const catalogApi = useApi(catalogApiRef);
148
+
149
+ return (
150
+ <Page themeId="home">
151
+ @@ -65,6 +73,27 @@ const SearchPage = () => {
152
+ ]}
153
+ />
154
+ <Paper className={classes.filters}>
155
+ + {types.includes('techdocs') && (
156
+ + <SearchFilter.Select
157
+ + className={classes.filter}
158
+ + label="Entity"
159
+ + name="name"
160
+ + values={async () => {
161
+ + // Return a list of entities which are documented.
162
+ + const { items } = await catalogApi.getEntities({
163
+ + fields: ['metadata.name'],
164
+ + filter: {
165
+ + 'metadata.annotations.backstage.io/techdocs-ref':
166
+ + CATALOG_FILTER_EXISTS,
167
+ + },
168
+ + });
169
+ +
170
+ + const names = items.map(entity => entity.metadata.name);
171
+ + names.sort();
172
+ + return names;
173
+ + }}
174
+ + />
175
+ + )}
176
+ <SearchFilter.Select
177
+ className={classes.filter}
178
+ name="kind"
179
+ ```
180
+
181
+ - 1dbe63ec39: A `label` prop was added to `<SearchFilter.* />` components in order to allow
182
+ user-friendly label strings (as well as the option to omit a label). In order
183
+ to maintain labels on your existing filters, add a `label` prop to them in your
184
+ `SearchPage.tsx`.
185
+
186
+ ```diff
187
+ --- a/packages/app/src/components/search/SearchPage.tsx
188
+ +++ b/packages/app/src/components/search/SearchPage.tsx
189
+ @@ -96,11 +96,13 @@ const SearchPage = () => {
190
+ )}
191
+ <SearchFilter.Select
192
+ className={classes.filter}
193
+ + label="Kind"
194
+ name="kind"
195
+ values={['Component', 'Template']}
196
+ />
197
+ <SearchFilter.Checkbox
198
+ className={classes.filter}
199
+ + label="Lifecycle"
200
+ name="lifecycle"
201
+ values={['experimental', 'production']}
202
+ />
203
+ ```
204
+
205
+ ## 0.4.14
206
+
207
+ ### Patch Changes
208
+
209
+ - d4941024bc: Rebind external route for catalog import plugin from `scaffolderPlugin.routes.root` to `catalogImportPlugin.routes.importPage`.
210
+
211
+ To make this change to an existing app, make the following change to `packages/app/src/App.tsx`
212
+
213
+ ```diff
214
+ const App = createApp({
215
+ ...
216
+ bindRoutes({ bind }) {
217
+ ...
218
+ bind(apiDocsPlugin.externalRoutes, {
219
+ - createComponent: scaffolderPlugin.routes.root,
220
+ + registerApi: catalogImportPlugin.routes.importPage,
221
+ });
222
+ ...
223
+ },
224
+ });
225
+ ```
226
+
227
+ - b5402d6d72: Migrated the app template to React 17.
228
+
229
+ To apply this change to an existing app, make sure you have updated to the latest version of `@backstage/cli`, and make the following change to `packages/app/package.json`:
230
+
231
+ ```diff
232
+ "history": "^5.0.0",
233
+ - "react": "^16.13.1",
234
+ - "react-dom": "^16.13.1",
235
+ + "react": "^17.0.2",
236
+ + "react-dom": "^17.0.2",
237
+ "react-router": "6.0.0-beta.0",
238
+ ```
239
+
240
+ Since we have recently moved over all `react` and `react-dom` dependencies to `peerDependencies` of all packages, and included React 17 in the version range, this should be all you need to do. If you end up with duplicate React installations, first make sure that all of your plugins are up-to-date, including ones for example from `@roadiehq`. If that doesn't work, you may need to fall back to adding [Yarn resolutions](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/) in the `package.json` of your project root:
241
+
242
+ ```diff
243
+ + "resolutions": {
244
+ + "react": "^17.0.2",
245
+ + "react-dom": "^17.0.2"
246
+ + },
247
+ ```
248
+
249
+ - 5e8d278f8e: Added an external route binding from the `org` plugin to the catalog index page.
250
+
251
+ This change is needed because `@backstage/plugin-org` now has a required external route that needs to be bound for the app to start.
252
+
253
+ To apply this change to an existing app, make the following change to `packages/app/src/App.tsx`:
254
+
255
+ ```diff
256
+ import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
257
+ +import { orgPlugin } from '@backstage/plugin-org';
258
+ import { SearchPage } from '@backstage/plugin-search';
259
+ ```
260
+
261
+ And further down within the `createApp` call:
262
+
263
+ ```diff
264
+ bind(scaffolderPlugin.externalRoutes, {
265
+ registerComponent: catalogImportPlugin.routes.importPage,
266
+ });
267
+ + bind(orgPlugin.externalRoutes, {
268
+ + catalogIndex: catalogPlugin.routes.catalogIndex,
269
+ + });
270
+ },
271
+ ```
272
+
273
+ - fb08e2f285: Updated the configuration of the `app-backend` plugin to enable the static asset store by passing on `database` from the plugin environment to `createRouter`.
274
+
275
+ To apply this change to an existing app, make the following change to `packages/backend/src/plugins/app.ts`:
276
+
277
+ ```diff
278
+ export default async function createPlugin({
279
+ logger,
280
+ config,
281
+ + database,
282
+ }: PluginEnvironment): Promise<Router> {
283
+ return await createRouter({
284
+ logger,
285
+ config,
286
+ + database,
287
+ appPackageName: 'app',
288
+ });
289
+ }
290
+ ```
291
+
292
+ - 7ba416be78: You can now add `SidebarGroup`s to the current `Sidebar`. This will not affect how the current sidebar is displayed, but allows a customization on how the `MobileSidebar` on smaller screens will look like. A `SidebarGroup` will be displayed with the given icon in the `MobileSidebar`.
293
+
294
+ A `SidebarGroup` can either link to an existing page (e.g. `/search` or `/settings`) or wrap components, which will be displayed in a full-screen overlay menu (e.g. `Menu`).
295
+
296
+ ```diff
297
+ <Sidebar>
298
+ <SidebarLogo />
299
+ + <SidebarGroup label="Search" icon={<SearchIcon />} to="/search">
300
+ <SidebarSearchModal />
301
+ + </SidebarGroup>
302
+ <SidebarDivider />
303
+ + <SidebarGroup label="Menu" icon={<MenuIcon />}>
304
+ <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
305
+ <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
306
+ <SidebarDivider />
307
+ <SidebarScrollWrapper>
308
+ <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
309
+ </SidebarScrollWrapper>
310
+ + </SidebarGroup>
311
+ <SidebarSpace />
312
+ <SidebarDivider />
313
+ + <SidebarGroup
314
+ + label="Settings"
315
+ + icon={<UserSettingsSignInAvatar />}
316
+ + to="/settings"
317
+ + >
318
+ <SidebarSettings />
319
+ + </SidebarGroup>
320
+ </Sidebar>
321
+ ```
322
+
323
+ Additionally, you can order the groups differently in the `MobileSidebar` than in the usual `Sidebar` simply by giving a group a priority. The groups will be displayed in descending order from left to right.
324
+
325
+ ```diff
326
+ <SidebarGroup
327
+ label="Settings"
328
+ icon={<UserSettingsSignInAvatar />}
329
+ to="/settings"
330
+ + priority={1}
331
+ >
332
+ <SidebarSettings />
333
+ </SidebarGroup>
334
+ ```
335
+
336
+ If you decide against adding `SidebarGroup`s to your `Sidebar` the `MobileSidebar` will contain one default menu item, which will open a full-screen overlay menu displaying all the content of the current `Sidebar`.
337
+
338
+ More information on the `SidebarGroup` & the `MobileSidebar` component can be found in the changeset for the `core-components`.
339
+
340
+ - 08fa6a604a: The app template has been updated to add an explicit dependency on `typescript` in the root `package.json`. This is because it was removed as a dependency of `@backstage/cli` in order to decouple the TypeScript versioning in Backstage projects.
341
+
342
+ To apply this change in an existing app, add a `typescript` dependency to your `package.json` in the project root:
343
+
344
+ ```json
345
+ "dependencies": {
346
+ ...
347
+ "typescript": "~4.5.4",
348
+ }
349
+ ```
350
+
351
+ We recommend using a `~` version range since TypeScript releases do not adhere to semver.
352
+
353
+ It may be the case that you end up with errors if you upgrade the TypeScript version. This is because there was a change to TypeScript not long ago that defaulted the type of errors caught in `catch` blocks to `unknown`. You can work around this by adding `"useUnknownInCatchVariables": false` to the `"compilerOptions"` in your `tsconfig.json`:
354
+
355
+ ```json
356
+ "compilerOptions": {
357
+ ...
358
+ "useUnknownInCatchVariables": false
359
+ }
360
+ ```
361
+
362
+ Another option is to use the utilities from `@backstage/errors` to assert the type of errors caught in `catch` blocks:
363
+
364
+ ```ts
365
+ import { assertError, isError } from '@backstage/errors';
366
+
367
+ try {
368
+ ...
369
+ } catch (error) {
370
+ assertError(error);
371
+ ...
372
+ // OR
373
+ if (isError(error)) {
374
+ ...
375
+ }
376
+ }
377
+ ```
378
+
379
+ Yet another issue you might run into when upgrading TypeScript is incompatibilities in the types from `react-use`. The error you would run into looks something like this:
380
+
381
+ ```plain
382
+ node_modules/react-use/lib/usePermission.d.ts:1:54 - error TS2304: Cannot find name 'DevicePermissionDescriptor'.
383
+
384
+ 1 declare type PermissionDesc = PermissionDescriptor | DevicePermissionDescriptor | MidiPermissionDescriptor | PushPermissionDescriptor;
385
+ ```
386
+
387
+ If you encounter this error, the simplest fix is to replace full imports of `react-use` with more specific ones. For example, the following:
388
+
389
+ ```ts
390
+ import { useAsync } from 'react-use';
391
+ ```
392
+
393
+ Would be converted into this:
394
+
395
+ ```ts
396
+ import useAsync from 'react-use/lib/useAsync';
397
+ ```
398
+
399
+ ## 0.4.13
400
+
401
+ ### Patch Changes
402
+
403
+ - fb08e2f285: Updated the configuration of the `app-backend` plugin to enable the static asset store by passing on `database` from the plugin environment to `createRouter`.
404
+
405
+ To apply this change to an existing app, make the following change to `packages/backend/src/plugins/app.ts`:
406
+
407
+ ```diff
408
+ export default async function createPlugin({
409
+ logger,
410
+ config,
411
+ + database,
412
+ }: PluginEnvironment): Promise<Router> {
413
+ return await createRouter({
414
+ logger,
415
+ config,
416
+ + database,
417
+ appPackageName: 'app',
418
+ });
419
+ }
420
+ ```
421
+
422
+ - 7ba416be78: You can now add `SidebarGroup`s to the current `Sidebar`. This will not affect how the current sidebar is displayed, but allows a customization on how the `MobileSidebar` on smaller screens will look like. A `SidebarGroup` will be displayed with the given icon in the `MobileSidebar`.
423
+
424
+ A `SidebarGroup` can either link to an existing page (e.g. `/search` or `/settings`) or wrap components, which will be displayed in a full-screen overlay menu (e.g. `Menu`).
425
+
426
+ ```diff
427
+ <Sidebar>
428
+ <SidebarLogo />
429
+ + <SidebarGroup label="Search" icon={<SearchIcon />} to="/search">
430
+ <SidebarSearchModal />
431
+ + </SidebarGroup>
432
+ <SidebarDivider />
433
+ + <SidebarGroup label="Menu" icon={<MenuIcon />}>
434
+ <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
435
+ <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
436
+ <SidebarDivider />
437
+ <SidebarScrollWrapper>
438
+ <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
439
+ </SidebarScrollWrapper>
440
+ + </SidebarGroup>
441
+ <SidebarSpace />
442
+ <SidebarDivider />
443
+ + <SidebarGroup
444
+ + label="Settings"
445
+ + icon={<UserSettingsSignInAvatar />}
446
+ + to="/settings"
447
+ + >
448
+ <SidebarSettings />
449
+ + </SidebarGroup>
450
+ </Sidebar>
451
+ ```
452
+
453
+ Additionally, you can order the groups differently in the `MobileSidebar` than in the usual `Sidebar` simply by giving a group a priority. The groups will be displayed in descending order from left to right.
454
+
455
+ ```diff
456
+ <SidebarGroup
457
+ label="Settings"
458
+ icon={<UserSettingsSignInAvatar />}
459
+ to="/settings"
460
+ + priority={1}
461
+ >
462
+ <SidebarSettings />
463
+ </SidebarGroup>
464
+ ```
465
+
466
+ If you decide against adding `SidebarGroup`s to your `Sidebar` the `MobileSidebar` will contain one default menu item, which will open a full-screen overlay menu displaying all the content of the current `Sidebar`.
467
+
468
+ More information on the `SidebarGroup` & the `MobileSidebar` component can be found in the changeset for the `core-components`.
469
+
470
+ - 08fa6a604a: The app template has been updated to add an explicit dependency on `typescript` in the root `package.json`. This is because it was removed as a dependency of `@backstage/cli` in order to decouple the TypeScript versioning in Backstage projects.
471
+
472
+ To apply this change in an existing app, add a `typescript` dependency to your `package.json` in the project root:
473
+
474
+ ```json
475
+ "dependencies": {
476
+ ...
477
+ "typescript": "~4.5.4",
478
+ }
479
+ ```
480
+
481
+ We recommend using a `~` version range since TypeScript releases do not adhere to semver.
482
+
483
+ It may be the case that you end up with errors if you upgrade the TypeScript version. This is because there was a change to TypeScript not long ago that defaulted the type of errors caught in `catch` blocks to `unknown`. You can work around this by adding `"useUnknownInCatchVariables": false` to the `"compilerOptions"` in your `tsconfig.json`:
484
+
485
+ ```json
486
+ "compilerOptions": {
487
+ ...
488
+ "useUnknownInCatchVariables": false
489
+ }
490
+ ```
491
+
492
+ Another option is to use the utilities from `@backstage/errors` to assert the type of errors caught in `catch` blocks:
493
+
494
+ ```ts
495
+ import { assertError, isError } from '@backstage/errors';
496
+
497
+ try {
498
+ ...
499
+ } catch (error) {
500
+ assertError(error);
501
+ ...
502
+ // OR
503
+ if (isError(error)) {
504
+ ...
505
+ }
506
+ }
507
+ ```
508
+
509
+ - Updated dependencies
510
+ - @backstage/plugin-tech-radar@0.5.3-next.0
511
+ - @backstage/plugin-auth-backend@0.7.0-next.0
512
+ - @backstage/core-components@0.8.5-next.0
513
+ - @backstage/plugin-api-docs@0.6.23-next.0
514
+ - @backstage/plugin-catalog-backend@0.21.0-next.0
515
+ - @backstage/plugin-permission-common@0.4.0-next.0
516
+ - @backstage/cli@0.12.0-next.0
517
+ - @backstage/core-plugin-api@0.6.0-next.0
518
+ - @backstage/plugin-catalog@0.7.9-next.0
519
+ - @backstage/plugin-user-settings@0.3.17-next.0
520
+ - @backstage/backend-common@0.10.4-next.0
521
+ - @backstage/config@0.1.13-next.0
522
+ - @backstage/plugin-app-backend@0.3.22-next.0
523
+ - @backstage/core-app-api@0.5.0-next.0
524
+ - @backstage/plugin-catalog-import@0.7.10-next.0
525
+ - @backstage/plugin-scaffolder@0.11.19-next.0
526
+ - @backstage/plugin-search@0.5.6-next.0
527
+ - @backstage/plugin-techdocs@0.12.15-next.0
528
+ - @backstage/plugin-permission-node@0.4.0-next.0
529
+ - @backstage/catalog-model@0.9.10-next.0
530
+ - @backstage/integration-react@0.1.19-next.0
531
+ - @backstage/plugin-explore@0.3.26-next.0
532
+ - @backstage/plugin-github-actions@0.4.32-next.0
533
+ - @backstage/plugin-lighthouse@0.2.35-next.0
534
+ - @backstage/plugin-scaffolder-backend@0.15.21-next.0
535
+ - @backstage/backend-tasks@0.1.4-next.0
536
+ - @backstage/catalog-client@0.5.5-next.0
537
+ - @backstage/test-utils@0.2.3-next.0
538
+ - @backstage/plugin-proxy-backend@0.2.16-next.0
539
+ - @backstage/plugin-rollbar-backend@0.1.19-next.0
540
+ - @backstage/plugin-search-backend@0.3.1-next.0
541
+ - @backstage/plugin-techdocs-backend@0.12.4-next.0
542
+
3
543
  ## 0.4.12
4
544
 
5
545
  ### Patch Changes
@@ -16,7 +556,7 @@
16
556
 
17
557
  function makeCreateEnv(config: Config) {
18
558
  ...
19
- + const permissions = ServerPerimssionClient.fromConfig(config, {
559
+ + const permissions = ServerPermissionClient.fromConfig(config, {
20
560
  + discovery,
21
561
  + tokenManager,
22
562
  + });
package/dist/index.cjs.js CHANGED
@@ -55,105 +55,108 @@ ${chalk__default["default"].red(`${error}`)}
55
55
  }
56
56
  }
57
57
 
58
- var version$D = "0.4.12";
58
+ var version$E = "0.4.16";
59
59
 
60
- var version$C = "0.1.4";
60
+ var version$D = "0.1.5";
61
61
 
62
- var version$B = "0.10.3";
62
+ var version$C = "0.10.5";
63
63
 
64
- var version$A = "0.1.3";
64
+ var version$B = "0.1.4";
65
65
 
66
- var version$z = "0.5.4";
66
+ var version$A = "0.5.5";
67
67
 
68
- var version$y = "0.9.9";
68
+ var version$z = "0.9.10";
69
69
 
70
- var version$x = "0.11.0";
70
+ var version$y = "0.13.0";
71
71
 
72
- var version$w = "0.1.12";
72
+ var version$x = "0.1.13";
73
73
 
74
- var version$v = "0.4.0";
74
+ var version$w = "0.5.1";
75
75
 
76
- var version$u = "0.8.4";
76
+ var version$v = "0.8.6";
77
77
 
78
- var version$t = "0.5.0";
78
+ var version$u = "0.6.0";
79
79
 
80
- var version$s = "0.2.0";
80
+ var version$t = "0.2.0";
81
81
 
82
- var version$r = "0.1.18";
82
+ var version$s = "0.1.19";
83
83
 
84
- var version$q = "0.2.2";
84
+ var version$r = "0.2.3";
85
85
 
86
- var version$p = "0.2.14";
86
+ var version$q = "0.2.14";
87
87
 
88
- var version$o = "0.6.22";
88
+ var version$p = "0.7.0";
89
89
 
90
- var version$n = "0.3.21";
90
+ var version$o = "0.3.22";
91
91
 
92
- var version$m = "0.6.2";
92
+ var version$n = "0.8.0";
93
93
 
94
- var version$l = "0.7.8";
94
+ var version$m = "0.7.10";
95
95
 
96
- var version$k = "0.6.11";
96
+ var version$l = "0.6.12";
97
97
 
98
- var version$j = "0.20.0";
98
+ var version$k = "0.21.1";
99
99
 
100
- var version$i = "0.7.9";
100
+ var version$j = "0.2.8";
101
101
 
102
- var version$h = "0.2.34";
102
+ var version$i = "0.7.10";
103
103
 
104
- var version$g = "0.3.25";
104
+ var version$h = "0.2.35";
105
105
 
106
- var version$f = "0.4.31";
106
+ var version$g = "0.3.27";
107
107
 
108
- var version$e = "0.2.34";
108
+ var version$f = "0.4.33";
109
109
 
110
- var version$d = "0.3.34";
110
+ var version$e = "0.2.35";
111
111
 
112
- var version$c = "0.3.1";
112
+ var version$d = "0.4.0";
113
113
 
114
- var version$b = "0.3.0";
114
+ var version$c = "0.4.0";
115
115
 
116
- var version$a = "0.2.15";
116
+ var version$b = "0.4.1";
117
117
 
118
- var version$9 = "0.1.18";
118
+ var version$a = "0.2.16";
119
119
 
120
- var version$8 = "0.11.18";
120
+ var version$9 = "0.1.19";
121
121
 
122
- var version$7 = "0.15.20";
122
+ var version$8 = "0.12.0";
123
123
 
124
- var version$6 = "0.5.5";
124
+ var version$7 = "0.15.22";
125
125
 
126
- var version$5 = "0.3.0";
126
+ var version$6 = "0.6.0";
127
127
 
128
- var version$4 = "0.4.4";
128
+ var version$5 = "0.4.0";
129
129
 
130
- var version$3 = "0.5.2";
130
+ var version$4 = "0.4.5";
131
131
 
132
- var version$2 = "0.12.14";
132
+ var version$3 = "0.5.3";
133
133
 
134
- var version$1 = "0.12.3";
134
+ var version$2 = "0.13.1";
135
135
 
136
- var version = "0.3.16";
136
+ var version$1 = "0.13.1";
137
+
138
+ var version = "0.3.17";
137
139
 
138
140
  const packageVersions = {
139
- "@backstage/app-defaults": version$C,
140
- "@backstage/backend-common": version$B,
141
- "@backstage/backend-tasks": version$A,
142
- "@backstage/catalog-client": version$z,
143
- "@backstage/catalog-model": version$y,
144
- "@backstage/cli": version$x,
145
- "@backstage/config": version$w,
146
- "@backstage/core-app-api": version$v,
147
- "@backstage/core-components": version$u,
148
- "@backstage/core-plugin-api": version$t,
149
- "@backstage/errors": version$s,
150
- "@backstage/integration-react": version$r,
151
- "@backstage/plugin-api-docs": version$o,
152
- "@backstage/plugin-app-backend": version$n,
153
- "@backstage/plugin-auth-backend": version$m,
154
- "@backstage/plugin-catalog": version$l,
155
- "@backstage/plugin-catalog-react": version$k,
156
- "@backstage/plugin-catalog-backend": version$j,
141
+ "@backstage/app-defaults": version$D,
142
+ "@backstage/backend-common": version$C,
143
+ "@backstage/backend-tasks": version$B,
144
+ "@backstage/catalog-client": version$A,
145
+ "@backstage/catalog-model": version$z,
146
+ "@backstage/cli": version$y,
147
+ "@backstage/config": version$x,
148
+ "@backstage/core-app-api": version$w,
149
+ "@backstage/core-components": version$v,
150
+ "@backstage/core-plugin-api": version$u,
151
+ "@backstage/errors": version$t,
152
+ "@backstage/integration-react": version$s,
153
+ "@backstage/plugin-api-docs": version$p,
154
+ "@backstage/plugin-app-backend": version$o,
155
+ "@backstage/plugin-auth-backend": version$n,
156
+ "@backstage/plugin-catalog": version$m,
157
+ "@backstage/plugin-catalog-react": version$l,
158
+ "@backstage/plugin-catalog-backend": version$k,
159
+ "@backstage/plugin-catalog-graph": version$j,
157
160
  "@backstage/plugin-catalog-import": version$i,
158
161
  "@backstage/plugin-circleci": version$h,
159
162
  "@backstage/plugin-explore": version$g,
@@ -173,8 +176,8 @@ const packageVersions = {
173
176
  "@backstage/plugin-techdocs": version$2,
174
177
  "@backstage/plugin-techdocs-backend": version$1,
175
178
  "@backstage/plugin-user-settings": version,
176
- "@backstage/test-utils": version$q,
177
- "@backstage/theme": version$p
179
+ "@backstage/test-utils": version$r,
180
+ "@backstage/theme": version$q
178
181
  };
179
182
 
180
183
  const TASK_NAME_MAX_LENGTH = 14;
@@ -309,7 +312,7 @@ async function moveAppTask(tempDir, destination, id) {
309
312
 
310
313
  var createApp = async (cmd, version) => {
311
314
  const paths = cliCommon.findPaths(__dirname);
312
- const questions = [
315
+ const answers = await inquirer__default["default"].prompt([
313
316
  {
314
317
  type: "input",
315
318
  name: "name",
@@ -329,8 +332,7 @@ var createApp = async (cmd, version) => {
329
332
  message: chalk__default["default"].blue("Select database for the backend [required]"),
330
333
  choices: ["SQLite", "PostgreSQL"]
331
334
  }
332
- ];
333
- const answers = await inquirer__default["default"].prompt(questions);
335
+ ]);
334
336
  answers.dbTypePG = answers.dbType === "PostgreSQL";
335
337
  answers.dbTypeSqlite = answers.dbType === "SQLite";
336
338
  const templateDir = paths.resolveOwn("templates/default-app");
@@ -376,7 +378,7 @@ var createApp = async (cmd, version) => {
376
378
  };
377
379
 
378
380
  const main = (argv) => {
379
- program__default["default"].name("backstage-create-app").version(version$D).description("Creates a new app in a new directory or specified path").option("--path [directory]", "Location to store the app defaulting to a new folder with the app name").option("--skip-install", "Skip the install and builds steps after creating the app").action((cmd) => createApp(cmd, version$D));
381
+ program__default["default"].name("backstage-create-app").version(version$E).description("Creates a new app in a new directory or specified path").option("--path [directory]", "Location to store the app defaulting to a new folder with the app name").option("--skip-install", "Skip the install and builds steps after creating the app").action((cmd) => createApp(cmd, version$E));
380
382
  program__default["default"].parse(argv);
381
383
  };
382
384
  process.on("unhandledRejection", (rejection) => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/lib/errors.ts","../src/lib/versions.ts","../src/lib/tasks.ts","../src/createApp.ts","../src/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chalk from 'chalk';\n\nexport class CustomError extends Error {\n get name(): string {\n return this.constructor.name;\n }\n}\n\nexport class ExitCodeError extends CustomError {\n readonly code: number;\n\n constructor(code: number, command?: string) {\n if (command) {\n super(`Command '${command}' exited with code ${code}`);\n } else {\n super(`Child exited with code ${code}`);\n }\n this.code = code;\n }\n}\n\nexport function exitWithError(error: Error): never {\n if (error instanceof ExitCodeError) {\n process.stderr.write(`\\n${chalk.red(error.message)}\\n\\n`);\n process.exit(error.code);\n } else {\n process.stderr.write(`\\n${chalk.red(`${error}`)}\\n\\n`);\n process.exit(1);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* eslint-disable monorepo/no-relative-import */\n\n/*\nThis is a list of all packages used by the template. If dependencies are added or removed,\nthis list should be updated as well.\n\nThe list, and the accompanying peerDependencies entries, are here to ensure correct versioning\nand bumping of this package. Without this list the version would not be bumped unless we\nmanually trigger a release.\n\nThis does not create an actual dependency on these packages and does not bring in any code.\nRelative imports are used rather than package imports to make sure the packages aren't externalized.\nRollup will extract the value of the version field in each package at build time without\nleaving any imports in place.\n*/\n\nimport { version as appDefaults } from '../../../app-defaults/package.json';\nimport { version as backendCommon } from '../../../backend-common/package.json';\nimport { version as backendTasks } from '../../../backend-tasks/package.json';\nimport { version as catalogClient } from '../../../catalog-client/package.json';\nimport { version as catalogModel } from '../../../catalog-model/package.json';\nimport { version as cli } from '../../../cli/package.json';\nimport { version as config } from '../../../config/package.json';\nimport { version as coreAppApi } from '../../../core-app-api/package.json';\nimport { version as coreComponents } from '../../../core-components/package.json';\nimport { version as corePluginApi } from '../../../core-plugin-api/package.json';\nimport { version as errors } from '../../../errors/package.json';\nimport { version as integrationReact } from '../../../integration-react/package.json';\nimport { version as testUtils } from '../../../test-utils/package.json';\nimport { version as theme } from '../../../theme/package.json';\n\nimport { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json';\nimport { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json';\nimport { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json';\nimport { version as pluginCatalog } from '../../../../plugins/catalog/package.json';\nimport { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';\nimport { version as pluginCatalogBackend } from '../../../../plugins/catalog-backend/package.json';\nimport { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json';\nimport { version as pluginCircleci } from '../../../../plugins/circleci/package.json';\nimport { version as pluginExplore } from '../../../../plugins/explore/package.json';\nimport { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json';\nimport { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json';\nimport { version as pluginOrg } from '../../../../plugins/org/package.json';\nimport { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';\nimport { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';\nimport { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';\nimport { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json';\nimport { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';\nimport { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json';\nimport { version as pluginSearch } from '../../../../plugins/search/package.json';\nimport { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json';\nimport { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';\nimport { version as pluginTechRadar } from '../../../../plugins/tech-radar/package.json';\nimport { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';\nimport { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json';\nimport { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json';\n\nexport const packageVersions = {\n '@backstage/app-defaults': appDefaults,\n '@backstage/backend-common': backendCommon,\n '@backstage/backend-tasks': backendTasks,\n '@backstage/catalog-client': catalogClient,\n '@backstage/catalog-model': catalogModel,\n '@backstage/cli': cli,\n '@backstage/config': config,\n '@backstage/core-app-api': coreAppApi,\n '@backstage/core-components': coreComponents,\n '@backstage/core-plugin-api': corePluginApi,\n '@backstage/errors': errors,\n '@backstage/integration-react': integrationReact,\n '@backstage/plugin-api-docs': pluginApiDocs,\n '@backstage/plugin-app-backend': pluginAppBackend,\n '@backstage/plugin-auth-backend': pluginAuthBackend,\n '@backstage/plugin-catalog': pluginCatalog,\n '@backstage/plugin-catalog-react': pluginCatalogReact,\n '@backstage/plugin-catalog-backend': pluginCatalogBackend,\n '@backstage/plugin-catalog-import': pluginCatalogImport,\n '@backstage/plugin-circleci': pluginCircleci,\n '@backstage/plugin-explore': pluginExplore,\n '@backstage/plugin-github-actions': pluginGithubActions,\n '@backstage/plugin-lighthouse': pluginLighthouse,\n '@backstage/plugin-org': pluginOrg,\n '@backstage/plugin-permission-common': pluginPermissionCommon,\n '@backstage/plugin-permission-node': pluginPermissionNode,\n '@backstage/plugin-proxy-backend': pluginProxyBackend,\n '@backstage/plugin-rollbar-backend': pluginRollbarBackend,\n '@backstage/plugin-scaffolder': pluginScaffolder,\n '@backstage/plugin-scaffolder-backend': pluginScaffolderBackend,\n '@backstage/plugin-search': pluginSearch,\n '@backstage/plugin-search-backend': pluginSearchBackend,\n '@backstage/plugin-search-backend-node': pluginSearchBackendNode,\n '@backstage/plugin-tech-radar': pluginTechRadar,\n '@backstage/plugin-techdocs': pluginTechdocs,\n '@backstage/plugin-techdocs-backend': pluginTechdocsBackend,\n '@backstage/plugin-user-settings': pluginUserSettings,\n '@backstage/test-utils': testUtils,\n '@backstage/theme': theme,\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BACKSTAGE_JSON } from '@backstage/cli-common';\nimport chalk from 'chalk';\nimport fs from 'fs-extra';\nimport handlebars from 'handlebars';\nimport ora from 'ora';\nimport recursive from 'recursive-readdir';\nimport {\n basename,\n dirname,\n join,\n resolve as resolvePath,\n relative as relativePath,\n} from 'path';\nimport { exec as execCb } from 'child_process';\nimport { packageVersions } from './versions';\nimport { promisify } from 'util';\n\nconst TASK_NAME_MAX_LENGTH = 14;\nconst exec = promisify(execCb);\n\nexport class Task {\n static log(name: string = '') {\n process.stdout.write(`${chalk.green(name)}\\n`);\n }\n\n static error(message: string = '') {\n process.stdout.write(`\\n${chalk.red(message)}\\n\\n`);\n }\n\n static section(name: string) {\n const title = chalk.green(`${name}:`);\n process.stdout.write(`\\n ${title}\\n`);\n }\n\n static exit(code: number = 0) {\n process.exit(code);\n }\n\n static async forItem(\n task: string,\n item: string,\n taskFunc: () => Promise<void>,\n ): Promise<void> {\n const paddedTask = chalk.green(task.padEnd(TASK_NAME_MAX_LENGTH));\n\n const spinner = ora({\n prefixText: chalk.green(` ${paddedTask}${chalk.cyan(item)}`),\n spinner: 'arc',\n color: 'green',\n }).start();\n\n try {\n await taskFunc();\n spinner.succeed();\n } catch (error) {\n spinner.fail();\n throw error;\n }\n }\n}\n\n/**\n * Generate a templated backstage project\n *\n * @param templateDir - location containing template files\n * @param destinationDir - location to save templated project\n * @param context - template parameters\n */\nexport async function templatingTask(\n templateDir: string,\n destinationDir: string,\n context: any,\n version: string,\n) {\n const files = await recursive(templateDir).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n\n for (const file of files) {\n const destinationFile = resolvePath(\n destinationDir,\n relativePath(templateDir, file),\n );\n await fs.ensureDir(dirname(destinationFile));\n\n if (file.endsWith('.hbs')) {\n await Task.forItem('templating', basename(file), async () => {\n const destination = destinationFile.replace(/\\.hbs$/, '');\n\n const template = await fs.readFile(file);\n const compiled = handlebars.compile(template.toString());\n const contents = compiled(\n { name: basename(destination), ...context },\n {\n helpers: {\n version(name: keyof typeof packageVersions) {\n if (name in packageVersions) {\n return packageVersions[name];\n }\n throw new Error(`No version available for package ${name}`);\n },\n },\n },\n );\n\n await fs.writeFile(destination, contents).catch(error => {\n throw new Error(\n `Failed to create file: ${destination}: ${error.message}`,\n );\n });\n });\n } else {\n await Task.forItem('copying', basename(file), async () => {\n await fs.copyFile(file, destinationFile).catch(error => {\n const destination = destinationFile;\n throw new Error(\n `Failed to copy file to ${destination} : ${error.message}`,\n );\n });\n });\n }\n }\n await Task.forItem('creating', BACKSTAGE_JSON, () =>\n fs.writeFile(\n join(destinationDir, BACKSTAGE_JSON),\n `{\\n \"version\": ${JSON.stringify(version)}\\n}\\n`,\n ),\n );\n}\n\n/**\n * Verify that application target does not already exist\n *\n * @param rootDir - The directory to create application folder `name`\n * @param name - The specified name of the application\n * @Throws Error - If directory with name of `destination` already exists\n */\nexport async function checkAppExistsTask(rootDir: string, name: string) {\n await Task.forItem('checking', name, async () => {\n const destination = resolvePath(rootDir, name);\n\n if (await fs.pathExists(destination)) {\n const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));\n throw new Error(\n `A directory with the same name already exists: ${existing}\\nPlease try again with a different app name`,\n );\n }\n });\n}\n\n/**\n * Verify that application `path` exists, otherwise create the directory\n *\n * @param path - target to create directory\n * @throws if `path` is a file, or `fs.mkdir` fails\n */\nexport async function checkPathExistsTask(path: string) {\n await Task.forItem('checking', path, async () => {\n try {\n await fs.mkdirs(path);\n } catch (error) {\n // will fail if a file already exists at given `path`\n throw new Error(`Failed to create app directory: ${error.message}`);\n }\n });\n}\n\n/**\n * Create a folder to store templated files\n *\n * @param tempDir - target temporary directory\n * @throws if `fs.mkdir` fails\n */\nexport async function createTemporaryAppFolderTask(tempDir: string) {\n await Task.forItem('creating', 'temporary directory', async () => {\n try {\n await fs.mkdir(tempDir);\n } catch (error) {\n throw new Error(`Failed to create temporary app directory, ${error}`);\n }\n });\n}\n\n/**\n * Run `yarn install` and `run tsc` in application directory\n *\n * @param appDir - location of application to build\n */\nexport async function buildAppTask(appDir: string) {\n const runCmd = async (cmd: string) => {\n await Task.forItem('executing', cmd, async () => {\n process.chdir(appDir);\n await exec(cmd).catch(error => {\n process.stdout.write(error.stderr);\n process.stdout.write(error.stdout);\n throw new Error(`Could not execute command ${chalk.cyan(cmd)}`);\n });\n });\n };\n\n await runCmd('yarn install');\n await runCmd('yarn tsc');\n}\n\n/**\n * Move temporary directory to destination application folder\n *\n * @param tempDir - source path to copy files from\n * @param destination - target path to copy files\n * @param id - item ID\n * @throws if `fs.move` fails\n */\nexport async function moveAppTask(\n tempDir: string,\n destination: string,\n id: string,\n) {\n await Task.forItem('moving', id, async () => {\n await fs\n .move(tempDir, destination)\n .catch(error => {\n throw new Error(\n `Failed to move app from ${tempDir} to ${destination}: ${error.message}`,\n );\n })\n .finally(() => {\n // remove temporary files on both success and failure\n fs.removeSync(tempDir);\n });\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport inquirer, { Answers, Question } from 'inquirer';\nimport { resolve as resolvePath } from 'path';\nimport { findPaths } from '@backstage/cli-common';\nimport os from 'os';\nimport {\n Task,\n buildAppTask,\n checkAppExistsTask,\n checkPathExistsTask,\n createTemporaryAppFolderTask,\n moveAppTask,\n templatingTask,\n} from './lib/tasks';\n\nexport default async (cmd: Command, version: string): Promise<void> => {\n /* eslint-disable-next-line no-restricted-syntax */\n const paths = findPaths(__dirname);\n\n const questions: Question[] = [\n {\n type: 'input',\n name: 'name',\n message: chalk.blue('Enter a name for the app [required]'),\n validate: (value: any) => {\n if (!value) {\n return chalk.red('Please enter a name for the app');\n } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {\n return chalk.red(\n 'App name must be lowercase and contain only letters, digits, and dashes.',\n );\n }\n return true;\n },\n },\n {\n type: 'list',\n name: 'dbType',\n message: chalk.blue('Select database for the backend [required]'),\n // @ts-ignore\n choices: ['SQLite', 'PostgreSQL'],\n },\n ];\n const answers: Answers = await inquirer.prompt(questions);\n answers.dbTypePG = answers.dbType === 'PostgreSQL';\n answers.dbTypeSqlite = answers.dbType === 'SQLite';\n\n const templateDir = paths.resolveOwn('templates/default-app');\n const tempDir = resolvePath(os.tmpdir(), answers.name);\n\n // Use `--path` argument as application directory when specified, otherwise\n // create a directory using `answers.name`\n const appDir = cmd.path\n ? resolvePath(paths.targetDir, cmd.path)\n : resolvePath(paths.targetDir, answers.name);\n\n Task.log();\n Task.log('Creating the app...');\n\n try {\n if (cmd.path) {\n // Template directly to specified path\n\n Task.section('Checking that supplied path exists');\n await checkPathExistsTask(appDir);\n\n Task.section('Preparing files');\n await templatingTask(templateDir, cmd.path, answers, version);\n } else {\n // Template to temporary location, and then move files\n\n Task.section('Checking if the directory is available');\n await checkAppExistsTask(paths.targetDir, answers.name);\n\n Task.section('Creating a temporary app directory');\n await createTemporaryAppFolderTask(tempDir);\n\n Task.section('Preparing files');\n await templatingTask(templateDir, tempDir, answers, version);\n\n Task.section('Moving to final location');\n await moveAppTask(tempDir, appDir, answers.name);\n }\n\n if (!cmd.skipInstall) {\n Task.section('Building the app');\n await buildAppTask(appDir);\n }\n\n Task.log();\n Task.log(\n chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),\n );\n Task.log();\n Task.section('All set! Now you might want to');\n Task.log(` Run the app: ${chalk.cyan(`cd ${answers.name} && yarn dev`)}`);\n Task.log(\n ' Set up the software catalog: https://backstage.io/docs/features/software-catalog/configuration',\n );\n Task.log(' Add authentication: https://backstage.io/docs/auth/');\n Task.log();\n Task.exit();\n } catch (error) {\n Task.error(String(error));\n\n Task.log('It seems that something went wrong when creating the app 🤔');\n\n Task.error('🔥 Failed to create app!');\n Task.exit(1);\n }\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A CLI that helps you create your own Backstage app\n *\n * @packageDocumentation\n */\n\nimport program from 'commander';\nimport { exitWithError } from './lib/errors';\nimport { version } from '../package.json';\nimport createApp from './createApp';\n\nconst main = (argv: string[]) => {\n program\n .name('backstage-create-app')\n .version(version)\n .description('Creates a new app in a new directory or specified path')\n .option(\n '--path [directory]',\n 'Location to store the app defaulting to a new folder with the app name',\n )\n .option(\n '--skip-install',\n 'Skip the install and builds steps after creating the app',\n )\n .action(cmd => createApp(cmd, version));\n\n program.parse(argv);\n};\n\nprocess.on('unhandledRejection', rejection => {\n if (rejection instanceof Error) {\n exitWithError(rejection);\n } else {\n exitWithError(new Error(`Unknown rejection: '${rejection}'`));\n }\n});\n\nmain(process.argv);\n"],"names":["chalk","appDefaults","backendCommon","backendTasks","catalogClient","catalogModel","cli","config","coreAppApi","coreComponents","corePluginApi","errors","integrationReact","pluginApiDocs","pluginAppBackend","pluginAuthBackend","pluginCatalog","pluginCatalogReact","pluginCatalogBackend","pluginCatalogImport","pluginCircleci","pluginExplore","pluginGithubActions","pluginLighthouse","pluginOrg","pluginPermissionCommon","pluginPermissionNode","pluginProxyBackend","pluginRollbarBackend","pluginScaffolder","pluginScaffolderBackend","pluginSearch","pluginSearchBackend","pluginSearchBackendNode","pluginTechRadar","pluginTechdocs","pluginTechdocsBackend","pluginUserSettings","testUtils","theme","promisify","execCb","ora","recursive","resolvePath","relativePath","fs","dirname","basename","handlebars","BACKSTAGE_JSON","join","findPaths","inquirer","os","version"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/lib/errors.ts","../src/lib/versions.ts","../src/lib/tasks.ts","../src/createApp.ts","../src/index.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chalk from 'chalk';\n\nexport class CustomError extends Error {\n get name(): string {\n return this.constructor.name;\n }\n}\n\nexport class ExitCodeError extends CustomError {\n readonly code: number;\n\n constructor(code: number, command?: string) {\n if (command) {\n super(`Command '${command}' exited with code ${code}`);\n } else {\n super(`Child exited with code ${code}`);\n }\n this.code = code;\n }\n}\n\nexport function exitWithError(error: Error): never {\n if (error instanceof ExitCodeError) {\n process.stderr.write(`\\n${chalk.red(error.message)}\\n\\n`);\n process.exit(error.code);\n } else {\n process.stderr.write(`\\n${chalk.red(`${error}`)}\\n\\n`);\n process.exit(1);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* eslint-disable monorepo/no-relative-import */\n\n/*\nThis is a list of all packages used by the template. If dependencies are added or removed,\nthis list should be updated as well.\n\nThe list, and the accompanying peerDependencies entries, are here to ensure correct versioning\nand bumping of this package. Without this list the version would not be bumped unless we\nmanually trigger a release.\n\nThis does not create an actual dependency on these packages and does not bring in any code.\nRelative imports are used rather than package imports to make sure the packages aren't externalized.\nRollup will extract the value of the version field in each package at build time without\nleaving any imports in place.\n*/\n\nimport { version as appDefaults } from '../../../app-defaults/package.json';\nimport { version as backendCommon } from '../../../backend-common/package.json';\nimport { version as backendTasks } from '../../../backend-tasks/package.json';\nimport { version as catalogClient } from '../../../catalog-client/package.json';\nimport { version as catalogModel } from '../../../catalog-model/package.json';\nimport { version as cli } from '../../../cli/package.json';\nimport { version as config } from '../../../config/package.json';\nimport { version as coreAppApi } from '../../../core-app-api/package.json';\nimport { version as coreComponents } from '../../../core-components/package.json';\nimport { version as corePluginApi } from '../../../core-plugin-api/package.json';\nimport { version as errors } from '../../../errors/package.json';\nimport { version as integrationReact } from '../../../integration-react/package.json';\nimport { version as testUtils } from '../../../test-utils/package.json';\nimport { version as theme } from '../../../theme/package.json';\n\nimport { version as pluginApiDocs } from '../../../../plugins/api-docs/package.json';\nimport { version as pluginAppBackend } from '../../../../plugins/app-backend/package.json';\nimport { version as pluginAuthBackend } from '../../../../plugins/auth-backend/package.json';\nimport { version as pluginCatalog } from '../../../../plugins/catalog/package.json';\nimport { version as pluginCatalogReact } from '../../../../plugins/catalog-react/package.json';\nimport { version as pluginCatalogBackend } from '../../../../plugins/catalog-backend/package.json';\nimport { version as pluginCatalogGraph } from '../../../../plugins/catalog-graph/package.json';\nimport { version as pluginCatalogImport } from '../../../../plugins/catalog-import/package.json';\nimport { version as pluginCircleci } from '../../../../plugins/circleci/package.json';\nimport { version as pluginExplore } from '../../../../plugins/explore/package.json';\nimport { version as pluginGithubActions } from '../../../../plugins/github-actions/package.json';\nimport { version as pluginLighthouse } from '../../../../plugins/lighthouse/package.json';\nimport { version as pluginOrg } from '../../../../plugins/org/package.json';\nimport { version as pluginPermissionCommon } from '../../../../plugins/permission-common/package.json';\nimport { version as pluginPermissionNode } from '../../../../plugins/permission-node/package.json';\nimport { version as pluginProxyBackend } from '../../../../plugins/proxy-backend/package.json';\nimport { version as pluginRollbarBackend } from '../../../../plugins/rollbar-backend/package.json';\nimport { version as pluginScaffolder } from '../../../../plugins/scaffolder/package.json';\nimport { version as pluginScaffolderBackend } from '../../../../plugins/scaffolder-backend/package.json';\nimport { version as pluginSearch } from '../../../../plugins/search/package.json';\nimport { version as pluginSearchBackend } from '../../../../plugins/search-backend/package.json';\nimport { version as pluginSearchBackendNode } from '../../../../plugins/search-backend-node/package.json';\nimport { version as pluginTechRadar } from '../../../../plugins/tech-radar/package.json';\nimport { version as pluginTechdocs } from '../../../../plugins/techdocs/package.json';\nimport { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-backend/package.json';\nimport { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json';\n\nexport const packageVersions = {\n '@backstage/app-defaults': appDefaults,\n '@backstage/backend-common': backendCommon,\n '@backstage/backend-tasks': backendTasks,\n '@backstage/catalog-client': catalogClient,\n '@backstage/catalog-model': catalogModel,\n '@backstage/cli': cli,\n '@backstage/config': config,\n '@backstage/core-app-api': coreAppApi,\n '@backstage/core-components': coreComponents,\n '@backstage/core-plugin-api': corePluginApi,\n '@backstage/errors': errors,\n '@backstage/integration-react': integrationReact,\n '@backstage/plugin-api-docs': pluginApiDocs,\n '@backstage/plugin-app-backend': pluginAppBackend,\n '@backstage/plugin-auth-backend': pluginAuthBackend,\n '@backstage/plugin-catalog': pluginCatalog,\n '@backstage/plugin-catalog-react': pluginCatalogReact,\n '@backstage/plugin-catalog-backend': pluginCatalogBackend,\n '@backstage/plugin-catalog-graph': pluginCatalogGraph,\n '@backstage/plugin-catalog-import': pluginCatalogImport,\n '@backstage/plugin-circleci': pluginCircleci,\n '@backstage/plugin-explore': pluginExplore,\n '@backstage/plugin-github-actions': pluginGithubActions,\n '@backstage/plugin-lighthouse': pluginLighthouse,\n '@backstage/plugin-org': pluginOrg,\n '@backstage/plugin-permission-common': pluginPermissionCommon,\n '@backstage/plugin-permission-node': pluginPermissionNode,\n '@backstage/plugin-proxy-backend': pluginProxyBackend,\n '@backstage/plugin-rollbar-backend': pluginRollbarBackend,\n '@backstage/plugin-scaffolder': pluginScaffolder,\n '@backstage/plugin-scaffolder-backend': pluginScaffolderBackend,\n '@backstage/plugin-search': pluginSearch,\n '@backstage/plugin-search-backend': pluginSearchBackend,\n '@backstage/plugin-search-backend-node': pluginSearchBackendNode,\n '@backstage/plugin-tech-radar': pluginTechRadar,\n '@backstage/plugin-techdocs': pluginTechdocs,\n '@backstage/plugin-techdocs-backend': pluginTechdocsBackend,\n '@backstage/plugin-user-settings': pluginUserSettings,\n '@backstage/test-utils': testUtils,\n '@backstage/theme': theme,\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { BACKSTAGE_JSON } from '@backstage/cli-common';\nimport chalk from 'chalk';\nimport fs from 'fs-extra';\nimport handlebars from 'handlebars';\nimport ora from 'ora';\nimport recursive from 'recursive-readdir';\nimport {\n basename,\n dirname,\n join,\n resolve as resolvePath,\n relative as relativePath,\n} from 'path';\nimport { exec as execCb } from 'child_process';\nimport { packageVersions } from './versions';\nimport { promisify } from 'util';\n\nconst TASK_NAME_MAX_LENGTH = 14;\nconst exec = promisify(execCb);\n\nexport class Task {\n static log(name: string = '') {\n process.stdout.write(`${chalk.green(name)}\\n`);\n }\n\n static error(message: string = '') {\n process.stdout.write(`\\n${chalk.red(message)}\\n\\n`);\n }\n\n static section(name: string) {\n const title = chalk.green(`${name}:`);\n process.stdout.write(`\\n ${title}\\n`);\n }\n\n static exit(code: number = 0) {\n process.exit(code);\n }\n\n static async forItem(\n task: string,\n item: string,\n taskFunc: () => Promise<void>,\n ): Promise<void> {\n const paddedTask = chalk.green(task.padEnd(TASK_NAME_MAX_LENGTH));\n\n const spinner = ora({\n prefixText: chalk.green(` ${paddedTask}${chalk.cyan(item)}`),\n spinner: 'arc',\n color: 'green',\n }).start();\n\n try {\n await taskFunc();\n spinner.succeed();\n } catch (error) {\n spinner.fail();\n throw error;\n }\n }\n}\n\n/**\n * Generate a templated backstage project\n *\n * @param templateDir - location containing template files\n * @param destinationDir - location to save templated project\n * @param context - template parameters\n */\nexport async function templatingTask(\n templateDir: string,\n destinationDir: string,\n context: any,\n version: string,\n) {\n const files = await recursive(templateDir).catch(error => {\n throw new Error(`Failed to read template directory: ${error.message}`);\n });\n\n for (const file of files) {\n const destinationFile = resolvePath(\n destinationDir,\n relativePath(templateDir, file),\n );\n await fs.ensureDir(dirname(destinationFile));\n\n if (file.endsWith('.hbs')) {\n await Task.forItem('templating', basename(file), async () => {\n const destination = destinationFile.replace(/\\.hbs$/, '');\n\n const template = await fs.readFile(file);\n const compiled = handlebars.compile(template.toString());\n const contents = compiled(\n { name: basename(destination), ...context },\n {\n helpers: {\n version(name: keyof typeof packageVersions) {\n if (name in packageVersions) {\n return packageVersions[name];\n }\n throw new Error(`No version available for package ${name}`);\n },\n },\n },\n );\n\n await fs.writeFile(destination, contents).catch(error => {\n throw new Error(\n `Failed to create file: ${destination}: ${error.message}`,\n );\n });\n });\n } else {\n await Task.forItem('copying', basename(file), async () => {\n await fs.copyFile(file, destinationFile).catch(error => {\n const destination = destinationFile;\n throw new Error(\n `Failed to copy file to ${destination} : ${error.message}`,\n );\n });\n });\n }\n }\n await Task.forItem('creating', BACKSTAGE_JSON, () =>\n fs.writeFile(\n join(destinationDir, BACKSTAGE_JSON),\n `{\\n \"version\": ${JSON.stringify(version)}\\n}\\n`,\n ),\n );\n}\n\n/**\n * Verify that application target does not already exist\n *\n * @param rootDir - The directory to create application folder `name`\n * @param name - The specified name of the application\n * @Throws Error - If directory with name of `destination` already exists\n */\nexport async function checkAppExistsTask(rootDir: string, name: string) {\n await Task.forItem('checking', name, async () => {\n const destination = resolvePath(rootDir, name);\n\n if (await fs.pathExists(destination)) {\n const existing = chalk.cyan(destination.replace(`${rootDir}/`, ''));\n throw new Error(\n `A directory with the same name already exists: ${existing}\\nPlease try again with a different app name`,\n );\n }\n });\n}\n\n/**\n * Verify that application `path` exists, otherwise create the directory\n *\n * @param path - target to create directory\n * @throws if `path` is a file, or `fs.mkdir` fails\n */\nexport async function checkPathExistsTask(path: string) {\n await Task.forItem('checking', path, async () => {\n try {\n await fs.mkdirs(path);\n } catch (error) {\n // will fail if a file already exists at given `path`\n throw new Error(`Failed to create app directory: ${error.message}`);\n }\n });\n}\n\n/**\n * Create a folder to store templated files\n *\n * @param tempDir - target temporary directory\n * @throws if `fs.mkdir` fails\n */\nexport async function createTemporaryAppFolderTask(tempDir: string) {\n await Task.forItem('creating', 'temporary directory', async () => {\n try {\n await fs.mkdir(tempDir);\n } catch (error) {\n throw new Error(`Failed to create temporary app directory, ${error}`);\n }\n });\n}\n\n/**\n * Run `yarn install` and `run tsc` in application directory\n *\n * @param appDir - location of application to build\n */\nexport async function buildAppTask(appDir: string) {\n const runCmd = async (cmd: string) => {\n await Task.forItem('executing', cmd, async () => {\n process.chdir(appDir);\n await exec(cmd).catch(error => {\n process.stdout.write(error.stderr);\n process.stdout.write(error.stdout);\n throw new Error(`Could not execute command ${chalk.cyan(cmd)}`);\n });\n });\n };\n\n await runCmd('yarn install');\n await runCmd('yarn tsc');\n}\n\n/**\n * Move temporary directory to destination application folder\n *\n * @param tempDir - source path to copy files from\n * @param destination - target path to copy files\n * @param id - item ID\n * @throws if `fs.move` fails\n */\nexport async function moveAppTask(\n tempDir: string,\n destination: string,\n id: string,\n) {\n await Task.forItem('moving', id, async () => {\n await fs\n .move(tempDir, destination)\n .catch(error => {\n throw new Error(\n `Failed to move app from ${tempDir} to ${destination}: ${error.message}`,\n );\n })\n .finally(() => {\n // remove temporary files on both success and failure\n fs.removeSync(tempDir);\n });\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport inquirer, { Answers } from 'inquirer';\nimport { resolve as resolvePath } from 'path';\nimport { findPaths } from '@backstage/cli-common';\nimport os from 'os';\nimport {\n Task,\n buildAppTask,\n checkAppExistsTask,\n checkPathExistsTask,\n createTemporaryAppFolderTask,\n moveAppTask,\n templatingTask,\n} from './lib/tasks';\n\nexport default async (cmd: Command, version: string): Promise<void> => {\n /* eslint-disable-next-line no-restricted-syntax */\n const paths = findPaths(__dirname);\n\n const answers: Answers = await inquirer.prompt([\n {\n type: 'input',\n name: 'name',\n message: chalk.blue('Enter a name for the app [required]'),\n validate: (value: any) => {\n if (!value) {\n return chalk.red('Please enter a name for the app');\n } else if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(value)) {\n return chalk.red(\n 'App name must be lowercase and contain only letters, digits, and dashes.',\n );\n }\n return true;\n },\n },\n {\n type: 'list',\n name: 'dbType',\n message: chalk.blue('Select database for the backend [required]'),\n choices: ['SQLite', 'PostgreSQL'],\n },\n ]);\n answers.dbTypePG = answers.dbType === 'PostgreSQL';\n answers.dbTypeSqlite = answers.dbType === 'SQLite';\n\n const templateDir = paths.resolveOwn('templates/default-app');\n const tempDir = resolvePath(os.tmpdir(), answers.name);\n\n // Use `--path` argument as application directory when specified, otherwise\n // create a directory using `answers.name`\n const appDir = cmd.path\n ? resolvePath(paths.targetDir, cmd.path)\n : resolvePath(paths.targetDir, answers.name);\n\n Task.log();\n Task.log('Creating the app...');\n\n try {\n if (cmd.path) {\n // Template directly to specified path\n\n Task.section('Checking that supplied path exists');\n await checkPathExistsTask(appDir);\n\n Task.section('Preparing files');\n await templatingTask(templateDir, cmd.path, answers, version);\n } else {\n // Template to temporary location, and then move files\n\n Task.section('Checking if the directory is available');\n await checkAppExistsTask(paths.targetDir, answers.name);\n\n Task.section('Creating a temporary app directory');\n await createTemporaryAppFolderTask(tempDir);\n\n Task.section('Preparing files');\n await templatingTask(templateDir, tempDir, answers, version);\n\n Task.section('Moving to final location');\n await moveAppTask(tempDir, appDir, answers.name);\n }\n\n if (!cmd.skipInstall) {\n Task.section('Building the app');\n await buildAppTask(appDir);\n }\n\n Task.log();\n Task.log(\n chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),\n );\n Task.log();\n Task.section('All set! Now you might want to');\n Task.log(` Run the app: ${chalk.cyan(`cd ${answers.name} && yarn dev`)}`);\n Task.log(\n ' Set up the software catalog: https://backstage.io/docs/features/software-catalog/configuration',\n );\n Task.log(' Add authentication: https://backstage.io/docs/auth/');\n Task.log();\n Task.exit();\n } catch (error) {\n Task.error(String(error));\n\n Task.log('It seems that something went wrong when creating the app 🤔');\n\n Task.error('🔥 Failed to create app!');\n Task.exit(1);\n }\n};\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * A CLI that helps you create your own Backstage app\n *\n * @packageDocumentation\n */\n\nimport program from 'commander';\nimport { exitWithError } from './lib/errors';\nimport { version } from '../package.json';\nimport createApp from './createApp';\n\nconst main = (argv: string[]) => {\n program\n .name('backstage-create-app')\n .version(version)\n .description('Creates a new app in a new directory or specified path')\n .option(\n '--path [directory]',\n 'Location to store the app defaulting to a new folder with the app name',\n )\n .option(\n '--skip-install',\n 'Skip the install and builds steps after creating the app',\n )\n .action(cmd => createApp(cmd, version));\n\n program.parse(argv);\n};\n\nprocess.on('unhandledRejection', rejection => {\n if (rejection instanceof Error) {\n exitWithError(rejection);\n } else {\n exitWithError(new Error(`Unknown rejection: '${rejection}'`));\n }\n});\n\nmain(process.argv);\n"],"names":["chalk","appDefaults","backendCommon","backendTasks","catalogClient","catalogModel","cli","config","coreAppApi","coreComponents","corePluginApi","errors","integrationReact","pluginApiDocs","pluginAppBackend","pluginAuthBackend","pluginCatalog","pluginCatalogReact","pluginCatalogBackend","pluginCatalogGraph","pluginCatalogImport","pluginCircleci","pluginExplore","pluginGithubActions","pluginLighthouse","pluginOrg","pluginPermissionCommon","pluginPermissionNode","pluginProxyBackend","pluginRollbarBackend","pluginScaffolder","pluginScaffolderBackend","pluginSearch","pluginSearchBackend","pluginSearchBackendNode","pluginTechRadar","pluginTechdocs","pluginTechdocsBackend","pluginUserSettings","testUtils","theme","promisify","execCb","ora","recursive","resolvePath","relativePath","fs","dirname","basename","handlebars","BACKSTAGE_JSON","join","findPaths","inquirer","os","version"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@backstage/create-app",
3
3
  "description": "A CLI that helps you create your own Backstage app",
4
- "version": "0.4.12",
4
+ "version": "0.4.16",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -57,5 +57,5 @@
57
57
  "dist",
58
58
  "templates"
59
59
  ],
60
- "gitHead": "da66c61bdd63cdb3f0f0cd2e26dc9e6454d93c7b"
60
+ "gitHead": "0c722e77190fc3772377336586e2dfc39dfdf466"
61
61
  }
@@ -34,7 +34,8 @@
34
34
  "@spotify/prettier-config": "^12.0.0",
35
35
  "concurrently": "^6.0.0",
36
36
  "lerna": "^4.0.0",
37
- "prettier": "^2.3.2"
37
+ "prettier": "^2.3.2",
38
+ "typescript": "~4.5.4"
38
39
  },
39
40
  "prettier": "@spotify/prettier-config",
40
41
  "lint-staged": {
@@ -13,6 +13,7 @@
13
13
  "@backstage/integration-react": "^{{version '@backstage/integration-react'}}",
14
14
  "@backstage/plugin-api-docs": "^{{version '@backstage/plugin-api-docs'}}",
15
15
  "@backstage/plugin-catalog": "^{{version '@backstage/plugin-catalog'}}",
16
+ "@backstage/plugin-catalog-graph": "^{{version '@backstage/plugin-catalog-graph'}}",
16
17
  "@backstage/plugin-catalog-import": "^{{version '@backstage/plugin-catalog-import'}}",
17
18
  "@backstage/plugin-catalog-react": "^{{version '@backstage/plugin-catalog-react'}}",
18
19
  "@backstage/plugin-github-actions": "^{{version '@backstage/plugin-github-actions'}}",
@@ -26,8 +27,8 @@
26
27
  "@material-ui/core": "^4.12.2",
27
28
  "@material-ui/icons": "^4.9.1",
28
29
  "history": "^5.0.0",
29
- "react": "^16.13.1",
30
- "react-dom": "^16.13.1",
30
+ "react": "^17.0.2",
31
+ "react-dom": "^17.0.2",
31
32
  "react-router": "6.0.0-beta.0",
32
33
  "react-router-dom": "6.0.0-beta.0",
33
34
  "react-use": "^15.3.3"
@@ -11,6 +11,7 @@ import {
11
11
  catalogImportPlugin,
12
12
  } from '@backstage/plugin-catalog-import';
13
13
  import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
14
+ import { orgPlugin } from '@backstage/plugin-org';
14
15
  import { SearchPage } from '@backstage/plugin-search';
15
16
  import { TechRadarPage } from '@backstage/plugin-tech-radar';
16
17
  import {
@@ -28,6 +29,7 @@ import { Root } from './components/Root';
28
29
  import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
29
30
  import { createApp } from '@backstage/app-defaults';
30
31
  import { FlatRoutes } from '@backstage/core-app-api';
32
+ import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
31
33
 
32
34
  const app = createApp({
33
35
  apis,
@@ -37,11 +39,14 @@ const app = createApp({
37
39
  viewTechDoc: techdocsPlugin.routes.docRoot,
38
40
  });
39
41
  bind(apiDocsPlugin.externalRoutes, {
40
- createComponent: scaffolderPlugin.routes.root,
42
+ registerApi: catalogImportPlugin.routes.importPage,
41
43
  });
42
44
  bind(scaffolderPlugin.externalRoutes, {
43
45
  registerComponent: catalogImportPlugin.routes.importPage,
44
46
  });
47
+ bind(orgPlugin.externalRoutes, {
48
+ catalogIndex: catalogPlugin.routes.catalogIndex,
49
+ });
45
50
  },
46
51
  });
47
52
 
@@ -76,6 +81,7 @@ const routes = (
76
81
  {searchPage}
77
82
  </Route>
78
83
  <Route path="/settings" element={<UserSettingsPage />} />
84
+ <Route path="/catalog-graph" element={<CatalogGraphPage />} />
79
85
  </FlatRoutes>
80
86
  );
81
87
 
@@ -24,21 +24,27 @@ import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
24
24
  import LogoFull from './LogoFull';
25
25
  import LogoIcon from './LogoIcon';
26
26
  import { NavLink } from 'react-router-dom';
27
- import { Settings as SidebarSettings } from '@backstage/plugin-user-settings';
27
+ import {
28
+ Settings as SidebarSettings,
29
+ UserSettingsSignInAvatar,
30
+ } from '@backstage/plugin-user-settings';
28
31
  import {
29
32
  SidebarSearchModal,
30
33
  SearchContextProvider,
31
34
  } from '@backstage/plugin-search';
32
35
  import {
33
36
  Sidebar,
34
- SidebarPage,
35
37
  sidebarConfig,
36
38
  SidebarContext,
37
- SidebarItem,
38
39
  SidebarDivider,
39
- SidebarSpace,
40
+ SidebarGroup,
41
+ SidebarItem,
42
+ SidebarPage,
40
43
  SidebarScrollWrapper,
44
+ SidebarSpace,
41
45
  } from '@backstage/core-components';
46
+ import MenuIcon from '@material-ui/icons/Menu';
47
+ import SearchIcon from '@material-ui/icons/Search';
42
48
 
43
49
  const useSidebarLogoStyles = makeStyles({
44
50
  root: {
@@ -77,23 +83,33 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
77
83
  <SidebarPage>
78
84
  <Sidebar>
79
85
  <SidebarLogo />
80
- <SearchContextProvider>
81
- <SidebarSearchModal />
82
- </SearchContextProvider>
86
+ <SidebarGroup label="Search" icon={<SearchIcon />} to="/search">
87
+ <SearchContextProvider>
88
+ <SidebarSearchModal />
89
+ </SearchContextProvider>{' '}
90
+ </SidebarGroup>
83
91
  <SidebarDivider />
84
- {/* Global nav, not org-specific */}
85
- <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
86
- <SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
87
- <SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
88
- <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
89
- {/* End global nav */}
90
- <SidebarDivider />
91
- <SidebarScrollWrapper>
92
- <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
93
- </SidebarScrollWrapper>
92
+ <SidebarGroup label="Menu" icon={<MenuIcon />}>
93
+ {/* Global nav, not org-specific */}
94
+ <SidebarItem icon={HomeIcon} to="catalog" text="Home" />
95
+ <SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
96
+ <SidebarItem icon={LibraryBooks} to="docs" text="Docs" />
97
+ <SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
98
+ {/* End global nav */}
99
+ <SidebarDivider />
100
+ <SidebarScrollWrapper>
101
+ <SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
102
+ </SidebarScrollWrapper>
103
+ </SidebarGroup>
94
104
  <SidebarSpace />
95
105
  <SidebarDivider />
96
- <SidebarSettings />
106
+ <SidebarGroup
107
+ label="Settings"
108
+ icon={<UserSettingsSignInAvatar />}
109
+ to="/settings"
110
+ >
111
+ <SidebarSettings />
112
+ </SidebarGroup>
97
113
  </Sidebar>
98
114
  {children}
99
115
  </SidebarPage>
@@ -27,7 +27,6 @@ import {
27
27
  EntityAboutCard,
28
28
  EntityDependsOnComponentsCard,
29
29
  EntityDependsOnResourcesCard,
30
- EntitySystemDiagramCard,
31
30
  EntityHasComponentsCard,
32
31
  EntityHasResourcesCard,
33
32
  EntityHasSubcomponentsCard,
@@ -54,6 +53,20 @@ import {
54
53
  } from '@backstage/plugin-org';
55
54
  import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
56
55
  import { EmptyState } from '@backstage/core-components';
56
+ import {
57
+ Direction,
58
+ EntityCatalogGraphCard,
59
+ } from '@backstage/plugin-catalog-graph';
60
+ import {
61
+ RELATION_API_CONSUMED_BY,
62
+ RELATION_API_PROVIDED_BY,
63
+ RELATION_CONSUMES_API,
64
+ RELATION_DEPENDENCY_OF,
65
+ RELATION_DEPENDS_ON,
66
+ RELATION_HAS_PART,
67
+ RELATION_PART_OF,
68
+ RELATION_PROVIDES_API,
69
+ } from '@backstage/catalog-model';
57
70
 
58
71
  const cicdContent = (
59
72
  // This is an example of how you can implement your company's logic in entity page.
@@ -108,6 +121,10 @@ const overviewContent = (
108
121
  <Grid item md={6}>
109
122
  <EntityAboutCard variant="gridItem" />
110
123
  </Grid>
124
+ <Grid item md={6} xs={12}>
125
+ <EntityCatalogGraphCard variant="gridItem" height={400} />
126
+ </Grid>
127
+
111
128
  <Grid item md={4} xs={12}>
112
129
  <EntityLinksCard />
113
130
  </Grid>
@@ -223,6 +240,9 @@ const apiPage = (
223
240
  <Grid item md={6}>
224
241
  <EntityAboutCard />
225
242
  </Grid>
243
+ <Grid item md={6} xs={12}>
244
+ <EntityCatalogGraphCard variant="gridItem" height={400} />
245
+ </Grid>
226
246
  <Grid item md={4} xs={12}>
227
247
  <EntityLinksCard />
228
248
  </Grid>
@@ -290,6 +310,9 @@ const systemPage = (
290
310
  <Grid item md={6}>
291
311
  <EntityAboutCard variant="gridItem" />
292
312
  </Grid>
313
+ <Grid item md={6} xs={12}>
314
+ <EntityCatalogGraphCard variant="gridItem" height={400} />
315
+ </Grid>
293
316
  <Grid item md={6}>
294
317
  <EntityHasComponentsCard variant="gridItem" />
295
318
  </Grid>
@@ -302,7 +325,23 @@ const systemPage = (
302
325
  </Grid>
303
326
  </EntityLayout.Route>
304
327
  <EntityLayout.Route path="/diagram" title="Diagram">
305
- <EntitySystemDiagramCard />
328
+ <EntityCatalogGraphCard
329
+ variant="gridItem"
330
+ direction={Direction.TOP_BOTTOM}
331
+ title="System Diagram"
332
+ height={700}
333
+ relations={[
334
+ RELATION_PART_OF,
335
+ RELATION_HAS_PART,
336
+ RELATION_API_CONSUMED_BY,
337
+ RELATION_API_PROVIDED_BY,
338
+ RELATION_CONSUMES_API,
339
+ RELATION_PROVIDES_API,
340
+ RELATION_DEPENDENCY_OF,
341
+ RELATION_DEPENDS_ON,
342
+ ]}
343
+ unidirectional={false}
344
+ />
306
345
  </EntityLayout.Route>
307
346
  </EntityLayout>
308
347
  );
@@ -315,6 +354,9 @@ const domainPage = (
315
354
  <Grid item md={6}>
316
355
  <EntityAboutCard variant="gridItem" />
317
356
  </Grid>
357
+ <Grid item md={6} xs={12}>
358
+ <EntityCatalogGraphCard variant="gridItem" height={400} />
359
+ </Grid>
318
360
  <Grid item md={6}>
319
361
  <EntityHasSystemsCard variant="gridItem" />
320
362
  </Grid>
@@ -2,6 +2,10 @@ import React from 'react';
2
2
  import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core';
3
3
 
4
4
  import { CatalogResultListItem } from '@backstage/plugin-catalog';
5
+ import {
6
+ catalogApiRef,
7
+ CATALOG_FILTER_EXISTS,
8
+ } from '@backstage/plugin-catalog-react';
5
9
  import { DocsResultListItem } from '@backstage/plugin-techdocs';
6
10
 
7
11
  import {
@@ -10,6 +14,7 @@ import {
10
14
  SearchResult,
11
15
  SearchType,
12
16
  DefaultResultListItem,
17
+ useSearch,
13
18
  } from '@backstage/plugin-search';
14
19
  import {
15
20
  CatalogIcon,
@@ -18,6 +23,7 @@ import {
18
23
  Header,
19
24
  Page,
20
25
  } from '@backstage/core-components';
26
+ import { useApi } from '@backstage/core-plugin-api';
21
27
 
22
28
  const useStyles = makeStyles((theme: Theme) => ({
23
29
  bar: {
@@ -36,6 +42,8 @@ const useStyles = makeStyles((theme: Theme) => ({
36
42
 
37
43
  const SearchPage = () => {
38
44
  const classes = useStyles();
45
+ const { types } = useSearch();
46
+ const catalogApi = useApi(catalogApiRef);
39
47
 
40
48
  return (
41
49
  <Page themeId="home">
@@ -65,13 +73,36 @@ const SearchPage = () => {
65
73
  ]}
66
74
  />
67
75
  <Paper className={classes.filters}>
76
+ {types.includes('techdocs') && (
77
+ <SearchFilter.Select
78
+ className={classes.filter}
79
+ label="Entity"
80
+ name="name"
81
+ values={async () => {
82
+ // Return a list of entities which are documented.
83
+ const { items } = await catalogApi.getEntities({
84
+ fields: ['metadata.name'],
85
+ filter: {
86
+ 'metadata.annotations.backstage.io/techdocs-ref':
87
+ CATALOG_FILTER_EXISTS,
88
+ },
89
+ });
90
+
91
+ const names = items.map(entity => entity.metadata.name);
92
+ names.sort();
93
+ return names;
94
+ }}
95
+ />
96
+ )}
68
97
  <SearchFilter.Select
69
98
  className={classes.filter}
99
+ label="Kind"
70
100
  name="kind"
71
101
  values={['Component', 'Template']}
72
102
  />
73
103
  <SearchFilter.Checkbox
74
104
  className={classes.filter}
105
+ label="Lifecycle"
75
106
  name="lifecycle"
76
107
  values={['experimental', 'production']}
77
108
  />
@@ -5,10 +5,12 @@ import { PluginEnvironment } from '../types';
5
5
  export default async function createPlugin({
6
6
  logger,
7
7
  config,
8
+ database,
8
9
  }: PluginEnvironment): Promise<Router> {
9
10
  return await createRouter({
10
11
  logger,
11
12
  config,
13
+ database,
12
14
  appPackageName: 'app',
13
15
  });
14
16
  }
@@ -10,6 +10,7 @@ import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend';
10
10
 
11
11
  export default async function createPlugin({
12
12
  logger,
13
+ permissions,
13
14
  discovery,
14
15
  config,
15
16
  tokenManager,
@@ -49,6 +50,9 @@ export default async function createPlugin({
49
50
 
50
51
  return await createRouter({
51
52
  engine: indexBuilder.getSearchEngine(),
53
+ types: indexBuilder.getDocumentTypes(),
54
+ permissions,
55
+ config,
52
56
  logger,
53
57
  });
54
58
  }