@backstage/plugin-scaffolder 1.11.0 → 1.11.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/alpha/package.json +4 -3
  3. package/dist/alpha.d.ts +73 -0
  4. package/dist/alpha.esm.js +98 -0
  5. package/dist/alpha.esm.js.map +1 -0
  6. package/dist/esm/alpha/ListTasksPage-5aa6305b.esm.js +192 -0
  7. package/dist/esm/alpha/ListTasksPage-5aa6305b.esm.js.map +1 -0
  8. package/dist/esm/alpha/Router-0b12a83d.esm.js +1563 -0
  9. package/dist/esm/alpha/Router-0b12a83d.esm.js.map +1 -0
  10. package/dist/esm/alpha/alpha-d9ed6c0a.esm.js +3664 -0
  11. package/dist/esm/alpha/alpha-d9ed6c0a.esm.js.map +1 -0
  12. package/dist/esm/alpha/index-9804fdae.esm.js +446 -0
  13. package/dist/esm/alpha/index-9804fdae.esm.js.map +1 -0
  14. package/dist/esm/{ListTasksPage-a0fe74a9.esm.js → index/ListTasksPage-e88f6608.esm.js} +2 -2
  15. package/dist/esm/index/ListTasksPage-e88f6608.esm.js.map +1 -0
  16. package/dist/esm/{Router-fae7a62f.esm.js → index/Router-dc8c17c7.esm.js} +6 -5
  17. package/dist/esm/index/Router-dc8c17c7.esm.js.map +1 -0
  18. package/dist/esm/{index-83c9fe8a.esm.js → index/index-509695ba.esm.js} +6 -5
  19. package/dist/esm/index/index-509695ba.esm.js.map +1 -0
  20. package/dist/esm/{index-5b3a75fa.esm.js → index/index-d9b6a6fb.esm.js} +8 -7
  21. package/dist/esm/index/index-d9b6a6fb.esm.js.map +1 -0
  22. package/dist/index.d.ts +292 -392
  23. package/dist/index.esm.js +27 -26
  24. package/dist/index.esm.js.map +1 -1
  25. package/package.json +35 -24
  26. package/dist/esm/ListTasksPage-a0fe74a9.esm.js.map +0 -1
  27. package/dist/esm/Router-fae7a62f.esm.js.map +0 -1
  28. package/dist/esm/index-5b3a75fa.esm.js.map +0 -1
  29. package/dist/esm/index-83c9fe8a.esm.js.map +0 -1
  30. package/dist/index.alpha.d.ts +0 -642
  31. package/dist/index.beta.d.ts +0 -598
