@spacelift-io/backstage-integration-frontend 0.0.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.
package/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # Spacelift Frontend Plugin
2
+
3
+ This frontend plugin for Backstage provides a user interface to view and interact with your Spacelift stacks and runs.
4
+
5
+ ## Installation
6
+
7
+ 1. Install the plugin package in your Backstage frontend app:
8
+
9
+ ```bash
10
+ # From your Backstage root directory
11
+ yarn --cwd packages/app add @spacelift/backstage-integration-frontend
12
+ ```
13
+
14
+ 2. Add the plugin to your `packages/app/src/App.tsx`:
15
+
16
+ ```tsx
17
+ // packages/app/src/App.tsx
18
+ import { SpaceliftIoPage } from '@spacelift/backstage-integration-frontend';
19
+
20
+ // ...
21
+
22
+ const routes = (
23
+ <FlatRoutes>
24
+ {/* ...other routes */}
25
+ <Route path="/spacelift" element={<SpaceliftIoPage />} />
26
+ </FlatRoutes>
27
+ );
28
+ ```
29
+
30
+ 3. Add the plugin to the sidebar in `packages/app/src/components/Root/Root.tsx`:
31
+
32
+ ```tsx
33
+ // packages/app/src/components/Root/Root.tsx
34
+ import SpaceliftIcon from '@material-ui/icons/CloudQueue'; // Example icon, choose an appropriate one
35
+
36
+ // ...
37
+
38
+ export const Root = ({ children }: PropsWithChildren<{}>) => (
39
+ <SidebarPage>
40
+ <Sidebar>
41
+ {/* ...other sidebar items */}
42
+ <SidebarItem icon={SpaceliftIcon} to="spacelift" text="Spacelift" />
43
+ </Sidebar>
44
+ {/* ... */}
45
+ </SidebarPage>
46
+ );
47
+ ```
48
+
49
+ ## Configuration
50
+
51
+ This plugin requires the `spacelift.hostUrl` to be configured in your `app-config.yaml` to allow the frontend to make requests to the Spacelift API via the backend plugin.
52
+
53
+ ```yaml
54
+ spacelift:
55
+ hostUrl: 'https://<your-subdomain>.app.spacelift.io' # Your Spacelift instance URL
56
+ ```
57
+
58
+ Make sure to replace `<your-subdomain>` with your actual Spacelift subdomain.
59
+
60
+ ### Important Note on Permissions
61
+
62
+ This frontend plugin relies on the permissions configured for the Spacelift API Key in the backend plugin. It does not implement separate user-level permission checks within the frontend components.
63
+
64
+ Ensure that your Backstage instance has appropriate general permissions set up to control access to this plugin's pages and functionalities. This is crucial to prevent users from performing actions in Spacelift for which they are not authorized via the configured API key.
65
+
66
+ ## Backend Plugin
67
+
68
+ This frontend plugin requires the [Spacelift Backend Plugin](../spacelift-io-backend/README.md) to be installed and configured.
69
+
70
+ ## Spacelift Documentation
71
+
72
+ For more information about Spacelift, please refer to the [official Spacelift documentation](https://docs.spacelift.io/).
package/config.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export interface Config {
2
+ /**
3
+ * @visibility frontend
4
+ */
5
+ spacelift: {
6
+ /**
7
+ * @visibility frontend
8
+ */
9
+ hostUrl: string;
10
+ };
11
+ }
@@ -0,0 +1,34 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+
3
+ const spaceliftApiRef = createApiRef({
4
+ id: "plugin.spacelift.io.service"
5
+ });
6
+ class SpaceliftApi {
7
+ constructor(discoveryApi, fetchApi) {
8
+ this.discoveryApi = discoveryApi;
9
+ this.fetchApi = fetchApi;
10
+ }
11
+ async getStacks() {
12
+ const url = await this.discoveryApi.getBaseUrl("spacelift-io");
13
+ const response = await this.fetchApi.fetch(`${url}/stacks`);
14
+ if (!response.ok) {
15
+ const { error } = await response.json();
16
+ throw new Error(error);
17
+ }
18
+ return response.json();
19
+ }
20
+ async triggerRun(stackId) {
21
+ const url = await this.discoveryApi.getBaseUrl("spacelift-io");
22
+ const response = await this.fetchApi.fetch(`${url}/stacks/${stackId}/trigger`, {
23
+ method: "POST"
24
+ });
25
+ if (!response.ok) {
26
+ const { error } = await response.json();
27
+ throw new Error(error);
28
+ }
29
+ return response.json();
30
+ }
31
+ }
32
+
33
+ export { SpaceliftApi, spaceliftApiRef };
34
+ //# sourceMappingURL=SpaceliftApiClient.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpaceliftApiClient.esm.js","sources":["../../src/api/SpaceliftApiClient.ts"],"sourcesContent":["import { createApiRef, DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { Stack, TriggerRunResponse } from '../types';\n\nexport interface ISpaceliftApi {\n getStacks(): Promise<Stack[]>;\n triggerRun(stackId: string): Promise<TriggerRunResponse>;\n}\n\nexport const spaceliftApiRef = createApiRef<ISpaceliftApi>({\n id: 'plugin.spacelift.io.service',\n});\n\nexport class SpaceliftApi implements ISpaceliftApi {\n constructor(private discoveryApi: DiscoveryApi, private fetchApi: FetchApi) {}\n\n async getStacks(): Promise<Stack[]> {\n const url = await this.discoveryApi.getBaseUrl('spacelift-io');\n\n const response = await this.fetchApi.fetch(`${url}/stacks`);\n if (!response.ok) {\n const { error } = await response.json();\n throw new Error(error);\n }\n return response.json();\n }\n\n async triggerRun(stackId: string): Promise<TriggerRunResponse> {\n const url = await this.discoveryApi.getBaseUrl('spacelift-io');\n\n const response = await this.fetchApi.fetch(`${url}/stacks/${stackId}/trigger`, {\n method: 'POST',\n });\n if (!response.ok) {\n const { error } = await response.json();\n throw new Error(error);\n }\n return response.json();\n }\n}\n"],"names":[],"mappings":";;AAQO,MAAM,kBAAkB,YAA4B,CAAA;AAAA,EACzD,EAAI,EAAA;AACN,CAAC;AAEM,MAAM,YAAsC,CAAA;AAAA,EACjD,WAAA,CAAoB,cAAoC,QAAoB,EAAA;AAAxD,IAAA,IAAA,CAAA,YAAA,GAAA,YAAA;AAAoC,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAAA;AAAqB,EAE7E,MAAM,SAA8B,GAAA;AAClC,IAAA,MAAM,GAAM,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,cAAc,CAAA;AAE7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAM,CAAA,CAAA,EAAG,GAAG,CAAS,OAAA,CAAA,CAAA;AAC1D,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,SAAS,IAAK,EAAA;AACtC,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA;AAAA;AAEvB,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AACvB,EAEA,MAAM,WAAW,OAA8C,EAAA;AAC7D,IAAA,MAAM,GAAM,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,cAAc,CAAA;AAE7D,IAAM,MAAA,QAAA,GAAW,MAAM,IAAK,CAAA,QAAA,CAAS,MAAM,CAAG,EAAA,GAAG,CAAW,QAAA,EAAA,OAAO,CAAY,QAAA,CAAA,EAAA;AAAA,MAC7E,MAAQ,EAAA;AAAA,KACT,CAAA;AACD,IAAI,IAAA,CAAC,SAAS,EAAI,EAAA;AAChB,MAAA,MAAM,EAAE,KAAA,EAAU,GAAA,MAAM,SAAS,IAAK,EAAA;AACtC,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA;AAAA;AAEvB,IAAA,OAAO,SAAS,IAAK,EAAA;AAAA;AAEzB;;;;"}
@@ -0,0 +1,144 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { Table, MarkdownContent } from '@backstage/core-components';
3
+ import { useApi, configApiRef } from '@backstage/core-plugin-api';
4
+ import { makeStyles, Drawer, Box, DialogTitle, IconButton, Link, Chip, Tooltip, Typography } from '@material-ui/core';
5
+ import CloseIcon from '@material-ui/icons/Close';
6
+ import RepeatIcon from '@material-ui/icons/RepeatRounded';
7
+ import { useState } from 'react';
8
+ import { DESCRIPTION_DRAWER_WIDTH, ALLOW_RETRY_STATES, DESCRIPTION_TRUNCATE_LENGTH } from '../../constants.esm.js';
9
+
10
+ const useStyles = makeStyles((theme) => ({
11
+ neutral: {
12
+ backgroundColor: theme.palette.grey[500]
13
+ },
14
+ info: {
15
+ backgroundColor: theme.palette.info.main
16
+ },
17
+ success: {
18
+ backgroundColor: theme.palette.success.main
19
+ },
20
+ danger: {
21
+ backgroundColor: theme.palette.error.main
22
+ },
23
+ warning: {
24
+ backgroundColor: theme.palette.warning.main
25
+ }
26
+ }));
27
+ const renderState = (classes, state) => {
28
+ const stateToClass = {
29
+ APPLYING: classes.info,
30
+ CONFIRMED: classes.warning,
31
+ DESTROYING: classes.info,
32
+ DISCARDED: classes.danger,
33
+ FAILED: classes.danger,
34
+ FINISHED: classes.success,
35
+ INITIALIZING: classes.info,
36
+ NONE: classes.neutral,
37
+ PLANNING: classes.info,
38
+ PREPARING_APPLY: classes.info,
39
+ PREPARING_REPLAN: classes.info,
40
+ PREPARING: classes.info,
41
+ REPLAN_REQUESTED: classes.neutral,
42
+ STOPPED: classes.danger,
43
+ UNCONFIRMED: classes.warning
44
+ };
45
+ const className = stateToClass[state] || classes.neutral;
46
+ return /* @__PURE__ */ jsx(Chip, { size: "small", label: state, className });
47
+ };
48
+ const renderDescription = (description, onExpand) => {
49
+ if (!description) return /* @__PURE__ */ jsx("span", { children: "-" });
50
+ return description.length > DESCRIPTION_TRUNCATE_LENGTH ? /* @__PURE__ */ jsxs(Typography, { variant: "inherit", onClick: () => onExpand(description), children: [
51
+ description.slice(0, DESCRIPTION_TRUNCATE_LENGTH),
52
+ " ..."
53
+ ] }) : description;
54
+ };
55
+ const SpaceliftStacksTable = ({
56
+ stacks,
57
+ loading,
58
+ triggerRun
59
+ }) => {
60
+ const [description, setDescription] = useState(null);
61
+ const classes = useStyles();
62
+ const config = useApi(configApiRef);
63
+ const hostUrl = config.getString("spacelift.hostUrl");
64
+ const columns = [
65
+ {
66
+ title: "Name",
67
+ field: "name",
68
+ highlight: true,
69
+ render: (row) => /* @__PURE__ */ jsx(Link, { href: `https://${hostUrl}/stack/${row.id}`, target: "_blank", children: row.name })
70
+ },
71
+ {
72
+ title: "Description",
73
+ field: "description",
74
+ render: (row) => renderDescription(row.description, setDescription)
75
+ },
76
+ {
77
+ title: "Labels",
78
+ field: "labels",
79
+ render: (row) => {
80
+ if (!row.labels.length) return /* @__PURE__ */ jsx("span", { children: "-" });
81
+ return /* @__PURE__ */ jsx(Box, { display: "flex", flexWrap: "wrap", children: row.labels.map((label) => /* @__PURE__ */ jsx(Chip, { label, style: { margin: "0.25rem" }, size: "small" }, label)) });
82
+ }
83
+ },
84
+ {
85
+ title: "Current State",
86
+ render: (row) => renderState(classes, row.state)
87
+ },
88
+ {
89
+ title: "Branch",
90
+ field: "branch"
91
+ },
92
+ {
93
+ title: "Space",
94
+ render: (row) => /* @__PURE__ */ jsx(Link, { href: `https://${hostUrl}/spaces/${row.spaceDetails.name}`, target: "_blank", children: row.spaceDetails.name })
95
+ },
96
+ {
97
+ width: "30px",
98
+ render: (row) => {
99
+ const showAction = ALLOW_RETRY_STATES.includes(row.state);
100
+ if (!showAction) return null;
101
+ return /* @__PURE__ */ jsx(Tooltip, { title: "Trigger run", children: /* @__PURE__ */ jsx(IconButton, { onClick: () => triggerRun(row.id), children: /* @__PURE__ */ jsx(RepeatIcon, {}) }) });
102
+ }
103
+ }
104
+ ];
105
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
106
+ /* @__PURE__ */ jsx(
107
+ Table,
108
+ {
109
+ title: "Spacelift Stacks",
110
+ options: {
111
+ search: true,
112
+ paging: true,
113
+ pageSize: 20,
114
+ padding: "dense"
115
+ },
116
+ columns,
117
+ data: stacks,
118
+ isLoading: loading,
119
+ emptyContent: "No stacks found"
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsxs(Drawer, { anchor: "right", open: !!description, onClose: () => setDescription(null), children: [
123
+ /* @__PURE__ */ jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [
124
+ /* @__PURE__ */ jsx(
125
+ DialogTitle,
126
+ {
127
+ id: "dialog-title",
128
+ style: {
129
+ padding: "0 1rem",
130
+ display: "flex",
131
+ justifyContent: "space-between"
132
+ },
133
+ children: "Stack description"
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx(IconButton, { "aria-label": "close", onClick: () => setDescription(null), children: /* @__PURE__ */ jsx(CloseIcon, {}) })
137
+ ] }),
138
+ /* @__PURE__ */ jsx(Box, { width: DESCRIPTION_DRAWER_WIDTH, padding: 2, style: { paddingTop: 0 }, children: /* @__PURE__ */ jsx(MarkdownContent, { content: description ?? "" }) })
139
+ ] })
140
+ ] });
141
+ };
142
+
143
+ export { SpaceliftStacksTable };
144
+ //# sourceMappingURL=SpaceliftStacksTable.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpaceliftStacksTable.esm.js","sources":["../../../src/components/SpaceliftStacksTable/SpaceliftStacksTable.tsx"],"sourcesContent":["import { MarkdownContent, Table, TableColumn } from '@backstage/core-components';\nimport { configApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n Box,\n Chip,\n DialogTitle,\n Drawer,\n IconButton,\n Link,\n makeStyles,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport CloseIcon from '@material-ui/icons/Close';\nimport RepeatIcon from '@material-ui/icons/RepeatRounded';\n\nimport { useState } from 'react';\nimport {\n ALLOW_RETRY_STATES,\n DESCRIPTION_DRAWER_WIDTH,\n DESCRIPTION_TRUNCATE_LENGTH,\n} from '../../constants';\nimport { Stack, StackState } from '../../types';\n\nconst useStyles = makeStyles(theme => ({\n neutral: {\n backgroundColor: theme.palette.grey[500],\n },\n info: {\n backgroundColor: theme.palette.info.main,\n },\n success: {\n backgroundColor: theme.palette.success.main,\n },\n danger: {\n backgroundColor: theme.palette.error.main,\n },\n warning: {\n backgroundColor: theme.palette.warning.main,\n },\n}));\n\n// Helper function to render stack state with appropriate styling\nconst renderState = (classes: ReturnType<typeof useStyles>, state: StackState) => {\n const stateToClass: Record<StackState, string> = {\n APPLYING: classes.info,\n CONFIRMED: classes.warning,\n DESTROYING: classes.info,\n DISCARDED: classes.danger,\n FAILED: classes.danger,\n FINISHED: classes.success,\n INITIALIZING: classes.info,\n NONE: classes.neutral,\n PLANNING: classes.info,\n PREPARING_APPLY: classes.info,\n PREPARING_REPLAN: classes.info,\n PREPARING: classes.info,\n REPLAN_REQUESTED: classes.neutral,\n STOPPED: classes.danger,\n UNCONFIRMED: classes.warning,\n };\n\n const className = stateToClass[state] || classes.neutral;\n return <Chip size=\"small\" label={state} className={className} />;\n};\n\nconst renderDescription = (\n description: string | null | undefined,\n onExpand: (description: string | null) => void\n) => {\n if (!description) return <span>-</span>;\n return description.length > DESCRIPTION_TRUNCATE_LENGTH ? (\n <Typography variant=\"inherit\" onClick={() => onExpand(description)}>\n {description.slice(0, DESCRIPTION_TRUNCATE_LENGTH)} ...\n </Typography>\n ) : (\n description\n );\n};\n\ntype SpaceliftStacksTableProps = {\n stacks: Stack[];\n loading: boolean;\n triggerRun: (stackId: string) => void;\n};\n\nexport const SpaceliftStacksTable = ({\n stacks,\n loading,\n triggerRun,\n}: SpaceliftStacksTableProps) => {\n const [description, setDescription] = useState<string | null>(null);\n const classes = useStyles();\n const config = useApi(configApiRef);\n const hostUrl = config.getString('spacelift.hostUrl');\n\n const columns: TableColumn<Stack>[] = [\n {\n title: 'Name',\n field: 'name',\n highlight: true,\n render: (row: Stack) => (\n <Link href={`https://${hostUrl}/stack/${row.id}`} target=\"_blank\">\n {row.name}\n </Link>\n ),\n },\n {\n title: 'Description',\n field: 'description',\n render: (row: Stack) => renderDescription(row.description, setDescription),\n },\n {\n title: 'Labels',\n field: 'labels',\n render: (row: Stack) => {\n if (!row.labels.length) return <span>-</span>;\n return (\n <Box display=\"flex\" flexWrap=\"wrap\">\n {row.labels.map(label => (\n <Chip key={label} label={label} style={{ margin: '0.25rem' }} size=\"small\" />\n ))}\n </Box>\n );\n },\n },\n {\n title: 'Current State',\n render: (row: Stack) => renderState(classes, row.state),\n },\n {\n title: 'Branch',\n field: 'branch',\n },\n {\n title: 'Space',\n render: (row: Stack) => (\n <Link href={`https://${hostUrl}/spaces/${row.spaceDetails.name}`} target=\"_blank\">\n {row.spaceDetails.name}\n </Link>\n ),\n },\n {\n width: '30px',\n render: (row: Stack) => {\n const showAction = ALLOW_RETRY_STATES.includes(row.state);\n if (!showAction) return null;\n return (\n <Tooltip title=\"Trigger run\">\n <IconButton onClick={() => triggerRun(row.id)}>\n <RepeatIcon />\n </IconButton>\n </Tooltip>\n );\n },\n },\n ];\n\n return (\n <>\n <Table\n title=\"Spacelift Stacks\"\n options={{\n search: true,\n paging: true,\n pageSize: 20,\n padding: 'dense',\n }}\n columns={columns}\n data={stacks}\n isLoading={loading}\n emptyContent=\"No stacks found\"\n />\n <Drawer anchor=\"right\" open={!!description} onClose={() => setDescription(null)}>\n <Box display=\"flex\" justifyContent=\"space-between\" alignItems=\"center\">\n <DialogTitle\n id=\"dialog-title\"\n style={{\n padding: '0 1rem',\n display: 'flex',\n justifyContent: 'space-between',\n }}\n >\n Stack description\n </DialogTitle>\n <IconButton aria-label=\"close\" onClick={() => setDescription(null)}>\n <CloseIcon />\n </IconButton>\n </Box>\n <Box width={DESCRIPTION_DRAWER_WIDTH} padding={2} style={{ paddingTop: 0 }}>\n <MarkdownContent content={description ?? ''} />\n </Box>\n </Drawer>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { Page, Header, HeaderLabel, Content } from '@backstage/core-components';
3
+ import { Box, Button } from '@material-ui/core';
4
+ import { Alert } from '@material-ui/lab';
5
+ import { useFetchStacks } from '../../hooks/useFetchStacks.esm.js';
6
+ import { useTriggerRun } from '../../hooks/useTriggerRun.esm.js';
7
+ import { SpaceliftStacksTable } from '../SpaceliftStacksTable/SpaceliftStacksTable.esm.js';
8
+
9
+ const StacksPage = () => {
10
+ const { stacks, loading: stacksLoading, error: stacksError, retry: getStacks } = useFetchStacks();
11
+ const {
12
+ triggerRun,
13
+ loading: loadingTriggerRun,
14
+ error: errorTriggerRun,
15
+ clear: clearTriggerRunError
16
+ } = useTriggerRun();
17
+ const handleTriggerRun = async (stackId) => {
18
+ const res = await triggerRun(stackId);
19
+ if (res?.id) {
20
+ getStacks();
21
+ }
22
+ };
23
+ const isLoading = stacksLoading || loadingTriggerRun;
24
+ return /* @__PURE__ */ jsxs(Page, { themeId: "tool", children: [
25
+ /* @__PURE__ */ jsx(Header, { title: "Spacelift", subtitle: "Manage your stacks", children: /* @__PURE__ */ jsx(HeaderLabel, { label: "Stacks", value: "List of stacks" }) }),
26
+ /* @__PURE__ */ jsx(Content, { children: /* @__PURE__ */ jsxs(Box, { display: "flex", flexDirection: "column", style: { gap: "16px" }, children: [
27
+ stacksError && /* @__PURE__ */ jsxs(
28
+ Alert,
29
+ {
30
+ variant: "outlined",
31
+ severity: "error",
32
+ action: /* @__PURE__ */ jsx(Button, { color: "inherit", size: "small", onClick: getStacks, children: "RETRY" }),
33
+ children: [
34
+ "Failed to load Spacelift stacks: ",
35
+ stacksError.message
36
+ ]
37
+ }
38
+ ),
39
+ !stacksError && errorTriggerRun && /* @__PURE__ */ jsxs(Alert, { variant: "outlined", severity: "warning", onClose: clearTriggerRunError, children: [
40
+ "Spacelift action failed: ",
41
+ errorTriggerRun.message
42
+ ] }),
43
+ /* @__PURE__ */ jsx(SpaceliftStacksTable, { stacks, loading: isLoading, triggerRun: handleTriggerRun })
44
+ ] }) })
45
+ ] });
46
+ };
47
+
48
+ export { StacksPage };
49
+ //# sourceMappingURL=Stacks.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Stacks.esm.js","sources":["../../../src/components/Stacks/Stacks.tsx"],"sourcesContent":["import { Content, Header, HeaderLabel, Page } from '@backstage/core-components';\nimport { Box, Button } from '@material-ui/core';\nimport { Alert } from '@material-ui/lab';\nimport { useFetchStacks } from '../../hooks/useFetchStacks';\nimport { useTriggerRun } from '../../hooks/useTriggerRun';\nimport { SpaceliftStacksTable } from '../SpaceliftStacksTable';\n\nexport const StacksPage = () => {\n const { stacks, loading: stacksLoading, error: stacksError, retry: getStacks } = useFetchStacks();\n const {\n triggerRun,\n loading: loadingTriggerRun,\n error: errorTriggerRun,\n clear: clearTriggerRunError,\n } = useTriggerRun();\n\n const handleTriggerRun = async (stackId: string) => {\n const res = await triggerRun(stackId);\n if (res?.id) {\n getStacks();\n }\n };\n\n const isLoading = stacksLoading || loadingTriggerRun;\n\n return (\n <Page themeId=\"tool\">\n <Header title=\"Spacelift\" subtitle=\"Manage your stacks\">\n <HeaderLabel label=\"Stacks\" value=\"List of stacks\" />\n </Header>\n <Content>\n <Box display=\"flex\" flexDirection=\"column\" style={{ gap: '16px' }}>\n {stacksError && (\n <Alert\n variant=\"outlined\"\n severity=\"error\"\n action={\n <Button color=\"inherit\" size=\"small\" onClick={getStacks}>\n RETRY\n </Button>\n }\n >\n Failed to load Spacelift stacks: {stacksError.message}\n </Alert>\n )}\n {!stacksError && errorTriggerRun && (\n <Alert variant=\"outlined\" severity=\"warning\" onClose={clearTriggerRunError}>\n Spacelift action failed: {errorTriggerRun.message}\n </Alert>\n )}\n <SpaceliftStacksTable stacks={stacks} loading={isLoading} triggerRun={handleTriggerRun} />\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":";;;;;;;;AAOO,MAAM,aAAa,MAAM;AAC9B,EAAM,MAAA,EAAE,QAAQ,OAAS,EAAA,aAAA,EAAe,OAAO,WAAa,EAAA,KAAA,EAAO,SAAU,EAAA,GAAI,cAAe,EAAA;AAChG,EAAM,MAAA;AAAA,IACJ,UAAA;AAAA,IACA,OAAS,EAAA,iBAAA;AAAA,IACT,KAAO,EAAA,eAAA;AAAA,IACP,KAAO,EAAA;AAAA,MACL,aAAc,EAAA;AAElB,EAAM,MAAA,gBAAA,GAAmB,OAAO,OAAoB,KAAA;AAClD,IAAM,MAAA,GAAA,GAAM,MAAM,UAAA,CAAW,OAAO,CAAA;AACpC,IAAA,IAAI,KAAK,EAAI,EAAA;AACX,MAAU,SAAA,EAAA;AAAA;AACZ,GACF;AAEA,EAAA,MAAM,YAAY,aAAiB,IAAA,iBAAA;AAEnC,EACE,uBAAA,IAAA,CAAC,IAAK,EAAA,EAAA,OAAA,EAAQ,MACZ,EAAA,QAAA,EAAA;AAAA,oBAAC,GAAA,CAAA,MAAA,EAAA,EAAO,KAAM,EAAA,WAAA,EAAY,QAAS,EAAA,oBAAA,EACjC,QAAC,kBAAA,GAAA,CAAA,WAAA,EAAA,EAAY,KAAM,EAAA,QAAA,EAAS,KAAM,EAAA,gBAAA,EAAiB,CACrD,EAAA,CAAA;AAAA,oBACC,GAAA,CAAA,OAAA,EAAA,EACC,QAAC,kBAAA,IAAA,CAAA,GAAA,EAAA,EAAI,OAAQ,EAAA,MAAA,EAAO,aAAc,EAAA,QAAA,EAAS,KAAO,EAAA,EAAE,GAAK,EAAA,MAAA,EACtD,EAAA,QAAA,EAAA;AAAA,MACC,WAAA,oBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,OAAQ,EAAA,UAAA;AAAA,UACR,QAAS,EAAA,OAAA;AAAA,UACT,MAAA,sBACG,MAAO,EAAA,EAAA,KAAA,EAAM,WAAU,IAAK,EAAA,OAAA,EAAQ,OAAS,EAAA,SAAA,EAAW,QAEzD,EAAA,OAAA,EAAA,CAAA;AAAA,UAEH,QAAA,EAAA;AAAA,YAAA,mCAAA;AAAA,YACmC,WAAY,CAAA;AAAA;AAAA;AAAA,OAChD;AAAA,MAED,CAAC,WAAe,IAAA,eAAA,oBACd,IAAA,CAAA,KAAA,EAAA,EAAM,SAAQ,UAAW,EAAA,QAAA,EAAS,SAAU,EAAA,OAAA,EAAS,oBAAsB,EAAA,QAAA,EAAA;AAAA,QAAA,2BAAA;AAAA,QAChD,eAAgB,CAAA;AAAA,OAC5C,EAAA,CAAA;AAAA,0BAED,oBAAqB,EAAA,EAAA,MAAA,EAAgB,OAAS,EAAA,SAAA,EAAW,YAAY,gBAAkB,EAAA;AAAA,KAAA,EAC1F,CACF,EAAA;AAAA,GACF,EAAA,CAAA;AAEJ;;;;"}
@@ -0,0 +1,2 @@
1
+ export { StacksPage } from './Stacks.esm.js';
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,7 @@
1
+ const POLL_INTERVAL = 1e4;
2
+ const ALLOW_RETRY_STATES = ["DISCARDED", "FAILED", "STOPPED", "FINISHED", "NONE"];
3
+ const DESCRIPTION_TRUNCATE_LENGTH = 50;
4
+ const DESCRIPTION_DRAWER_WIDTH = 400;
5
+
6
+ export { ALLOW_RETRY_STATES, DESCRIPTION_DRAWER_WIDTH, DESCRIPTION_TRUNCATE_LENGTH, POLL_INTERVAL };
7
+ //# sourceMappingURL=constants.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.esm.js","sources":["../src/constants.ts"],"sourcesContent":["// Spacelift Plugin Constants\n\n// For useFetchStacks.ts\nexport const POLL_INTERVAL = 10000; // 10 seconds\n\n// For SpaceliftStacksTable.tsx\nexport const ALLOW_RETRY_STATES = ['DISCARDED', 'FAILED', 'STOPPED', 'FINISHED', 'NONE'];\n\nexport const DESCRIPTION_TRUNCATE_LENGTH = 50;\nexport const DESCRIPTION_DRAWER_WIDTH = 400;\n"],"names":[],"mappings":"AAGO,MAAM,aAAgB,GAAA;AAGtB,MAAM,qBAAqB,CAAC,WAAA,EAAa,QAAU,EAAA,SAAA,EAAW,YAAY,MAAM;AAEhF,MAAM,2BAA8B,GAAA;AACpC,MAAM,wBAA2B,GAAA;;;;"}
@@ -0,0 +1,42 @@
1
+ import { useApi } from '@backstage/core-plugin-api';
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ import { spaceliftApiRef } from '../api/SpaceliftApiClient.esm.js';
4
+ import { POLL_INTERVAL } from '../constants.esm.js';
5
+
6
+ const useFetchStacks = () => {
7
+ const spaceliftApi = useApi(spaceliftApiRef);
8
+ const [stacks, setStacks] = useState([]);
9
+ const [loading, setLoading] = useState(true);
10
+ const [error, setError] = useState(null);
11
+ const fetchStacks = useCallback(async () => {
12
+ try {
13
+ const response = await spaceliftApi.getStacks();
14
+ setStacks(response);
15
+ setError(null);
16
+ } catch (err) {
17
+ setError(err);
18
+ setStacks([]);
19
+ } finally {
20
+ setLoading(false);
21
+ }
22
+ }, [spaceliftApi]);
23
+ useEffect(() => {
24
+ const poll = setInterval(() => {
25
+ fetchStacks();
26
+ }, POLL_INTERVAL);
27
+ fetchStacks();
28
+ return () => {
29
+ clearInterval(poll);
30
+ };
31
+ }, [fetchStacks]);
32
+ return {
33
+ stacks,
34
+ loading,
35
+ error,
36
+ retry: fetchStacks,
37
+ clear: () => setError(null)
38
+ };
39
+ };
40
+
41
+ export { useFetchStacks };
42
+ //# sourceMappingURL=useFetchStacks.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFetchStacks.esm.js","sources":["../../src/hooks/useFetchStacks.ts"],"sourcesContent":["import { useApi } from '@backstage/core-plugin-api';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { ISpaceliftApi, spaceliftApiRef } from '../api/SpaceliftApiClient';\nimport { POLL_INTERVAL } from '../constants';\nimport { Stack } from '../types';\n\nexport const useFetchStacks = () => {\n const spaceliftApi = useApi<ISpaceliftApi>(spaceliftApiRef);\n const [stacks, setStacks] = useState<Stack[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchStacks = useCallback(async () => {\n try {\n const response = await spaceliftApi.getStacks();\n setStacks(response);\n setError(null);\n } catch (err) {\n setError(err as Error);\n setStacks([]);\n } finally {\n setLoading(false);\n }\n }, [spaceliftApi]);\n\n useEffect(() => {\n const poll = setInterval(() => {\n fetchStacks();\n }, POLL_INTERVAL);\n\n fetchStacks();\n\n return () => {\n clearInterval(poll);\n };\n }, [fetchStacks]);\n\n return {\n stacks,\n loading,\n error,\n retry: fetchStacks,\n clear: () => setError(null),\n };\n};\n"],"names":[],"mappings":";;;;;AAOO,MAAM,iBAAiB,MAAM;AAClC,EAAM,MAAA,YAAA,GAAe,OAAsB,eAAe,CAAA;AAC1D,EAAA,MAAM,CAAC,MAAQ,EAAA,SAAS,CAAI,GAAA,QAAA,CAAkB,EAAE,CAAA;AAChD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAM,MAAA,WAAA,GAAc,YAAY,YAAY;AAC1C,IAAI,IAAA;AACF,MAAM,MAAA,QAAA,GAAW,MAAM,YAAA,CAAa,SAAU,EAAA;AAC9C,MAAA,SAAA,CAAU,QAAQ,CAAA;AAClB,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,aACN,GAAK,EAAA;AACZ,MAAA,QAAA,CAAS,GAAY,CAAA;AACrB,MAAA,SAAA,CAAU,EAAE,CAAA;AAAA,KACZ,SAAA;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,GACF,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,SAAA,CAAU,MAAM;AACd,IAAM,MAAA,IAAA,GAAO,YAAY,MAAM;AAC7B,MAAY,WAAA,EAAA;AAAA,OACX,aAAa,CAAA;AAEhB,IAAY,WAAA,EAAA;AAEZ,IAAA,OAAO,MAAM;AACX,MAAA,aAAA,CAAc,IAAI,CAAA;AAAA,KACpB;AAAA,GACF,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAO,OAAA;AAAA,IACL,MAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAO,EAAA,WAAA;AAAA,IACP,KAAA,EAAO,MAAM,QAAA,CAAS,IAAI;AAAA,GAC5B;AACF;;;;"}
@@ -0,0 +1,29 @@
1
+ import { useApi } from '@backstage/core-plugin-api';
2
+ import { useState, useCallback } from 'react';
3
+ import { spaceliftApiRef } from '../api/SpaceliftApiClient.esm.js';
4
+
5
+ const useTriggerRun = () => {
6
+ const [loading, setLoading] = useState(false);
7
+ const [error, setError] = useState(null);
8
+ const spaceliftApi = useApi(spaceliftApiRef);
9
+ const triggerRun = useCallback(
10
+ async (stackId) => {
11
+ setLoading(true);
12
+ setError(null);
13
+ try {
14
+ const response = await spaceliftApi.triggerRun(stackId);
15
+ return response;
16
+ } catch (err) {
17
+ setError(err);
18
+ return null;
19
+ } finally {
20
+ setLoading(false);
21
+ }
22
+ },
23
+ [spaceliftApi]
24
+ );
25
+ return { triggerRun, loading, error, clear: () => setError(null) };
26
+ };
27
+
28
+ export { useTriggerRun };
29
+ //# sourceMappingURL=useTriggerRun.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTriggerRun.esm.js","sources":["../../src/hooks/useTriggerRun.ts"],"sourcesContent":["import { useApi } from '@backstage/core-plugin-api';\nimport { useCallback, useState } from 'react';\nimport { ISpaceliftApi, spaceliftApiRef } from '../api/SpaceliftApiClient';\n\nexport const useTriggerRun = () => {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const spaceliftApi = useApi<ISpaceliftApi>(spaceliftApiRef);\n\n const triggerRun = useCallback(\n async (stackId: string) => {\n setLoading(true);\n setError(null);\n try {\n const response = await spaceliftApi.triggerRun(stackId);\n return response;\n } catch (err) {\n setError(err as Error);\n return null;\n } finally {\n setLoading(false);\n }\n },\n [spaceliftApi]\n );\n\n return { triggerRun, loading, error, clear: () => setError(null) };\n};\n"],"names":[],"mappings":";;;;AAIO,MAAM,gBAAgB,MAAM;AACjC,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAuB,IAAI,CAAA;AAErD,EAAM,MAAA,YAAA,GAAe,OAAsB,eAAe,CAAA;AAE1D,EAAA,MAAM,UAAa,GAAA,WAAA;AAAA,IACjB,OAAO,OAAoB,KAAA;AACzB,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,QAAA,CAAS,IAAI,CAAA;AACb,MAAI,IAAA;AACF,QAAA,MAAM,QAAW,GAAA,MAAM,YAAa,CAAA,UAAA,CAAW,OAAO,CAAA;AACtD,QAAO,OAAA,QAAA;AAAA,eACA,GAAK,EAAA;AACZ,QAAA,QAAA,CAAS,GAAY,CAAA;AACrB,QAAO,OAAA,IAAA;AAAA,OACP,SAAA;AACA,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA;AAClB,KACF;AAAA,IACA,CAAC,YAAY;AAAA,GACf;AAEA,EAAO,OAAA,EAAE,YAAY,OAAS,EAAA,KAAA,EAAO,OAAO,MAAM,QAAA,CAAS,IAAI,CAAE,EAAA;AACnE;;;;"}
@@ -0,0 +1,9 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
3
+
4
+ declare const spaceliftIoPlugin: _backstage_core_plugin_api.BackstagePlugin<{
5
+ root: _backstage_core_plugin_api.RouteRef<undefined>;
6
+ }, {}, {}>;
7
+ declare const SpaceliftIoPage: () => react_jsx_runtime.JSX.Element;
8
+
9
+ export { SpaceliftIoPage, spaceliftIoPlugin };
@@ -0,0 +1,2 @@
1
+ export { SpaceliftIoPage, spaceliftIoPlugin } from './plugin.esm.js';
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ import { createPlugin, createApiFactory, fetchApiRef, discoveryApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
2
+ import { SpaceliftApi, spaceliftApiRef } from './api/SpaceliftApiClient.esm.js';
3
+ import { rootRouteRef } from './routes.esm.js';
4
+
5
+ const spaceliftIoPlugin = createPlugin({
6
+ id: "spacelift",
7
+ routes: {
8
+ root: rootRouteRef
9
+ },
10
+ apis: [
11
+ createApiFactory({
12
+ api: spaceliftApiRef,
13
+ deps: {
14
+ discoveryApi: discoveryApiRef,
15
+ fetchApi: fetchApiRef
16
+ },
17
+ factory: ({ discoveryApi, fetchApi }) => new SpaceliftApi(discoveryApi, fetchApi)
18
+ })
19
+ ]
20
+ });
21
+ const SpaceliftIoPage = spaceliftIoPlugin.provide(
22
+ createRoutableExtension({
23
+ name: "SpaceliftIoPage",
24
+ component: () => import('./components/Stacks/index.esm.js').then((m) => m.StacksPage),
25
+ mountPoint: rootRouteRef
26
+ })
27
+ );
28
+
29
+ export { SpaceliftIoPage, spaceliftIoPlugin };
30
+ //# sourceMappingURL=plugin.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { SpaceliftApi, spaceliftApiRef } from './api/SpaceliftApiClient';\nimport { rootRouteRef } from './routes';\n\nexport const spaceliftIoPlugin = createPlugin({\n id: 'spacelift',\n routes: {\n root: rootRouteRef,\n },\n apis: [\n createApiFactory({\n api: spaceliftApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: ({ discoveryApi, fetchApi }) => new SpaceliftApi(discoveryApi, fetchApi),\n }),\n ],\n});\n\nexport const SpaceliftIoPage = spaceliftIoPlugin.provide(\n createRoutableExtension({\n name: 'SpaceliftIoPage',\n component: () => import('./components/Stacks').then(m => m.StacksPage),\n mountPoint: rootRouteRef,\n })\n);\n"],"names":[],"mappings":";;;;AAWO,MAAM,oBAAoB,YAAa,CAAA;AAAA,EAC5C,EAAI,EAAA,WAAA;AAAA,EACJ,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA;AAAA,GACR;AAAA,EACA,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,eAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,QAAU,EAAA;AAAA,OACZ;AAAA,MACA,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,UAAe,KAAA,IAAI,YAAa,CAAA,YAAA,EAAc,QAAQ;AAAA,KACjF;AAAA;AAEL,CAAC;AAEM,MAAM,kBAAkB,iBAAkB,CAAA,OAAA;AAAA,EAC/C,uBAAwB,CAAA;AAAA,IACtB,IAAM,EAAA,iBAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,kCAAqB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,UAAU,CAAA;AAAA,IACrE,UAAY,EAAA;AAAA,GACb;AACH;;;;"}
@@ -0,0 +1,8 @@
1
+ import { createRouteRef } from '@backstage/core-plugin-api';
2
+
3
+ const rootRouteRef = createRouteRef({
4
+ id: "spacelift"
5
+ });
6
+
7
+ export { rootRouteRef };
8
+ //# sourceMappingURL=routes.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'spacelift',\n});\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAe,CAAA;AAAA,EACzC,EAAI,EAAA;AACN,CAAC;;;;"}
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@spacelift-io/backstage-integration-frontend",
3
+ "version": "0.0.0",
4
+ "description": "Backstage plugin for integrating Spacelift.io with Backstage",
5
+ "keywords": [
6
+ "backstage-plugin",
7
+ "backstage",
8
+ "spacelift",
9
+ "infrastructure-as-code",
10
+ "IaC",
11
+ "devops",
12
+ "cloud",
13
+ "category/cloud",
14
+ "category/infrastructure"
15
+ ],
16
+ "homepage": "https://spacelift.io",
17
+ "repository": "https://github.com/spacelift-io/backstage-plugins.git",
18
+ "license": "MIT",
19
+ "main": "dist/index.esm.js",
20
+ "types": "dist/index.d.ts",
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "main": "dist/index.esm.js",
24
+ "types": "dist/index.d.ts"
25
+ },
26
+ "backstage": {
27
+ "role": "frontend-plugin",
28
+ "pluginId": "spacelift-io-frontend",
29
+ "pluginPackages": [
30
+ "@spacelift-io/backstage-integration-frontend"
31
+ ]
32
+ },
33
+ "configSchema": "config.d.ts",
34
+ "sideEffects": false,
35
+ "scripts": {
36
+ "start": "backstage-cli package start",
37
+ "build:types": "tsc -b",
38
+ "build": "yarn build:types && backstage-cli package build",
39
+ "lint": "backstage-cli package lint",
40
+ "test": "backstage-cli package test --coverage",
41
+ "test:watch": "backstage-cli package test --watch",
42
+ "clean": "backstage-cli package clean",
43
+ "prepack": "backstage-cli package prepack",
44
+ "postpack": "backstage-cli package postpack"
45
+ },
46
+ "jest": {
47
+ "collectCoverageFrom": [
48
+ "**/*.{ts,tsx}",
49
+ "!constants.ts",
50
+ "!plugin.ts",
51
+ "!routes.ts",
52
+ "!**/mocks/**"
53
+ ],
54
+ "coverageThreshold": {
55
+ "global": {
56
+ "branches": 90,
57
+ "functions": 90,
58
+ "lines": 90,
59
+ "statements": 90
60
+ }
61
+ }
62
+ },
63
+ "dependencies": {
64
+ "@backstage/core-components": "^0.17.1",
65
+ "@backstage/core-plugin-api": "^1.10.6",
66
+ "@backstage/plugin-catalog-react": "^1.17.0",
67
+ "@backstage/theme": "^0.6.5",
68
+ "@material-ui/core": "^4.9.13",
69
+ "@material-ui/icons": "^4.9.1",
70
+ "@material-ui/lab": "^4.0.0-alpha.61",
71
+ "eslint-plugin-react-hooks": "^4.6.2",
72
+ "luxon": "^3.6.1",
73
+ "react": "^18.0.0",
74
+ "react-dom": "^18.0.0",
75
+ "react-use": "^17.2.4"
76
+ },
77
+ "peerDependencies": {
78
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
79
+ },
80
+ "devDependencies": {
81
+ "@backstage/cli": "^0.32.0",
82
+ "@backstage/core-app-api": "^1.16.1",
83
+ "@backstage/dev-utils": "^1.1.9",
84
+ "@backstage/test-utils": "^1.7.8",
85
+ "@testing-library/jest-dom": "^6.0.0",
86
+ "@testing-library/react": "^14.0.0",
87
+ "@testing-library/user-event": "^14.0.0",
88
+ "@types/luxon": "^3",
89
+ "@types/react": "^18.0.0",
90
+ "@types/react-dom": "^18.0.0",
91
+ "msw": "^1.0.0",
92
+ "react-router-dom": "^6.22.0"
93
+ },
94
+ "files": [
95
+ "config.d.ts",
96
+ "dist"
97
+ ],
98
+ "typesVersions": {
99
+ "*": {
100
+ "package.json": [
101
+ "package.json"
102
+ ]
103
+ }
104
+ },
105
+ "module": "./dist/index.esm.js"
106
+ }