@@ -0,0 +1,1563 @@
1
+ import React, { useState, useCallback, useMemo, Component, useEffect } from 'react';
2
+ import { useNavigate, Navigate, useOutlet, Routes, Route } from 'react-router-dom';
3
+ import { ItemCardHeader, MarkdownContent, Button, Link, ContentHeader, Progress, WarningPanel, Content, ItemCardGrid, Page, Header, CreateButton, SupportButton, StructuredMetadataTable, InfoCard } from '@backstage/core-components';
4
+ import { useApp, useRouteRef, useApi, alertApiRef, useRouteRefParams, useAnalytics, errorApiRef, featureFlagsApiRef, useApiHolder, AnalyticsContext } from '@backstage/core-plugin-api';
5
+ import { getEntityRelations, getEntitySourceLocation, FavoriteEntity, EntityRefLinks, useEntityList, useEntityTypeFilter, EntityListProvider, CatalogFilterLayout, EntitySearchBar, EntityKindPicker, UserListPicker, EntityTagPicker, catalogApiRef, humanizeEntityRef } from '@backstage/plugin-catalog-react';
6
+ import { RELATION_OWNED_BY, parseEntityRef, stringifyEntityRef, DEFAULT_NAMESPACE } from '@backstage/catalog-model';
7
+ import { makeStyles, useTheme, Card, CardMedia, CardContent, Box, Typography, Chip, CardActions, Tooltip, IconButton, FormControlLabel, Checkbox, TextField, Paper, Button as Button$1, Stepper, Step, StepLabel, StepContent, LinearProgress, FormControl, InputLabel, Select, MenuItem as MenuItem$1, CardHeader } from '@material-ui/core';
8
+ import { scmIntegrationsApiRef, ScmIntegrationIcon } from '@backstage/integration-react';
9
+ import LanguageIcon from '@material-ui/icons/Language';
10
+ import WarningIcon from '@material-ui/icons/Warning';
11
+ import { s as selectedTemplateRouteRef, v as viewTechDocRouteRef, e as editRouteRef, a as actionsRouteRef, b as scaffolderListTaskRouteRef, r as registerComponentRouteRef, c as scaffolderTaskRouteRef, d as rootRouteRef, u as useDryRun, f as useDirectoryEditor, D as DirectoryEditorProvider, g as DryRunProvider, T as TemplateEditorBrowser, h as TemplateEditorTextArea, i as DryRunResults, j as TemplateEditorIntro, W as WebFileSystemAccess, l as legacySelectedTemplateRouteRef, A as ActionsPage, k as TaskPage } from './alpha-d9ed6c0a.esm.js';
12
+ import capitalize from 'lodash/capitalize';
13
+ import CheckBoxIcon from '@material-ui/icons/CheckBox';
14
+ import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
15
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
16
+ import { Autocomplete } from '@material-ui/lab';
17
+ import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
18
+ import { usePermission } from '@backstage/plugin-permission-react';
19
+ import IconButton$1 from '@material-ui/core/IconButton';
20
+ import ListItemIcon from '@material-ui/core/ListItemIcon';
21
+ import ListItemText from '@material-ui/core/ListItemText';
22
+ import MenuItem from '@material-ui/core/MenuItem';
23
+ import MenuList from '@material-ui/core/MenuList';
24
+ import Popover from '@material-ui/core/Popover';
25
+ import { makeStyles as makeStyles$1 } from '@material-ui/core/styles';
26
+ import Description from '@material-ui/icons/Description';
27
+ import Edit from '@material-ui/icons/Edit';
28
+ import List from '@material-ui/icons/List';
29
+ import MoreVert from '@material-ui/icons/MoreVert';
30
+ import qs from 'qs';
31
+ import useAsync from 'react-use/lib/useAsync';
32
+ import { useTemplateSecrets, scaffolderApiRef, useCustomFieldExtensions, useCustomLayouts, SecretsContextProvider } from '@backstage/plugin-scaffolder-react';
33
+ import { withTheme } from '@rjsf/core';
34
+ import { Theme } from '@rjsf/material-ui';
35
+ import cloneDeep from 'lodash/cloneDeep';
36
+ import { extractSchemaFromStep } from '@backstage/plugin-scaffolder-react/alpha';
37
+ import { StreamLanguage } from '@codemirror/language';
38
+ import { yaml as yaml$1 } from '@codemirror/legacy-modes/mode/yaml';
39
+ import CloseIcon from '@material-ui/icons/Close';
40
+ import CodeMirror from '@uiw/react-codemirror';
41
+ import yaml from 'yaml';
42
+ import useDebounce from 'react-use/lib/useDebounce';
43
+ import { D as DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, L as ListTasksPage } from './ListTasksPage-5aa6305b.esm.js';
44
+ import '@backstage/errors';
45
+ import 'zen-observable';
46
+ import '@material-ui/core/FormControl';
47
+ import '@material-ui/lab/Autocomplete';
48
+ import 'zod';
49
+ import 'zod-to-json-schema';
50
+ import '@material-ui/core/FormHelperText';
51
+ import '@material-ui/core/Input';
52
+ import '@material-ui/core/InputLabel';
53
+ import 'react-use/lib/useEffectOnce';
54
+ import '@material-ui/core/Button';
55
+ import '@material-ui/core/useMediaQuery';
56
+ import '@material-ui/icons/AddCircleOutline';
57
+ import 'use-immer';
58
+ import '@material-ui/icons/RemoveCircleOutline';
59
+ import '@material-ui/icons/PanoramaFishEye';
60
+ import 'classnames';
61
+ import '@material-ui/icons/CheckCircleOutline';
62
+ import '@material-ui/icons/ErrorOutline';
63
+ import 'react-use/lib/useInterval';
64
+ import 'luxon';
65
+ import 'humanize-duration';
66
+ import '@react-hookz/web';
67
+ import '@material-ui/icons/Repeat';
68
+ import '@material-ui/icons/Toc';
69
+ import '@material-ui/icons/Settings';
70
+ import '@material-ui/icons/FontDownload';
71
+ import '@material-ui/core/Typography';
72
+ import '@rjsf/validator-ajv8';
73
+ import '@material-ui/core/Accordion';
74
+ import '@material-ui/core/AccordionDetails';
75
+ import '@material-ui/core/AccordionSummary';
76
+ import '@material-ui/core/Divider';
77
+ import '@material-ui/icons/ExpandLess';
78
+ import '@material-ui/core/List';
79
+ import '@material-ui/core/ListItem';
80
+ import '@material-ui/core/ListItemSecondaryAction';
81
+ import '@material-ui/icons/Cancel';
82
+ import '@material-ui/icons/Check';
83
+ import '@material-ui/icons/Delete';
84
+ import '@material-ui/core/Box';
85
+ import '@material-ui/core/Tab';
86
+ import '@material-ui/core/Tabs';
87
+ import '@material-ui/core/Grid';
88
+ import '@material-ui/core/Step';
89
+ import '@material-ui/core/StepLabel';
90
+ import '@material-ui/core/Stepper';
91
+ import '@material-ui/icons/FiberManualRecord';
92
+ import '@material-ui/lab/TreeView';
93
+ import '@material-ui/icons/ChevronRight';
94
+ import '@material-ui/lab/TreeItem';
95
+ import '@material-ui/icons/Refresh';
96
+ import '@material-ui/icons/Save';
97
+ import '@codemirror/view';
98
+ import '@material-ui/core/Card';
99
+ import '@material-ui/core/CardActionArea';
100
+ import '@material-ui/core/CardContent';
101
+ import '@material-ui/core/Tooltip';
102
+ import '@material-ui/icons/InfoOutlined';
103
+
104
+ const useStyles$5 = makeStyles((theme) => ({
105
+ cardHeader: {
106
+ position: "relative"
107
+ },
108
+ title: {
109
+ backgroundImage: ({ backgroundImage }) => backgroundImage
110
+ },
111
+ box: {
112
+ overflow: "hidden",
113
+ textOverflow: "ellipsis",
114
+ display: "-webkit-box",
115
+ "-webkit-line-clamp": 10,
116
+ "-webkit-box-orient": "vertical"
117
+ },
118
+ label: {
119
+ color: theme.palette.text.secondary,
120
+ textTransform: "uppercase",
121
+ fontSize: "0.65rem",
122
+ fontWeight: "bold",
123
+ letterSpacing: 0.5,
124
+ lineHeight: 1,
125
+ paddingBottom: "0.2rem"
126
+ },
127
+ linksLabel: {
128
+ padding: "0 16px"
129
+ },
130
+ description: {
131
+ "& p": {
132
+ margin: "0px"
133
+ }
134
+ },
135
+ leftButton: {
136
+ marginRight: "auto"
137
+ },
138
+ starButton: {
139
+ position: "absolute",
140
+ top: theme.spacing(0.5),
141
+ right: theme.spacing(0.5),
142
+ padding: "0.25rem",
143
+ color: "#fff"
144
+ }
145
+ }));
146
+ const MuiIcon = ({ icon: Icon }) => /* @__PURE__ */ React.createElement(Icon, null);
147
+ const useDeprecationStyles = makeStyles((theme) => ({
148
+ deprecationIcon: {
149
+ position: "absolute",
150
+ top: theme.spacing(0.5),
151
+ right: theme.spacing(3.5),
152
+ padding: "0.25rem"
153
+ },
154
+ link: {
155
+ color: theme.palette.warning.light
156
+ }
157
+ }));
158
+ const getTemplateCardProps = (template) => {
159
+ var _a, _b, _c, _d, _e, _f;
160
+ return {
161
+ key: template.metadata.uid,
162
+ name: template.metadata.name,
163
+ title: `${(_a = template.metadata.title || template.metadata.name) != null ? _a : ""}`,
164
+ type: (_b = template.spec.type) != null ? _b : "",
165
+ description: (_c = template.metadata.description) != null ? _c : "-",
166
+ tags: (_e = (_d = template.metadata) == null ? void 0 : _d.tags) != null ? _e : [],
167
+ links: (_f = template.metadata.links) != null ? _f : []
168
+ };
169
+ };
170
+ const DeprecationWarning = () => {
171
+ const styles = useDeprecationStyles();
172
+ const Title = /* @__PURE__ */ React.createElement(Typography, { style: { padding: 10, maxWidth: 300 } }, "This template uses a syntax that has been deprecated, and should be migrated to a newer syntax. Click for more info.");
173
+ return /* @__PURE__ */ React.createElement("div", { className: styles.deprecationIcon }, /* @__PURE__ */ React.createElement(Tooltip, { title: Title }, /* @__PURE__ */ React.createElement(
174
+ Link,
175
+ {
176
+ to: "https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3",
177
+ className: styles.link
178
+ },
179
+ /* @__PURE__ */ React.createElement(WarningIcon, null)
180
+ )));
181
+ };
182
+ const TemplateCard = ({ template, deprecated }) => {
183
+ var _a, _b, _c;
184
+ const app = useApp();
185
+ const backstageTheme = useTheme();
186
+ const templateRoute = useRouteRef(selectedTemplateRouteRef);
187
+ const templateProps = getTemplateCardProps(template);
188
+ const ownedByRelations = getEntityRelations(
189
+ template,
190
+ RELATION_OWNED_BY
191
+ );
192
+ const themeId = backstageTheme.getPageTheme({ themeId: templateProps.type }) ? templateProps.type : "other";
193
+ const theme = backstageTheme.getPageTheme({ themeId });
194
+ const classes = useStyles$5({ backgroundImage: theme.backgroundImage });
195
+ const { name, namespace } = parseEntityRef(stringifyEntityRef(template));
196
+ const href = templateRoute({ templateName: name, namespace });
197
+ const viewTechDoc = useRouteRef(viewTechDocRouteRef);
198
+ const viewTechDocsAnnotation = (_a = template.metadata.annotations) == null ? void 0 : _a["backstage.io/techdocs-ref"];
199
+ const viewTechDocsLink = !!viewTechDocsAnnotation && !!viewTechDoc && viewTechDoc({
200
+ namespace: template.metadata.namespace || DEFAULT_NAMESPACE,
201
+ kind: template.kind,
202
+ name: template.metadata.name
203
+ });
204
+ const iconResolver = (key) => {
205
+ var _a2;
206
+ return key ? (_a2 = app.getSystemIcon(key)) != null ? _a2 : LanguageIcon : LanguageIcon;
207
+ };
208
+ const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
209
+ const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi);
210
+ return /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardMedia, { className: classes.cardHeader }, /* @__PURE__ */ React.createElement(FavoriteEntity, { className: classes.starButton, entity: template }), deprecated && /* @__PURE__ */ React.createElement(DeprecationWarning, null), /* @__PURE__ */ React.createElement(
211
+ ItemCardHeader,
212
+ {
213
+ title: templateProps.title,
214
+ subtitle: templateProps.type,
215
+ classes: { root: classes.title }
216
+ }
217
+ )), /* @__PURE__ */ React.createElement(
218
+ CardContent,
219
+ {
220
+ style: { display: "flex", flexDirection: "column", gap: "16px" }
221
+ },
222
+ /* @__PURE__ */ React.createElement(Box, { className: classes.box }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.label }, "Description"), /* @__PURE__ */ React.createElement(
223
+ MarkdownContent,
224
+ {
225
+ className: classes.description,
226
+ content: templateProps.description
227
+ }
228
+ )),
229
+ /* @__PURE__ */ React.createElement(Box, { className: classes.box }, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.label }, "Owner"), /* @__PURE__ */ React.createElement(EntityRefLinks, { entityRefs: ownedByRelations, defaultKind: "Group" })),
230
+ /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(
231
+ Typography,
232
+ {
233
+ style: { marginBottom: "4px" },
234
+ variant: "body2",
235
+ className: classes.label
236
+ },
237
+ "Tags"
238
+ ), (_b = templateProps.tags) == null ? void 0 : _b.map((tag) => /* @__PURE__ */ React.createElement(Chip, { size: "small", label: tag, key: tag })))
239
+ ), /* @__PURE__ */ React.createElement(
240
+ Typography,
241
+ {
242
+ variant: "body2",
243
+ className: [classes.label, classes.linksLabel].join(" ")
244
+ },
245
+ "Links"
246
+ ), /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement("div", { className: classes.leftButton }, sourceLocation && /* @__PURE__ */ React.createElement(
247
+ Tooltip,
248
+ {
249
+ title: sourceLocation.integrationType || sourceLocation.locationTargetUrl
250
+ },
251
+ /* @__PURE__ */ React.createElement(
252
+ IconButton,
253
+ {
254
+ className: classes.leftButton,
255
+ href: sourceLocation.locationTargetUrl
256
+ },
257
+ /* @__PURE__ */ React.createElement(ScmIntegrationIcon, { type: sourceLocation.integrationType })
258
+ )
259
+ ), viewTechDocsLink && /* @__PURE__ */ React.createElement(Tooltip, { title: "View TechDocs" }, /* @__PURE__ */ React.createElement(
260
+ IconButton,
261
+ {
262
+ className: classes.leftButton,
263
+ href: viewTechDocsLink
264
+ },
265
+ /* @__PURE__ */ React.createElement(MuiIcon, { icon: iconResolver("docs") })
266
+ )), (_c = templateProps.links) == null ? void 0 : _c.map((link, i) => /* @__PURE__ */ React.createElement(Tooltip, { key: `${link.url}_${i}`, title: link.title || link.url }, /* @__PURE__ */ React.createElement(IconButton, { size: "medium", href: link.url }, /* @__PURE__ */ React.createElement(MuiIcon, { icon: iconResolver(link.icon) }))))), /* @__PURE__ */ React.createElement(
267
+ Button,
268
+ {
269
+ color: "primary",
270
+ to: href,
271
+ "aria-label": `Choose ${templateProps.title}`
272
+ },
273
+ "Choose"
274
+ )));
275
+ };
276
+
277
+ const TemplateList = ({
278
+ TemplateCardComponent,
279
+ group
280
+ }) => {
281
+ const { loading, error, entities } = useEntityList();
282
+ const Card = TemplateCardComponent || TemplateCard;
283
+ const maybeFilteredEntities = group ? entities.filter((e) => group.filter(e)) : entities;
284
+ const titleComponent = (() => {
285
+ if (group && group.title) {
286
+ if (typeof group.title === "string") {
287
+ return /* @__PURE__ */ React.createElement(ContentHeader, { title: group.title });
288
+ }
289
+ return group.title;
290
+ }
291
+ return /* @__PURE__ */ React.createElement(ContentHeader, { title: "Other Templates" });
292
+ })();
293
+ if (group && maybeFilteredEntities.length === 0) {
294
+ return null;
295
+ }
296
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(Progress, null), error && /* @__PURE__ */ React.createElement(WarningPanel, { title: "Oops! Something went wrong loading the templates" }, error.message), !error && !loading && !entities.length && /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "No templates found that match your filter. Learn more about", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://backstage.io/docs/features/software-templates/adding-templates" }, "adding templates"), "."), /* @__PURE__ */ React.createElement(Content, null, titleComponent, /* @__PURE__ */ React.createElement(ItemCardGrid, null, maybeFilteredEntities && (maybeFilteredEntities == null ? void 0 : maybeFilteredEntities.length) > 0 && maybeFilteredEntities.map((template) => /* @__PURE__ */ React.createElement(
297
+ Card,
298
+ {
299
+ key: stringifyEntityRef(template),
300
+ template,
301
+ deprecated: template.apiVersion === "backstage.io/v1beta2"
302
+ }
303
+ )))));
304
+ };
305
+
306
+ const icon = /* @__PURE__ */ React.createElement(CheckBoxOutlineBlankIcon, { fontSize: "small" });
307
+ const checkedIcon = /* @__PURE__ */ React.createElement(CheckBoxIcon, { fontSize: "small" });
308
+ const TemplateTypePicker = () => {
309
+ const alertApi = useApi(alertApiRef);
310
+ const { error, loading, availableTypes, selectedTypes, setSelectedTypes } = useEntityTypeFilter();
311
+ if (loading)
312
+ return /* @__PURE__ */ React.createElement(Progress, null);
313
+ if (!availableTypes)
314
+ return null;
315
+ if (error) {
316
+ alertApi.post({
317
+ message: `Failed to load entity types`,
318
+ severity: "error"
319
+ });
320
+ return null;
321
+ }
322
+ return /* @__PURE__ */ React.createElement(Box, { pb: 1, pt: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "button" }, "Categories"), /* @__PURE__ */ React.createElement(
323
+ Autocomplete,
324
+ {
325
+ multiple: true,
326
+ "aria-label": "Categories",
327
+ options: availableTypes,
328
+ value: selectedTypes,
329
+ onChange: (_, value) => setSelectedTypes(value),
330
+ renderOption: (option, { selected }) => /* @__PURE__ */ React.createElement(
331
+ FormControlLabel,
332
+ {
333
+ control: /* @__PURE__ */ React.createElement(
334
+ Checkbox,
335
+ {
336
+ icon,
337
+ checkedIcon,
338
+ checked: selected
339
+ }
340
+ ),
341
+ label: capitalize(option)
342
+ }
343
+ ),
344
+ size: "small",
345
+ popupIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, { "data-testid": "categories-picker-expand" }),
346
+ renderInput: (params) => /* @__PURE__ */ React.createElement(TextField, { ...params, variant: "outlined" })
347
+ }
348
+ ));
349
+ };
350
+
351
+ const useStyles$4 = makeStyles$1({
352
+ button: {
353
+ color: "white"
354
+ }
355
+ });
356
+ function ScaffolderPageContextMenu(props) {
357
+ const classes = useStyles$4();
358
+ const [anchorEl, setAnchorEl] = useState();
359
+ const editLink = useRouteRef(editRouteRef);
360
+ const actionsLink = useRouteRef(actionsRouteRef);
361
+ const tasksLink = useRouteRef(scaffolderListTaskRouteRef);
362
+ const navigate = useNavigate();
363
+ const showEditor = props.editor !== false;
364
+ const showActions = props.actions !== false;
365
+ const showTasks = props.tasks !== false;
366
+ if (!showEditor && !showActions) {
367
+ return null;
368
+ }
369
+ const onOpen = (event) => {
370
+ setAnchorEl(event.currentTarget);
371
+ };
372
+ const onClose = () => {
373
+ setAnchorEl(void 0);
374
+ };
375
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
376
+ IconButton$1,
377
+ {
378
+ "aria-label": "more",
379
+ "aria-controls": "long-menu",
380
+ "aria-haspopup": "true",
381
+ onClick: onOpen,
382
+ "data-testid": "menu-button",
383
+ color: "inherit",
384
+ className: classes.button
385
+ },
386
+ /* @__PURE__ */ React.createElement(MoreVert, null)
387
+ ), /* @__PURE__ */ React.createElement(
388
+ Popover,
389
+ {
390
+ open: Boolean(anchorEl),
391
+ onClose,
392
+ anchorEl,
393
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
394
+ transformOrigin: { vertical: "top", horizontal: "right" }
395
+ },
396
+ /* @__PURE__ */ React.createElement(MenuList, null, showEditor && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(editLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Edit, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Template Editor" })), showActions && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(actionsLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(Description, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Installed Actions" })), showTasks && /* @__PURE__ */ React.createElement(MenuItem, { onClick: () => navigate(tasksLink()) }, /* @__PURE__ */ React.createElement(ListItemIcon, null, /* @__PURE__ */ React.createElement(List, { fontSize: "small" })), /* @__PURE__ */ React.createElement(ListItemText, { primary: "Task List" })))
397
+ ));
398
+ }
399
+
400
+ const ScaffolderPageContents = ({
401
+ TemplateCardComponent,
402
+ groups,
403
+ contextMenu,
404
+ headerOptions
405
+ }) => {
406
+ const registerComponentLink = useRouteRef(registerComponentRouteRef);
407
+ const otherTemplatesGroup = {
408
+ title: groups ? "Other Templates" : "Templates",
409
+ filter: (entity) => {
410
+ const filtered = (groups != null ? groups : []).map((group) => group.filter(entity));
411
+ return !filtered.some((result) => result === true);
412
+ }
413
+ };
414
+ const { allowed } = usePermission({
415
+ permission: catalogEntityCreatePermission
416
+ });
417
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
418
+ Header,
419
+ {
420
+ pageTitleOverride: "Create a New Component",
421
+ title: "Create a New Component",
422
+ subtitle: "Create new software components using standard templates",
423
+ ...headerOptions
424
+ },
425
+ /* @__PURE__ */ React.createElement(ScaffolderPageContextMenu, { ...contextMenu })
426
+ ), /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ContentHeader, { title: "Available Templates" }, allowed && /* @__PURE__ */ React.createElement(
427
+ CreateButton,
428
+ {
429
+ title: "Register Existing Component",
430
+ to: registerComponentLink && registerComponentLink()
431
+ }
432
+ ), /* @__PURE__ */ React.createElement(SupportButton, null, "Create new software components using standard templates. Different templates create different kinds of components (services, websites, documentation, ...).")), /* @__PURE__ */ React.createElement(CatalogFilterLayout, null, /* @__PURE__ */ React.createElement(CatalogFilterLayout.Filters, null, /* @__PURE__ */ React.createElement(EntitySearchBar, null), /* @__PURE__ */ React.createElement(EntityKindPicker, { initialFilter: "template", hidden: true }), /* @__PURE__ */ React.createElement(
433
+ UserListPicker,
434
+ {
435
+ initialFilter: "all",
436
+ availableFilters: ["all", "starred"]
437
+ }
438
+ ), /* @__PURE__ */ React.createElement(TemplateTypePicker, null), /* @__PURE__ */ React.createElement(EntityTagPicker, null)), /* @__PURE__ */ React.createElement(CatalogFilterLayout.Content, null, groups && groups.map((group, index) => /* @__PURE__ */ React.createElement(
439
+ TemplateList,
440
+ {
441
+ key: index,
442
+ TemplateCardComponent,
443
+ group
444
+ }
445
+ )), /* @__PURE__ */ React.createElement(
446
+ TemplateList,
447
+ {
448
+ key: "other",
449
+ TemplateCardComponent,
450
+ group: otherTemplatesGroup
451
+ }
452
+ )))));
453
+ };
454
+ const ScaffolderPage = ({
455
+ TemplateCardComponent,
456
+ groups,
457
+ contextMenu,
458
+ headerOptions
459
+ }) => /* @__PURE__ */ React.createElement(EntityListProvider, null, /* @__PURE__ */ React.createElement(
460
+ ScaffolderPageContents,
461
+ {
462
+ TemplateCardComponent,
463
+ groups,
464
+ contextMenu,
465
+ headerOptions
466
+ }
467
+ ));
468
+
469
+ function isObject$1(value) {
470
+ return typeof value === "object" && value !== null && !Array.isArray(value);
471
+ }
472
+ function extractUiSchema(schema, uiSchema) {
473
+ if (!isObject$1(schema)) {
474
+ return;
475
+ }
476
+ const { properties, items, anyOf, oneOf, allOf, dependencies } = schema;
477
+ for (const propName in schema) {
478
+ if (!schema.hasOwnProperty(propName)) {
479
+ continue;
480
+ }
481
+ if (propName.startsWith("ui:")) {
482
+ uiSchema[propName] = schema[propName];
483
+ delete schema[propName];
484
+ }
485
+ }
486
+ if (isObject$1(properties)) {
487
+ for (const propName in properties) {
488
+ if (!properties.hasOwnProperty(propName)) {
489
+ continue;
490
+ }
491
+ const schemaNode = properties[propName];
492
+ if (!isObject$1(schemaNode)) {
493
+ continue;
494
+ }
495
+ const innerUiSchema = {};
496
+ uiSchema[propName] = uiSchema[propName] || innerUiSchema;
497
+ extractUiSchema(schemaNode, innerUiSchema);
498
+ }
499
+ }
500
+ if (isObject$1(items)) {
501
+ const innerUiSchema = {};
502
+ uiSchema.items = innerUiSchema;
503
+ extractUiSchema(items, innerUiSchema);
504
+ }
505
+ if (Array.isArray(anyOf)) {
506
+ for (const schemaNode of anyOf) {
507
+ if (!isObject$1(schemaNode)) {
508
+ continue;
509
+ }
510
+ extractUiSchema(schemaNode, uiSchema);
511
+ }
512
+ }
513
+ if (Array.isArray(oneOf)) {
514
+ for (const schemaNode of oneOf) {
515
+ if (!isObject$1(schemaNode)) {
516
+ continue;
517
+ }
518
+ extractUiSchema(schemaNode, uiSchema);
519
+ }
520
+ }
521
+ if (Array.isArray(allOf)) {
522
+ for (const schemaNode of allOf) {
523
+ if (!isObject$1(schemaNode)) {
524
+ continue;
525
+ }
526
+ extractUiSchema(schemaNode, uiSchema);
527
+ }
528
+ }
529
+ if (isObject$1(dependencies)) {
530
+ for (const depName of Object.keys(dependencies)) {
531
+ const schemaNode = dependencies[depName];
532
+ if (!isObject$1(schemaNode)) {
533
+ continue;
534
+ }
535
+ extractUiSchema(schemaNode, uiSchema);
536
+ }
537
+ }
538
+ }
539
+ function transformSchemaToProps(inputSchema, layouts = []) {
540
+ var _a;
541
+ const customLayoutName = inputSchema["ui:ObjectFieldTemplate"];
542
+ inputSchema.type = inputSchema.type || "object";
543
+ const schema = JSON.parse(JSON.stringify(inputSchema));
544
+ delete schema.title;
545
+ const uiSchema = {};
546
+ extractUiSchema(schema, uiSchema);
547
+ if (customLayoutName) {
548
+ const Layout = (_a = layouts.find(
549
+ (layout) => layout.name === customLayoutName
550
+ )) == null ? void 0 : _a.component;
551
+ if (Layout) {
552
+ uiSchema["ui:ObjectFieldTemplate"] = Layout;
553
+ }
554
+ }
555
+ return { schema, uiSchema };
556
+ }
557
+
558
+ const DescriptionField = ({ description }) => description && /* @__PURE__ */ React.createElement(MarkdownContent, { content: description, linkTarget: "_blank" });
559
+
560
+ var fieldOverrides = /*#__PURE__*/Object.freeze({
561
+ __proto__: null,
562
+ DescriptionField: DescriptionField
563
+ });
564
+
565
+ function getReviewData(formData, uiSchemas) {
566
+ const reviewData = {};
567
+ const orderedReviewProperties = new Set(
568
+ uiSchemas.map((us) => us.name).concat(Object.getOwnPropertyNames(formData))
569
+ );
570
+ for (const key of orderedReviewProperties) {
571
+ const uiSchema = uiSchemas.find((us) => us.name === key);
572
+ if (!uiSchema) {
573
+ reviewData[key] = formData[key];
574
+ continue;
575
+ }
576
+ if (uiSchema["ui:widget"] === "password") {
577
+ reviewData[key] = "******";
578
+ continue;
579
+ }
580
+ if (!uiSchema["ui:backstage"] || !uiSchema["ui:backstage"].review) {
581
+ reviewData[key] = formData[key];
582
+ continue;
583
+ }
584
+ const review = uiSchema["ui:backstage"].review;
585
+ if (review.mask) {
586
+ reviewData[key] = review.mask;
587
+ continue;
588
+ }
589
+ if (!review.show) {
590
+ continue;
591
+ }
592
+ reviewData[key] = formData[key];
593
+ }
594
+ return reviewData;
595
+ }
596
+ function getUiSchemasFromSteps(steps) {
597
+ const uiSchemas = [];
598
+ steps.forEach((step) => {
599
+ const schemaProps = step.schema.properties;
600
+ for (const key in schemaProps) {
601
+ if (schemaProps.hasOwnProperty(key)) {
602
+ const uiSchema = schemaProps[key];
603
+ uiSchema.name = key;
604
+ uiSchemas.push(uiSchema);
605
+ }
606
+ }
607
+ });
608
+ return uiSchemas;
609
+ }
610
+ const ReviewStep = (props) => {
611
+ const {
612
+ disableButtons,
613
+ formData,
614
+ handleBack,
615
+ handleCreate,
616
+ handleReset,
617
+ steps
618
+ } = props;
619
+ return /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Paper, { square: true, elevation: 0 }, /* @__PURE__ */ React.createElement(Typography, { variant: "h6" }, "Review and create"), /* @__PURE__ */ React.createElement(
620
+ StructuredMetadataTable,
621
+ {
622
+ dense: true,
623
+ metadata: getReviewData(
624
+ formData,
625
+ getUiSchemasFromSteps(
626
+ steps.map(({ mergedSchema }) => ({ schema: mergedSchema }))
627
+ )
628
+ )
629
+ }
630
+ ), /* @__PURE__ */ React.createElement(Box, { mb: 4 }), /* @__PURE__ */ React.createElement(Button$1, { onClick: handleBack, disabled: disableButtons }, "Back"), /* @__PURE__ */ React.createElement(Button$1, { onClick: handleReset, disabled: disableButtons }, "Reset"), /* @__PURE__ */ React.createElement(
631
+ Button$1,
632
+ {
633
+ variant: "contained",
634
+ color: "primary",
635
+ onClick: handleCreate,
636
+ disabled: disableButtons
637
+ },
638
+ "Create"
639
+ )));
640
+ };
641
+
642
+ const Form$1 = withTheme(Theme);
643
+ function getSchemasFromSteps(steps) {
644
+ return steps.map(({ schema }) => ({
645
+ mergedSchema: schema,
646
+ ...extractSchemaFromStep(schema)
647
+ }));
648
+ }
649
+ const MultistepJsonForm = (props) => {
650
+ const {
651
+ formData,
652
+ onChange,
653
+ onReset,
654
+ onFinish,
655
+ fields,
656
+ widgets,
657
+ layouts,
658
+ ReviewStepComponent
659
+ } = props;
660
+ const { templateName } = useRouteRefParams(selectedTemplateRouteRef);
661
+ const analytics = useAnalytics();
662
+ const [activeStep, setActiveStep] = useState(0);
663
+ const [disableButtons, setDisableButtons] = useState(false);
664
+ const errorApi = useApi(errorApiRef);
665
+ const featureFlagApi = useApi(featureFlagsApiRef);
666
+ const featureFlagKey = "backstage:featureFlag";
667
+ const filterOutProperties = (step) => {
668
+ var _a;
669
+ const filteredStep = cloneDeep(step);
670
+ const removedPropertyKeys = [];
671
+ if (filteredStep.schema.properties) {
672
+ filteredStep.schema.properties = Object.fromEntries(
673
+ Object.entries(filteredStep.schema.properties).filter(
674
+ ([key, value]) => {
675
+ if (value[featureFlagKey]) {
676
+ if (featureFlagApi.isActive(value[featureFlagKey])) {
677
+ return true;
678
+ }
679
+ removedPropertyKeys.push(key);
680
+ return false;
681
+ }
682
+ return true;
683
+ }
684
+ )
685
+ );
686
+ filteredStep.schema.required = Array.isArray(filteredStep.schema.required) ? (_a = filteredStep.schema.required) == null ? void 0 : _a.filter(
687
+ (r) => !removedPropertyKeys.includes(r)
688
+ ) : filteredStep.schema.required;
689
+ }
690
+ return filteredStep;
691
+ };
692
+ const steps = props.steps.filter((step) => {
693
+ const featureFlag = step.schema[featureFlagKey];
694
+ return typeof featureFlag !== "string" || featureFlagApi.isActive(featureFlag);
695
+ }).map(filterOutProperties);
696
+ const handleReset = () => {
697
+ setActiveStep(0);
698
+ onReset();
699
+ };
700
+ const handleNext = () => {
701
+ const stepNum = Math.min(activeStep + 1, steps.length);
702
+ setActiveStep(stepNum);
703
+ analytics.captureEvent("click", `Next Step (${stepNum})`);
704
+ };
705
+ const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0));
706
+ const handleCreate = async () => {
707
+ if (!onFinish) {
708
+ return;
709
+ }
710
+ setDisableButtons(true);
711
+ try {
712
+ await onFinish();
713
+ analytics.captureEvent("create", formData.name || `new ${templateName}`);
714
+ } catch (err) {
715
+ errorApi.post(err);
716
+ } finally {
717
+ setDisableButtons(false);
718
+ }
719
+ };
720
+ const ReviewStepElement = ReviewStepComponent != null ? ReviewStepComponent : ReviewStep;
721
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Stepper, { activeStep, orientation: "vertical" }, steps.map(({ title, schema, ...formProps }, index) => {
722
+ return /* @__PURE__ */ React.createElement(Step, { key: title }, /* @__PURE__ */ React.createElement(
723
+ StepLabel,
724
+ {
725
+ "aria-label": `Step ${index + 1} ${title}`,
726
+ "aria-disabled": "false",
727
+ tabIndex: 0
728
+ },
729
+ /* @__PURE__ */ React.createElement(Typography, { variant: "h6", component: "h3" }, title)
730
+ ), /* @__PURE__ */ React.createElement(StepContent, { key: title }, /* @__PURE__ */ React.createElement(
731
+ Form$1,
732
+ {
733
+ showErrorList: false,
734
+ fields: { ...fieldOverrides, ...fields },
735
+ widgets,
736
+ noHtml5Validate: true,
737
+ formData,
738
+ formContext: { formData },
739
+ onChange,
740
+ onSubmit: (e) => {
741
+ if (e.errors.length === 0)
742
+ handleNext();
743
+ },
744
+ ...formProps,
745
+ ...transformSchemaToProps(schema, layouts)
746
+ },
747
+ /* @__PURE__ */ React.createElement(Button$1, { disabled: activeStep === 0, onClick: handleBack }, "Back"),
748
+ /* @__PURE__ */ React.createElement(Button$1, { variant: "contained", color: "primary", type: "submit" }, "Next step")
749
+ )));
750
+ })), activeStep === steps.length && /* @__PURE__ */ React.createElement(
751
+ ReviewStepElement,
752
+ {
753
+ disableButtons,
754
+ handleBack,
755
+ handleCreate,
756
+ handleReset,
757
+ formData,
758
+ steps: getSchemasFromSteps(steps)
759
+ }
760
+ ));
761
+ };
762
+
763
+ function isObject(obj) {
764
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
765
+ }
766
+ function isArray(obj) {
767
+ return typeof obj === "object" && obj !== null && Array.isArray(obj);
768
+ }
769
+ const createValidator = (rootSchema, validators, context) => {
770
+ function validate(schema, formData, errors) {
771
+ const schemaProps = schema.properties;
772
+ const customObject = schema.type === "object" && schemaProps === void 0;
773
+ if (!isObject(schemaProps) && !customObject) {
774
+ return;
775
+ }
776
+ if (schemaProps) {
777
+ for (const [key, propData] of Object.entries(formData)) {
778
+ const propValidation = errors[key];
779
+ const propSchemaProps = schemaProps[key];
780
+ if (isObject(propData)) {
781
+ if (isObject(propSchemaProps)) {
782
+ validate(
783
+ propSchemaProps,
784
+ propData,
785
+ propValidation
786
+ );
787
+ }
788
+ } else if (isArray(propData)) {
789
+ if (isObject(propSchemaProps)) {
790
+ const { items } = propSchemaProps;
791
+ if (isObject(items)) {
792
+ const fieldName = items["ui:field"];
793
+ if (fieldName && typeof validators[fieldName] === "function") {
794
+ validators[fieldName](
795
+ propData,
796
+ propValidation,
797
+ context
798
+ );
799
+ }
800
+ }
801
+ }
802
+ } else {
803
+ const fieldName = isObject(propSchemaProps) && propSchemaProps["ui:field"];
804
+ if (fieldName && typeof validators[fieldName] === "function") {
805
+ validators[fieldName](
806
+ propData,
807
+ propValidation,
808
+ context
809
+ );
810
+ }
811
+ }
812
+ }
813
+ } else if (customObject) {
814
+ const fieldName = schema["ui:field"];
815
+ if (fieldName && typeof validators[fieldName] === "function") {
816
+ validators[fieldName](formData, errors, context);
817
+ }
818
+ }
819
+ }
820
+ return (formData, errors) => {
821
+ validate(rootSchema, formData, errors);
822
+ return errors;
823
+ };
824
+ };
825
+
826
+ const useTemplateParameterSchema = (templateRef) => {
827
+ const scaffolderApi = useApi(scaffolderApiRef);
828
+ const { value, loading, error } = useAsync(
829
+ () => scaffolderApi.getTemplateParameterSchema(templateRef),
830
+ [scaffolderApi, templateRef]
831
+ );
832
+ return { schema: value, loading, error };
833
+ };
834
+ const TemplatePage = ({
835
+ ReviewStepComponent,
836
+ customFieldExtensions = [],
837
+ layouts = [],
838
+ headerOptions
839
+ }) => {
840
+ const apiHolder = useApiHolder();
841
+ const secretsContext = useTemplateSecrets();
842
+ const errorApi = useApi(errorApiRef);
843
+ const scaffolderApi = useApi(scaffolderApiRef);
844
+ const { templateName, namespace } = useRouteRefParams(
845
+ selectedTemplateRouteRef
846
+ );
847
+ const templateRef = stringifyEntityRef({
848
+ name: templateName,
849
+ kind: "template",
850
+ namespace
851
+ });
852
+ const navigate = useNavigate();
853
+ const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef);
854
+ const rootRoute = useRouteRef(rootRouteRef);
855
+ const { schema, loading, error } = useTemplateParameterSchema(templateRef);
856
+ const [formState, setFormState] = useState(() => {
857
+ var _a;
858
+ const query = qs.parse(window.location.search, {
859
+ ignoreQueryPrefix: true
860
+ });
861
+ try {
862
+ return JSON.parse(query.formData);
863
+ } catch (e) {
864
+ return (_a = query.formData) != null ? _a : {};
865
+ }
866
+ });
867
+ const handleFormReset = () => setFormState({});
868
+ const handleChange = useCallback(
869
+ (e) => setFormState(e.formData),
870
+ [setFormState]
871
+ );
872
+ const handleCreate = async () => {
873
+ var _a;
874
+ const { taskId } = await scaffolderApi.scaffold({
875
+ templateRef,
876
+ values: formState,
877
+ secrets: secretsContext == null ? void 0 : secretsContext.secrets
878
+ });
879
+ const formParams = qs.stringify(
880
+ { formData: formState },
881
+ { addQueryPrefix: true }
882
+ );
883
+ const newUrl = `${window.location.pathname}${formParams}`;
884
+ (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
885
+ navigate(scaffolderTaskRoute({ taskId }));
886
+ };
887
+ if (error) {
888
+ errorApi.post(new Error(`Failed to load template, ${error}`));
889
+ return /* @__PURE__ */ React.createElement(Navigate, { to: rootRoute() });
890
+ }
891
+ if (!loading && !schema) {
892
+ errorApi.post(new Error("Template was not found."));
893
+ return /* @__PURE__ */ React.createElement(Navigate, { to: rootRoute() });
894
+ }
895
+ const customFieldComponents = Object.fromEntries(
896
+ customFieldExtensions.map(({ name, component }) => [name, component])
897
+ );
898
+ const customFieldValidators = Object.fromEntries(
899
+ customFieldExtensions.map(({ name, validation }) => [name, validation])
900
+ );
901
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes: { entityRef: templateRef } }, /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
902
+ Header,
903
+ {
904
+ pageTitleOverride: "Create a New Component",
905
+ title: "Create a New Component",
906
+ subtitle: "Create new software components using standard templates",
907
+ ...headerOptions
908
+ }
909
+ ), /* @__PURE__ */ React.createElement(Content, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, { "data-testid": "loading-progress" }), schema && /* @__PURE__ */ React.createElement(
910
+ InfoCard,
911
+ {
912
+ title: schema.title,
913
+ noPadding: true,
914
+ titleTypographyProps: { component: "h2" }
915
+ },
916
+ /* @__PURE__ */ React.createElement(
917
+ MultistepJsonForm,
918
+ {
919
+ ReviewStepComponent,
920
+ formData: formState,
921
+ fields: customFieldComponents,
922
+ onChange: handleChange,
923
+ onReset: handleFormReset,
924
+ onFinish: handleCreate,
925
+ layouts,
926
+ steps: schema.steps.map((step) => {
927
+ return {
928
+ ...step,
929
+ validate: createValidator(
930
+ step.schema,
931
+ customFieldValidators,
932
+ { apiHolder }
933
+ )
934
+ };
935
+ })
936
+ }
937
+ )
938
+ ))));
939
+ };
940
+
941
+ const useStyles$3 = makeStyles$1({
942
+ containerWrapper: {
943
+ position: "relative",
944
+ width: "100%",
945
+ height: "100%"
946
+ },
947
+ container: {
948
+ position: "absolute",
949
+ top: 0,
950
+ bottom: 0,
951
+ left: 0,
952
+ right: 0,
953
+ overflow: "auto"
954
+ }
955
+ });
956
+ class ErrorBoundary extends Component {
957
+ constructor() {
958
+ super(...arguments);
959
+ this.state = {
960
+ shouldRender: true
961
+ };
962
+ }
963
+ componentDidUpdate(prevProps) {
964
+ if (prevProps.invalidator !== this.props.invalidator) {
965
+ this.setState({ shouldRender: true });
966
+ }
967
+ }
968
+ componentDidCatch(error) {
969
+ this.props.setErrorText(error.message);
970
+ this.setState({ shouldRender: false });
971
+ }
972
+ render() {
973
+ return this.state.shouldRender ? this.props.children : null;
974
+ }
975
+ }
976
+ function isJsonObject(value) {
977
+ return typeof value === "object" && value !== null && !Array.isArray(value);
978
+ }
979
+ function TemplateEditorForm(props) {
980
+ const {
981
+ content,
982
+ contentIsSpec,
983
+ data,
984
+ onUpdate,
985
+ onDryRun,
986
+ setErrorText,
987
+ fieldExtensions = [],
988
+ layouts = []
989
+ } = props;
990
+ const classes = useStyles$3();
991
+ const apiHolder = useApiHolder();
992
+ const [steps, setSteps] = useState();
993
+ const fields = useMemo(() => {
994
+ return Object.fromEntries(
995
+ fieldExtensions.map(({ name, component }) => [name, component])
996
+ );
997
+ }, [fieldExtensions]);
998
+ useDebounce(
999
+ () => {
1000
+ try {
1001
+ if (!content) {
1002
+ setSteps(void 0);
1003
+ return;
1004
+ }
1005
+ const parsed = yaml.parse(content);
1006
+ if (!isJsonObject(parsed)) {
1007
+ setSteps(void 0);
1008
+ return;
1009
+ }
1010
+ let rootObj = parsed;
1011
+ if (!contentIsSpec) {
1012
+ const isTemplate = String(parsed.kind).toLocaleLowerCase("en-US") === "template";
1013
+ if (!isTemplate) {
1014
+ setSteps(void 0);
1015
+ return;
1016
+ }
1017
+ rootObj = isJsonObject(parsed.spec) ? parsed.spec : {};
1018
+ }
1019
+ const { parameters } = rootObj;
1020
+ if (!Array.isArray(parameters)) {
1021
+ setErrorText("Template parameters must be an array");
1022
+ setSteps(void 0);
1023
+ return;
1024
+ }
1025
+ const fieldValidators = Object.fromEntries(
1026
+ fieldExtensions.map(({ name, validation }) => [name, validation])
1027
+ );
1028
+ setErrorText();
1029
+ setSteps(
1030
+ parameters.flatMap(
1031
+ (param) => isJsonObject(param) ? [
1032
+ {
1033
+ title: String(param.title),
1034
+ schema: param,
1035
+ validate: createValidator(param, fieldValidators, {
1036
+ apiHolder
1037
+ })
1038
+ }
1039
+ ] : []
1040
+ )
1041
+ );
1042
+ } catch (e) {
1043
+ setErrorText(e.message);
1044
+ }
1045
+ },
1046
+ 250,
1047
+ [contentIsSpec, content, apiHolder]
1048
+ );
1049
+ if (!steps) {
1050
+ return null;
1051
+ }
1052
+ return /* @__PURE__ */ React.createElement("div", { className: classes.containerWrapper }, /* @__PURE__ */ React.createElement("div", { className: classes.container }, /* @__PURE__ */ React.createElement(ErrorBoundary, { invalidator: steps, setErrorText }, /* @__PURE__ */ React.createElement(
1053
+ MultistepJsonForm,
1054
+ {
1055
+ steps,
1056
+ fields,
1057
+ formData: data,
1058
+ onChange: (e) => onUpdate(e.formData),
1059
+ onReset: () => onUpdate({}),
1060
+ finishButtonLabel: onDryRun && "Try It",
1061
+ onFinish: onDryRun && (() => onDryRun(data)),
1062
+ layouts
1063
+ }
1064
+ ))));
1065
+ }
1066
+ function TemplateEditorFormDirectoryEditorDryRun(props) {
1067
+ const { setErrorText, fieldExtensions = [], layouts } = props;
1068
+ const dryRun = useDryRun();
1069
+ const directoryEditor = useDirectoryEditor();
1070
+ const { selectedFile } = directoryEditor;
1071
+ const [data, setData] = useState({});
1072
+ const handleDryRun = async () => {
1073
+ if (!selectedFile) {
1074
+ return;
1075
+ }
1076
+ try {
1077
+ await dryRun.execute({
1078
+ templateContent: selectedFile.content,
1079
+ values: data,
1080
+ files: directoryEditor.files
1081
+ });
1082
+ setErrorText();
1083
+ } catch (e) {
1084
+ setErrorText(String(e.cause || e));
1085
+ throw e;
1086
+ }
1087
+ };
1088
+ const content = selectedFile && selectedFile.path.match(/\.ya?ml$/) ? selectedFile.content : void 0;
1089
+ return /* @__PURE__ */ React.createElement(
1090
+ TemplateEditorForm,
1091
+ {
1092
+ onDryRun: handleDryRun,
1093
+ fieldExtensions,
1094
+ setErrorText,
1095
+ content,
1096
+ data,
1097
+ onUpdate: setData,
1098
+ layouts
1099
+ }
1100
+ );
1101
+ }
1102
+ TemplateEditorForm.DirectoryEditorDryRun = TemplateEditorFormDirectoryEditorDryRun;
1103
+
1104
+ const Form = withTheme(Theme);
1105
+ const useStyles$2 = makeStyles((theme) => ({
1106
+ root: {
1107
+ gridArea: "pageContent",
1108
+ display: "grid",
1109
+ gridTemplateAreas: `
1110
+ "controls controls"
1111
+ "fieldForm preview"
1112
+ `,
1113
+ gridTemplateRows: "auto 1fr",
1114
+ gridTemplateColumns: "1fr 1fr"
1115
+ },
1116
+ controls: {
1117
+ gridArea: "controls",
1118
+ display: "flex",
1119
+ flexFlow: "row nowrap",
1120
+ alignItems: "center",
1121
+ margin: theme.spacing(1)
1122
+ },
1123
+ fieldForm: {
1124
+ gridArea: "fieldForm"
1125
+ },
1126
+ preview: {
1127
+ gridArea: "preview"
1128
+ }
1129
+ }));
1130
+ const CustomFieldExplorer = ({
1131
+ customFieldExtensions = [],
1132
+ onClose
1133
+ }) => {
1134
+ var _a, _b;
1135
+ const classes = useStyles$2();
1136
+ const fieldOptions = customFieldExtensions.filter((field) => !!field.schema);
1137
+ const [selectedField, setSelectedField] = useState(fieldOptions[0]);
1138
+ const [fieldFormState, setFieldFormState] = useState({});
1139
+ const [formState, setFormState] = useState({});
1140
+ const [refreshKey, setRefreshKey] = useState(Date.now());
1141
+ const sampleFieldTemplate = useMemo(
1142
+ () => {
1143
+ var _a2, _b2;
1144
+ return yaml.stringify({
1145
+ parameters: [
1146
+ {
1147
+ title: `${selectedField.name} Example`,
1148
+ properties: {
1149
+ [selectedField.name]: {
1150
+ type: (_b2 = (_a2 = selectedField.schema) == null ? void 0 : _a2.returnValue) == null ? void 0 : _b2.type,
1151
+ "ui:field": selectedField.name,
1152
+ "ui:options": fieldFormState
1153
+ }
1154
+ }
1155
+ }
1156
+ ]
1157
+ });
1158
+ },
1159
+ [fieldFormState, selectedField]
1160
+ );
1161
+ const fieldComponents = useMemo(() => {
1162
+ return Object.fromEntries(
1163
+ customFieldExtensions.map(({ name, component }) => [name, component])
1164
+ );
1165
+ }, [customFieldExtensions]);
1166
+ const handleSelectionChange = useCallback(
1167
+ (selection) => {
1168
+ setSelectedField(selection);
1169
+ setFieldFormState({});
1170
+ setFormState({});
1171
+ },
1172
+ [setFieldFormState, setFormState, setSelectedField]
1173
+ );
1174
+ const handleFieldConfigChange = useCallback(
1175
+ (state) => {
1176
+ setFieldFormState(state);
1177
+ setFormState({});
1178
+ setRefreshKey(Date.now());
1179
+ },
1180
+ [setFieldFormState, setRefreshKey]
1181
+ );
1182
+ return /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-field-label" }, "Choose Custom Field Extension"), /* @__PURE__ */ React.createElement(
1183
+ Select,
1184
+ {
1185
+ value: selectedField,
1186
+ label: "Choose Custom Field Extension",
1187
+ labelId: "select-field-label",
1188
+ onChange: (e) => handleSelectionChange(e.target.value)
1189
+ },
1190
+ fieldOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option }, option.name))
1191
+ )), /* @__PURE__ */ React.createElement(IconButton, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.fieldForm }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Field Options" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1192
+ Form,
1193
+ {
1194
+ showErrorList: false,
1195
+ fields: { ...fieldOverrides, ...fieldComponents },
1196
+ noHtml5Validate: true,
1197
+ formData: fieldFormState,
1198
+ formContext: { fieldFormState },
1199
+ onSubmit: (e) => handleFieldConfigChange(e.formData),
1200
+ schema: ((_a = selectedField.schema) == null ? void 0 : _a.uiOptions) || {}
1201
+ },
1202
+ /* @__PURE__ */ React.createElement(
1203
+ Button$1,
1204
+ {
1205
+ variant: "contained",
1206
+ color: "primary",
1207
+ type: "submit",
1208
+ disabled: !((_b = selectedField.schema) == null ? void 0 : _b.uiOptions)
1209
+ },
1210
+ "Apply"
1211
+ )
1212
+ )))), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardHeader, { title: "Example Template Spec" }), /* @__PURE__ */ React.createElement(CardContent, null, /* @__PURE__ */ React.createElement(
1213
+ CodeMirror,
1214
+ {
1215
+ readOnly: true,
1216
+ theme: "dark",
1217
+ height: "100%",
1218
+ extensions: [StreamLanguage.define(yaml$1)],
1219
+ value: sampleFieldTemplate
1220
+ }
1221
+ ))), /* @__PURE__ */ React.createElement(
1222
+ TemplateEditorForm,
1223
+ {
1224
+ key: refreshKey,
1225
+ content: sampleFieldTemplate,
1226
+ contentIsSpec: true,
1227
+ fieldExtensions: customFieldExtensions,
1228
+ data: formState,
1229
+ onUpdate: setFormState,
1230
+ setErrorText: () => null
1231
+ }
1232
+ )));
1233
+ };
1234
+
1235
+ const useStyles$1 = makeStyles({
1236
+ // Reset and fix sizing to make sure scrolling behaves correctly
1237
+ root: {
1238
+ gridArea: "pageContent",
1239
+ display: "grid",
1240
+ gridTemplateAreas: `
1241
+ "browser editor preview"
1242
+ "results results results"
1243
+ `,
1244
+ gridTemplateColumns: "1fr 3fr 2fr",
1245
+ gridTemplateRows: "1fr auto"
1246
+ },
1247
+ browser: {
1248
+ gridArea: "browser",
1249
+ overflow: "auto"
1250
+ },
1251
+ editor: {
1252
+ gridArea: "editor",
1253
+ overflow: "auto"
1254
+ },
1255
+ preview: {
1256
+ gridArea: "preview",
1257
+ overflow: "auto"
1258
+ },
1259
+ results: {
1260
+ gridArea: "results"
1261
+ }
1262
+ });
1263
+ const TemplateEditor = (props) => {
1264
+ const classes = useStyles$1();
1265
+ const [errorText, setErrorText] = useState();
1266
+ return /* @__PURE__ */ React.createElement(DirectoryEditorProvider, { directory: props.directory }, /* @__PURE__ */ React.createElement(DryRunProvider, null, /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("section", { className: classes.browser }, /* @__PURE__ */ React.createElement(TemplateEditorBrowser, { onClose: props.onClose })), /* @__PURE__ */ React.createElement("section", { className: classes.editor }, /* @__PURE__ */ React.createElement(TemplateEditorTextArea.DirectoryEditor, { errorText })), /* @__PURE__ */ React.createElement("section", { className: classes.preview }, /* @__PURE__ */ React.createElement(
1267
+ TemplateEditorForm.DirectoryEditorDryRun,
1268
+ {
1269
+ setErrorText,
1270
+ fieldExtensions: props.fieldExtensions,
1271
+ layouts: props.layouts
1272
+ }
1273
+ )), /* @__PURE__ */ React.createElement("section", { className: classes.results }, /* @__PURE__ */ React.createElement(DryRunResults, null)))));
1274
+ };
1275
+
1276
+ const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
1277
+ parameters:
1278
+ - title: Fill in some steps
1279
+ required:
1280
+ - name
1281
+ properties:
1282
+ name:
1283
+ title: Name
1284
+ type: string
1285
+ description: Unique name of the component
1286
+ owner:
1287
+ title: Owner
1288
+ type: string
1289
+ description: Owner of the component
1290
+ ui:field: OwnerPicker
1291
+ ui:options:
1292
+ catalogFilter:
1293
+ kind: Group
1294
+ - title: Choose a location
1295
+ required:
1296
+ - repoUrl
1297
+ properties:
1298
+ repoUrl:
1299
+ title: Repository Location
1300
+ type: string
1301
+ ui:field: RepoUrlPicker
1302
+ ui:options:
1303
+ allowedHosts:
1304
+ - github.com
1305
+ steps:
1306
+ - id: fetch-base
1307
+ name: Fetch Base
1308
+ action: fetch:template
1309
+ input:
1310
+ url: ./template
1311
+ values:
1312
+ name: \${{parameters.name}}
1313
+ `;
1314
+ const useStyles = makeStyles((theme) => ({
1315
+ root: {
1316
+ gridArea: "pageContent",
1317
+ display: "grid",
1318
+ gridTemplateAreas: `
1319
+ "controls controls"
1320
+ "textArea preview"
1321
+ `,
1322
+ gridTemplateRows: "auto 1fr",
1323
+ gridTemplateColumns: "1fr 1fr"
1324
+ },
1325
+ controls: {
1326
+ gridArea: "controls",
1327
+ display: "flex",
1328
+ flexFlow: "row nowrap",
1329
+ alignItems: "center",
1330
+ margin: theme.spacing(1)
1331
+ },
1332
+ textArea: {
1333
+ gridArea: "textArea"
1334
+ },
1335
+ preview: {
1336
+ gridArea: "preview"
1337
+ }
1338
+ }));
1339
+ const TemplateFormPreviewer = ({
1340
+ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
1341
+ customFieldExtensions = [],
1342
+ onClose,
1343
+ layouts = []
1344
+ }) => {
1345
+ const classes = useStyles();
1346
+ const alertApi = useApi(alertApiRef);
1347
+ const catalogApi = useApi(catalogApiRef);
1348
+ const [selectedTemplate, setSelectedTemplate] = useState("");
1349
+ const [errorText, setErrorText] = useState();
1350
+ const [templateOptions, setTemplateOptions] = useState([]);
1351
+ const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
1352
+ const [formState, setFormState] = useState({});
1353
+ const { loading } = useAsync(
1354
+ () => catalogApi.getEntities({
1355
+ filter: { kind: "template" },
1356
+ fields: [
1357
+ "kind",
1358
+ "metadata.namespace",
1359
+ "metadata.name",
1360
+ "metadata.title",
1361
+ "spec.parameters",
1362
+ "spec.steps",
1363
+ "spec.output"
1364
+ ]
1365
+ }).then(
1366
+ ({ items }) => setTemplateOptions(
1367
+ items.map((template) => {
1368
+ var _a;
1369
+ return {
1370
+ label: (_a = template.metadata.title) != null ? _a : humanizeEntityRef(template, { defaultKind: "template" }),
1371
+ value: template
1372
+ };
1373
+ })
1374
+ )
1375
+ ).catch(
1376
+ (e) => alertApi.post({
1377
+ message: `Error loading exisiting templates: ${e.message}`,
1378
+ severity: "error"
1379
+ })
1380
+ ),
1381
+ [catalogApi]
1382
+ );
1383
+ const handleSelectChange = useCallback(
1384
+ (selected) => {
1385
+ setSelectedTemplate(selected);
1386
+ setTemplateYaml(yaml.stringify(selected.spec));
1387
+ },
1388
+ [setTemplateYaml]
1389
+ );
1390
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, loading && /* @__PURE__ */ React.createElement(LinearProgress, null), /* @__PURE__ */ React.createElement("main", { className: classes.root }, /* @__PURE__ */ React.createElement("div", { className: classes.controls }, /* @__PURE__ */ React.createElement(FormControl, { variant: "outlined", size: "small", fullWidth: true }, /* @__PURE__ */ React.createElement(InputLabel, { id: "select-template-label" }, "Load Existing Template"), /* @__PURE__ */ React.createElement(
1391
+ Select,
1392
+ {
1393
+ value: selectedTemplate,
1394
+ label: "Load Existing Template",
1395
+ labelId: "select-template-label",
1396
+ onChange: (e) => handleSelectChange(e.target.value)
1397
+ },
1398
+ templateOptions.map((option, idx) => /* @__PURE__ */ React.createElement(MenuItem$1, { key: idx, value: option.value }, option.label))
1399
+ )), /* @__PURE__ */ React.createElement(IconButton, { size: "medium", onClick: onClose }, /* @__PURE__ */ React.createElement(CloseIcon, null))), /* @__PURE__ */ React.createElement("div", { className: classes.textArea }, /* @__PURE__ */ React.createElement(
1400
+ TemplateEditorTextArea,
1401
+ {
1402
+ content: templateYaml,
1403
+ onUpdate: setTemplateYaml,
1404
+ errorText
1405
+ }
1406
+ )), /* @__PURE__ */ React.createElement("div", { className: classes.preview }, /* @__PURE__ */ React.createElement(
1407
+ TemplateEditorForm,
1408
+ {
1409
+ content: templateYaml,
1410
+ contentIsSpec: true,
1411
+ fieldExtensions: customFieldExtensions,
1412
+ data: formState,
1413
+ onUpdate: setFormState,
1414
+ setErrorText,
1415
+ layouts
1416
+ }
1417
+ ))));
1418
+ };
1419
+
1420
+ function TemplateEditorPage(props) {
1421
+ const [selection, setSelection] = useState();
1422
+ let content = null;
1423
+ if ((selection == null ? void 0 : selection.type) === "local") {
1424
+ content = /* @__PURE__ */ React.createElement(
1425
+ TemplateEditor,
1426
+ {
1427
+ directory: selection.directory,
1428
+ fieldExtensions: props.customFieldExtensions,
1429
+ onClose: () => setSelection(void 0),
1430
+ layouts: props.layouts
1431
+ }
1432
+ );
1433
+ } else if ((selection == null ? void 0 : selection.type) === "form") {
1434
+ content = /* @__PURE__ */ React.createElement(
1435
+ TemplateFormPreviewer,
1436
+ {
1437
+ defaultPreviewTemplate: props.defaultPreviewTemplate,
1438
+ customFieldExtensions: props.customFieldExtensions,
1439
+ onClose: () => setSelection(void 0),
1440
+ layouts: props.layouts
1441
+ }
1442
+ );
1443
+ } else if ((selection == null ? void 0 : selection.type) === "field-explorer") {
1444
+ content = /* @__PURE__ */ React.createElement(
1445
+ CustomFieldExplorer,
1446
+ {
1447
+ customFieldExtensions: props.customFieldExtensions,
1448
+ onClose: () => setSelection(void 0)
1449
+ }
1450
+ );
1451
+ } else {
1452
+ content = /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(
1453
+ TemplateEditorIntro,
1454
+ {
1455
+ onSelect: (option) => {
1456
+ if (option === "local") {
1457
+ WebFileSystemAccess.requestDirectoryAccess().then((directory) => setSelection({ type: "local", directory })).catch(() => {
1458
+ });
1459
+ } else if (option === "form") {
1460
+ setSelection({ type: "form" });
1461
+ } else if (option === "field-explorer") {
1462
+ setSelection({ type: "field-explorer" });
1463
+ }
1464
+ }
1465
+ }
1466
+ ));
1467
+ }
1468
+ return /* @__PURE__ */ React.createElement(Page, { themeId: "home" }, /* @__PURE__ */ React.createElement(
1469
+ Header,
1470
+ {
1471
+ title: "Template Editor",
1472
+ subtitle: "Edit, preview, and try out templates and template forms"
1473
+ }
1474
+ ), content);
1475
+ }
1476
+
1477
+ const Router = (props) => {
1478
+ const { groups, components = {}, defaultPreviewTemplate } = props;
1479
+ const { ReviewStepComponent, TemplateCardComponent, TaskPageComponent } = components;
1480
+ const outlet = useOutlet();
1481
+ const TaskPageElement = TaskPageComponent != null ? TaskPageComponent : TaskPage;
1482
+ const customFieldExtensions = useCustomFieldExtensions(outlet);
1483
+ const fieldExtensions = [
1484
+ ...customFieldExtensions,
1485
+ ...DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS.filter(
1486
+ ({ name }) => !customFieldExtensions.some(
1487
+ (customFieldExtension) => customFieldExtension.name === name
1488
+ )
1489
+ )
1490
+ ];
1491
+ const customLayouts = useCustomLayouts(outlet);
1492
+ const RedirectingComponent = () => {
1493
+ const { templateName } = useRouteRefParams(legacySelectedTemplateRouteRef);
1494
+ const newLink = useRouteRef(selectedTemplateRouteRef);
1495
+ useEffect(
1496
+ () => (
1497
+ // eslint-disable-next-line no-console
1498
+ console.warn(
1499
+ "The route /template/:templateName is deprecated, please use the new /template/:namespace/:templateName route instead"
1500
+ )
1501
+ ),
1502
+ []
1503
+ );
1504
+ return /* @__PURE__ */ React.createElement(Navigate, { to: newLink({ namespace: "default", templateName }) });
1505
+ };
1506
+ return /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(
1507
+ Route,
1508
+ {
1509
+ path: "/",
1510
+ element: /* @__PURE__ */ React.createElement(
1511
+ ScaffolderPage,
1512
+ {
1513
+ groups,
1514
+ TemplateCardComponent,
1515
+ contextMenu: props.contextMenu,
1516
+ headerOptions: props.headerOptions
1517
+ }
1518
+ )
1519
+ }
1520
+ ), /* @__PURE__ */ React.createElement(
1521
+ Route,
1522
+ {
1523
+ path: legacySelectedTemplateRouteRef.path,
1524
+ element: /* @__PURE__ */ React.createElement(RedirectingComponent, null)
1525
+ }
1526
+ ), /* @__PURE__ */ React.createElement(
1527
+ Route,
1528
+ {
1529
+ path: selectedTemplateRouteRef.path,
1530
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
1531
+ TemplatePage,
1532
+ {
1533
+ ReviewStepComponent,
1534
+ customFieldExtensions: fieldExtensions,
1535
+ layouts: customLayouts,
1536
+ headerOptions: props.headerOptions
1537
+ }
1538
+ ))
1539
+ }
1540
+ ), /* @__PURE__ */ React.createElement(
1541
+ Route,
1542
+ {
1543
+ path: scaffolderListTaskRouteRef.path,
1544
+ element: /* @__PURE__ */ React.createElement(ListTasksPage, null)
1545
+ }
1546
+ ), /* @__PURE__ */ React.createElement(Route, { path: scaffolderTaskRouteRef.path, element: /* @__PURE__ */ React.createElement(TaskPageElement, null) }), /* @__PURE__ */ React.createElement(Route, { path: actionsRouteRef.path, element: /* @__PURE__ */ React.createElement(ActionsPage, null) }), /* @__PURE__ */ React.createElement(
1547
+ Route,
1548
+ {
1549
+ path: editRouteRef.path,
1550
+ element: /* @__PURE__ */ React.createElement(SecretsContextProvider, null, /* @__PURE__ */ React.createElement(
1551
+ TemplateEditorPage,
1552
+ {
1553
+ defaultPreviewTemplate,
1554
+ customFieldExtensions: fieldExtensions,
1555
+ layouts: customLayouts
1556
+ }
1557
+ ))
1558
+ }
1559
+ ), /* @__PURE__ */ React.createElement(Route, { path: "preview", element: /* @__PURE__ */ React.createElement(Navigate, { to: "../edit" }) }));
1560
+ };
1561
+
1562
+ export { Router };
1563
+ //# sourceMappingURL=Router-0b12a83d.esm.js.map