Merge branch 'master' into broadcast_notifications

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-03-14 12:10:31 +01:00
committed by GitHub
86 changed files with 483 additions and 192 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-apache-airflow': patch
---
added an optional ESLint rule - no-top-level-material-ui-4-imports - which has an auto fix function to migrate the imports and used it to migrate the imports for plugins/apache-airflow
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-api-docs': patch
---
added an optional ESLint rule - no-top-level-material-ui-4-imports - in api-docs plugin which has an auto fix function to migrate the imports and used it to migrate the material-ui imports for plugins/api-docs
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed a bug that could cause the `build-workspace` command to fail when invoked with `--alwaysYarnPack` enabled in environments with limited resources.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-notifications-backend': patch
'@backstage/plugin-notifications': patch
---
All notifications can be marked and filtered by severity critical, high, normal or low, the default is 'normal'
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-explore': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports -in explore plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/explore.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog': patch
---
Resolved an issue where the `PaginatedCatalogTable` was not propagating table options to its child table.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-circleci': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports - in CircleCI plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for `plugins/circleci`.
+1 -1
View File
@@ -2,4 +2,4 @@
'@backstage/plugin-airbrake': patch
---
added an optional ESLint rule - no-top-level-material-ui-4-imports - which has an auto fix function to migrate the imports and using it migrated the imports
Added an optional ESLint rule - no-top-level-material-ui-4-imports - which has an auto fix function to migrate the imports and using it migrated the imports.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-cloudbuild': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports -in cloudbuild plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/cloudbuild.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-apollo-explorer': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports -in apollo-explorer plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/apollo-explorer.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-bazaar': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports - in bazaar plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/bazaar
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-kubernetes': patch
---
Added an optional ESLint rule - no-top-level-material-ui-4-imports -in kubernetes plugin which has an auto fix function to migrate the imports and used it to migrate the Material UI imports for plugins/kubernetes.
@@ -29,7 +29,7 @@ jobs:
cache-prefix: ${{ runner.os }}-v18.x
- name: Create Snyk report
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found
with:
args: >
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Monitor and Synchronize Snyk Policies
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
with:
command: monitor
args: >
@@ -46,7 +46,7 @@ jobs:
# Above we run the `monitor` command, this runs the `test` command which is
# the one that generates the SARIF report that we can upload to GitHub.
- name: Create Snyk report
uses: snyk/actions/node@1d672a455ab3339ef0a0021e1ec809165ee12fad # master
uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master
continue-on-error: true # To make sure that SARIF upload gets called
with:
args: >
+1
View File
@@ -112,6 +112,7 @@
"node-libs-browser": "^2.2.1",
"npm-packlist": "^5.0.0",
"ora": "^5.3.0",
"p-limit": "^3.1.0",
"p-queue": "^6.6.2",
"postcss": "^8.1.0",
"process": "^0.11.10",
@@ -21,6 +21,7 @@ import {
resolve as resolvePath,
relative as relativePath,
} from 'path';
import pLimit from 'p-limit';
import { tmpdir } from 'os';
import tar, { CreateOptions, FileOptions } from 'tar';
import partition from 'lodash/partition';
@@ -352,9 +353,11 @@ async function moveToDistWorkspace(
}
// Repacking in parallel is much faster and safe for all packages outside of the Backstage repo
// Limit concurrency to 10 to avoid resource exhaustion on larger monorepos.
const limit = pLimit(10);
await Promise.all(
safePackages.map(async (target, index) =>
pack(target, `temp-package-${index}.tgz`),
safePackages.map((target, index) =>
limit(() => pack(target, `temp-package-${index}.tgz`)),
),
);
}
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -22,7 +22,7 @@ import {
Page,
SupportButton,
} from '@backstage/core-components';
import { Grid } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import React from 'react';
import { DagTableComponent } from '../DagTableComponent';
import { StatusComponent } from '../StatusComponent';
@@ -18,17 +18,15 @@ import { apacheAirflowApiRef } from '../../api';
import useAsync from 'react-use/lib/useAsync';
import { DagRun } from '../../api/types/Dags';
import { useApi } from '@backstage/core-plugin-api';
import {
Box,
Button,
CircularProgress,
List,
ListItem,
ListItemIcon,
makeStyles,
Tooltip,
Typography,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import Tooltip from '@material-ui/core/Tooltip';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import {
Link,
StatusError,
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
+1 -1
View File
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { Grid } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import {
createApiExtension,
@@ -16,7 +16,7 @@
import { ApiEntity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { Alert } from '@material-ui/lab';
import Alert from '@material-ui/lab/Alert';
import React from 'react';
import { apiDocsConfigRef } from '../../config';
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
@@ -16,18 +16,16 @@
import { ApiEntity } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
makeStyles,
Tab,
Tabs,
Typography,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogTitle from '@material-ui/core/DialogTitle';
import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import React, { useEffect } from 'react';
import { apiDocsConfigRef } from '../../config';
import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget';
@@ -15,7 +15,7 @@
*/
import { ApiEntity, RELATION_CONSUMES_API } from '@backstage/catalog-model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
EntityTable,
useEntity,
@@ -15,7 +15,7 @@
*/
import { ApiEntity, RELATION_HAS_PART } from '@backstage/catalog-model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
EntityTable,
useEntity,
@@ -15,7 +15,7 @@
*/
import { ApiEntity, RELATION_PROVIDES_API } from '@backstage/catalog-model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
EntityTable,
useEntity,
@@ -18,7 +18,7 @@ import { ApiEntity } from '@backstage/catalog-model';
import { TableColumn } from '@backstage/core-components';
import { EntityTable } from '@backstage/plugin-catalog-react';
import ExtensionIcon from '@material-ui/icons/Extension';
import { ToggleButton } from '@material-ui/lab';
import ToggleButton from '@material-ui/lab/ToggleButton';
import React, { useState } from 'react';
import { ApiTypeTitle } from '../ApiDefinitionCard';
import { ApiDefinitionDialog } from '../ApiDefinitionDialog';
@@ -18,7 +18,7 @@ import AsyncApi from '@asyncapi/react-component';
import '@asyncapi/react-component/styles/default.css';
import { makeStyles, alpha, darken } from '@material-ui/core/styles';
import React from 'react';
import { useTheme } from '@material-ui/core';
import { useTheme } from '@material-ui/core/styles';
const useStyles = makeStyles(theme => ({
root: {
@@ -18,7 +18,7 @@ import {
ComponentEntity,
RELATION_API_CONSUMED_BY,
} from '@backstage/catalog-model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
EntityTable,
useEntity,
@@ -18,7 +18,7 @@ import {
ComponentEntity,
RELATION_API_PROVIDED_BY,
} from '@backstage/catalog-model';
import { Typography } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
EntityTable,
useEntity,
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -15,7 +15,10 @@
*/
import React, { useState } from 'react';
import { Divider, makeStyles, Tab, Tabs } from '@material-ui/core';
import Divider from '@material-ui/core/Divider';
import Tab from '@material-ui/core/Tab';
import Tabs from '@material-ui/core/Tabs';
import { makeStyles } from '@material-ui/core/styles';
import { JSONObject } from '@apollo/explorer/src/helpers/types';
import { ApolloExplorer } from '@apollo/explorer/react';
import { Content } from '@backstage/core-components';
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -15,7 +15,9 @@
*/
import React from 'react';
import { Grid, Typography, makeStyles } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { InfoCard, Link } from '@backstage/core-components';
const useStyles = makeStyles({
@@ -26,7 +26,7 @@ import { fetchCatalogItems } from '../../util/fetchMethods';
import { parseBazaarProject } from '../../util/parseMethods';
import { ErrorPanel, InfoCard, Link } from '@backstage/core-components';
import { bazaarPlugin } from '../../plugin';
import { IconButton } from '@material-ui/core';
import IconButton from '@material-ui/core/IconButton';
import StorefrontIcon from '@material-ui/icons/Storefront';
/** @public */
@@ -15,7 +15,9 @@
*/
import { useElementFilter } from '@backstage/core-plugin-api';
import { Grid, makeStyles, Typography } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import React from 'react';
const useStyles = makeStyles(theme => ({
@@ -15,13 +15,11 @@
*/
import React from 'react';
import {
Grid,
Card,
CardContent,
Typography,
GridSize,
} from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import { GridSize } from '@material-ui/core/Grid';
import { parseEntityRef } from '@backstage/catalog-model';
import { Avatar, Link } from '@backstage/core-components';
import { useRouteRef } from '@backstage/core-plugin-api';
@@ -18,12 +18,10 @@ import React from 'react';
import FormControl from '@material-ui/core/FormControl';
import { Controller, Control, UseFormSetValue } from 'react-hook-form';
import { FormValues } from '../../types';
import {
KeyboardDatePicker,
MuiPickersUtilsProvider,
} from '@material-ui/pickers';
import { KeyboardDatePicker } from '@material-ui/pickers/DatePicker';
import MuiPickersUtilsProvider from '@material-ui/pickers/MuiPickersUtilsProvider';
import LuxonUtils from '@date-io/luxon';
import { IconButton } from '@material-ui/core';
import IconButton from '@material-ui/core/IconButton';
import ClearIcon from '@material-ui/icons/Clear';
type Props = {
@@ -18,7 +18,8 @@ import React from 'react';
import { Control, UseFormSetValue } from 'react-hook-form';
import { FormValues } from '../../types';
import { DateSelector } from '../DateSelector/DateSelector';
import { Typography, makeStyles } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
type Props = {
control: Control<FormValues, object>;
@@ -21,7 +21,8 @@ import { BazaarProject, FormValues } from '../../types';
import { bazaarApiRef } from '../../api';
import { UseFormGetValues } from 'react-hook-form';
import { ConfirmationDialog } from '../ConfirmationDialog';
import { Button, makeStyles } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';
type Props = {
bazaarProject: BazaarProject;
@@ -21,7 +21,7 @@ import { stringifyEntityRef } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import { bazaarApiRef } from '../../api';
import { EntityBazaarInfoContent } from '../EntityBazaarInfoContent';
import { Card } from '@material-ui/core';
import Card from '@material-ui/core/Card';
import { parseBazaarResponse } from '../../util/parseMethods';
/** @public */
@@ -15,7 +15,10 @@
*/
import React, { useState, useEffect } from 'react';
import { CardHeader, Divider, IconButton, makeStyles } from '@material-ui/core';
import CardHeader from '@material-ui/core/CardHeader';
import Divider from '@material-ui/core/Divider';
import IconButton from '@material-ui/core/IconButton';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import {
HeaderIconLinkRow,
@@ -31,7 +34,7 @@ import { EditProjectDialog } from '../EditProjectDialog';
import { useApi, identityApiRef } from '@backstage/core-plugin-api';
import { BazaarProject, Member } from '../../types';
import { bazaarApiRef } from '../../api';
import { Alert } from '@material-ui/lab';
import Alert from '@material-ui/lab/Alert';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';
import { parseEntityRef } from '@backstage/catalog-model';
@@ -15,13 +15,11 @@
*/
import React, { useState, useEffect } from 'react';
import {
Card,
CardHeader,
Divider,
IconButton,
makeStyles,
} from '@material-ui/core';
import Card from '@material-ui/core/Card';
import CardHeader from '@material-ui/core/CardHeader';
import Divider from '@material-ui/core/Divider';
import IconButton from '@material-ui/core/IconButton';
import { makeStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import {
HeaderIconLinkRow,
@@ -45,7 +43,7 @@ import {
} from '@backstage/core-plugin-api';
import { Member, BazaarProject } from '../../types';
import { bazaarApiRef } from '../../api';
import { Alert } from '@material-ui/lab';
import Alert from '@material-ui/lab/Alert';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
@@ -21,7 +21,7 @@ import {
FieldError,
ValidationRule,
} from 'react-hook-form';
import { TextField } from '@material-ui/core';
import TextField from '@material-ui/core/TextField';
import { FormValues } from '../../types';
type Rules = {
@@ -15,13 +15,11 @@
*/
import React, { useState } from 'react';
import {
Dialog,
DialogActions,
Button,
DialogContent,
makeStyles,
} from '@material-ui/core';
import Dialog from '@material-ui/core/Dialog';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import DialogContent from '@material-ui/core/DialogContent';
import { makeStyles } from '@material-ui/core/styles';
import { ProjectSelector } from '../ProjectSelector';
import { CustomDialogTitle } from '../CustomDialogTitle';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
@@ -16,14 +16,12 @@
import React, { useState } from 'react';
import { ItemCardHeader } from '@backstage/core-components';
import {
Card,
CardActionArea,
CardContent,
Dialog,
makeStyles,
Typography,
} from '@material-ui/core';
import Card from '@material-ui/core/Card';
import CardActionArea from '@material-ui/core/CardActionArea';
import CardContent from '@material-ui/core/CardContent';
import Dialog from '@material-ui/core/Dialog';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { StatusTag } from '../StatusTag/StatusTag';
import { BazaarProject } from '../../types';
import { DateTime } from 'luxon';
@@ -15,7 +15,8 @@
*/
import React from 'react';
import { Button, Dialog } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import Dialog from '@material-ui/core/Dialog';
import { useForm, UseFormReset, UseFormGetValues } from 'react-hook-form';
import { InputField } from '../InputField/InputField';
import { InputSelector } from '../InputSelector/InputSelector';
@@ -16,13 +16,11 @@
import React, { ChangeEvent, useState } from 'react';
import { ProjectCard } from '../ProjectCard/ProjectCard';
import {
Box,
makeStyles,
Grid,
TablePagination,
GridSize,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Grid from '@material-ui/core/Grid';
import TablePagination from '@material-ui/core/TablePagination';
import { GridSize } from '@material-ui/core/Grid';
import { makeStyles } from '@material-ui/core/styles';
import { BazaarProject } from '../../types';
import { Entity } from '@backstage/catalog-model';
@@ -17,8 +17,9 @@
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import Typography from '@material-ui/core/Typography';
import { Autocomplete } from '@material-ui/lab';
import { TextField, makeStyles } from '@material-ui/core';
import Autocomplete from '@material-ui/lab/Autocomplete';
import TextField from '@material-ui/core/TextField';
import { makeStyles } from '@material-ui/core/styles';
type Props = {
catalogEntities: Entity[];
@@ -15,7 +15,10 @@
*/
import React, { ChangeEvent, ReactNode } from 'react';
import { FormControl, MenuItem, Select, makeStyles } from '@material-ui/core';
import FormControl from '@material-ui/core/FormControl';
import MenuItem from '@material-ui/core/MenuItem';
import Select from '@material-ui/core/Select';
import { makeStyles } from '@material-ui/core/styles';
const useStyles = makeStyles({
select: {
@@ -18,14 +18,15 @@ import React, { ChangeEvent, useEffect, useState } from 'react';
import { Content, SupportButton } from '@backstage/core-components';
import { AddProjectDialog } from '../AddProjectDialog';
import { ProjectPreview } from '../ProjectPreview/ProjectPreview';
import { Button, makeStyles } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import { makeStyles } from '@material-ui/core/styles';
import useAsyncFn from 'react-use/lib/useAsyncFn';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { useApi } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { BazaarProject } from '../../types';
import { bazaarApiRef } from '../../api';
import { Alert } from '@material-ui/lab';
import Alert from '@material-ui/lab/Alert';
import SearchBar from 'material-ui-search-bar';
import {
sortByDate,
@@ -32,7 +32,7 @@ type PaginatedCatalogTableProps = {
* @internal
*/
export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
const { columns, data, next, prev, title, isLoading } = props;
const { columns, data, next, prev, title, isLoading, options } = props;
const { updateFilters } = useEntityList();
return (
@@ -41,6 +41,8 @@ export function PaginatedCatalogTable(props: PaginatedCatalogTableProps) {
columns={columns}
data={data}
options={{
...options,
// These settings are configured to force server side pagination
paginationPosition: 'both',
pageSizeOptions: [],
showFirstLastPageButtons: false,
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -17,7 +17,9 @@
import React, { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { BuildWithSteps, BuildStepAction } from '../../api';
import { Grid, Box, Typography } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { ActionOutput } from './lib/ActionOutput/ActionOutput';
import LaunchIcon from '@material-ui/icons/Launch';
@@ -15,12 +15,10 @@
*/
import { LogViewer } from '@backstage/core-components';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Typography,
} from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
import AccordionDetails from '@material-ui/core/AccordionDetails';
import AccordionSummary from '@material-ui/core/AccordionSummary';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { BuildStepAction } from 'circleci-api';
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { Builds } from './lib/Builds';
import { Grid } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
export const BuildsPage = () => (
<Grid container spacing={3} direction="column">
@@ -15,14 +15,12 @@
*/
import React from 'react';
import {
Avatar,
Typography,
Box,
IconButton,
makeStyles,
Tooltip,
} from '@material-ui/core';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import { makeStyles } from '@material-ui/core/styles';
import RetryIcon from '@material-ui/icons/Replay';
import GitHubIcon from '@material-ui/icons/GitHub';
import LaunchIcon from '@material-ui/icons/Launch';
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
@@ -19,7 +19,8 @@ import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns';
import { WorkflowRunsTable } from '../WorkflowRunsTable';
import { useEntity } from '@backstage/plugin-catalog-react';
import { WorkflowRunStatus } from '../WorkflowRunStatus';
import { makeStyles, LinearProgress } from '@material-ui/core';
import LinearProgress from '@material-ui/core/LinearProgress';
import { makeStyles } from '@material-ui/core/styles';
import ExternalLinkIcon from '@material-ui/icons/Launch';
import { CLOUDBUILD_ANNOTATION } from '../useProjectName';
import { getLocation } from '../useLocation';
@@ -15,18 +15,16 @@
*/
import { Entity } from '@backstage/catalog-model';
import {
Box,
LinearProgress,
makeStyles,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableRow,
Typography,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import LinearProgress from '@material-ui/core/LinearProgress';
import Paper from '@material-ui/core/Paper';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableRow from '@material-ui/core/TableRow';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import ExternalLinkIcon from '@material-ui/icons/Launch';
import qs from 'qs';
import React from 'react';
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import React from 'react';
import { Typography, Box, IconButton, Tooltip } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import RetryIcon from '@material-ui/icons/Replay';
import GoogleIcon from '@material-ui/icons/CloudCircle';
import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns';
+6 -1
View File
@@ -1 +1,6 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
+1 -1
View File
@@ -23,7 +23,7 @@ import { ReactNode } from 'react';
import { ResultHighlight } from '@backstage/plugin-search-common';
import { RouteRef } from '@backstage/core-plugin-api';
import { SearchResultListItemExtensionProps } from '@backstage/plugin-search-react';
import { TabProps } from '@material-ui/core';
import { TabProps } from '@material-ui/core/Tab';
// @public @deprecated (undocumented)
export const catalogEntityRouteRef: ExternalRouteRef<
@@ -16,7 +16,7 @@
import { Entity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Button } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import { EntityCard } from '../EntityCard';
@@ -21,14 +21,12 @@ import {
getEntityRelations,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import {
Box,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Chip from '@material-ui/core/Chip';
import React from 'react';
import { LinkButton, ItemCardHeader } from '@backstage/core-components';
@@ -16,7 +16,7 @@
import { DomainEntity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { Button } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import React from 'react';
import useAsync from 'react-use/lib/useAsync';
import {
@@ -21,14 +21,12 @@ import {
getEntityRelations,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import {
Box,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Chip from '@material-ui/core/Chip';
import React from 'react';
import { LinkButton, ItemCardHeader } from '@backstage/core-components';
@@ -19,7 +19,7 @@ import {
attachComponentData,
useElementFilter,
} from '@backstage/core-plugin-api';
import { TabProps } from '@material-ui/core';
import { TabProps } from '@material-ui/core/Tab';
import { default as React } from 'react';
// TODO: This layout could be a shared based component if it was possible to create custom TabbedLayouts
@@ -33,7 +33,8 @@ import {
getEntityRelations,
EntityDisplayName,
} from '@backstage/plugin-catalog-react';
import { makeStyles, Typography, useTheme } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import ZoomOutMap from '@material-ui/icons/ZoomOutMap';
import classNames from 'classnames';
import React from 'react';
@@ -15,17 +15,14 @@
*/
import { ExploreTool } from '@backstage/plugin-explore-react';
import {
Box,
Card,
CardActions,
CardContent,
CardMedia,
Chip,
makeStyles,
Theme,
Typography,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardMedia from '@material-ui/core/CardMedia';
import Chip from '@material-ui/core/Chip';
import Typography from '@material-ui/core/Typography';
import { makeStyles, Theme } from '@material-ui/core/styles';
import { LinkButton } from '@backstage/core-components';
import classNames from 'classnames';
import React from 'react';
@@ -15,13 +15,11 @@
*/
import React, { ReactNode } from 'react';
import {
Box,
Chip,
ListItemIcon,
ListItemText,
makeStyles,
} from '@material-ui/core';
import Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import { makeStyles } from '@material-ui/core/styles';
import { Link } from '@backstage/core-components';
import {
IndexableDocument,
+5 -1
View File
@@ -1 +1,5 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
rules: {
'@backstage/no-top-level-material-ui-4-imports': 'error',
},
});
+2 -1
View File
@@ -15,7 +15,8 @@
*/
import React from 'react';
import { Grid, Typography } from '@material-ui/core';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import { Entity } from '@backstage/catalog-model';
import {
ErrorPanel,
+1 -1
View File
@@ -22,7 +22,7 @@ import {
} from '@backstage/plugin-catalog-react';
import { Route, Routes } from 'react-router-dom';
import { KubernetesContent } from './KubernetesContent';
import { Button } from '@material-ui/core';
import Button from '@material-ui/core/Button';
const KUBERNETES_ANNOTATION = 'backstage.io/kubernetes-id';
const KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION =
@@ -16,7 +16,10 @@
import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils';
import { DatabaseNotificationsStore } from './DatabaseNotificationsStore';
import { Knex } from 'knex';
import { Notification } from '@backstage/plugin-notifications-common';
import {
Notification,
NotificationSeverity,
} from '@backstage/plugin-notifications-common';
jest.setTimeout(60_000);
@@ -48,6 +51,7 @@ const id4 = '04e0871e-e60a-4f68-8110-5ae3513f992e';
const id5 = '05e0871e-e60a-4f68-8110-5ae3513f992e';
const id6 = '06e0871e-e60a-4f68-8110-5ae3513f992e';
const id7 = '07e0871e-e60a-4f68-8110-5ae3513f992e';
const ids = [id1, id2, id3, id4, id5, id6, id7];
const now = Date.now();
const timeDelay = 5 * 1000; /* 5 secs */
@@ -269,6 +273,64 @@ describe.each(databases.eachSupportedId())(
});
});
describe('getNotifications filters on severity', () => {
beforeEach(async () => {
const severities: (NotificationSeverity | undefined)[] = [
'normal',
undefined,
'critical',
'high',
'low',
];
await Promise.all(
severities.map((severity, idx) =>
storage.saveNotification({
id: ids[idx],
user,
origin: 'test-origin',
created: new Date(now - idx * timeDelay),
payload: {
title: severity || 'default',
severity,
},
}),
),
);
});
it('normal', async () => {
const normal = await storage.getNotifications({
user,
minimumSeverity: 'normal',
});
expect(normal.map(idOnly)).toEqual([id1, id2, id3, id4]);
});
it('critical', async () => {
const critical = await storage.getNotifications({
user,
minimumSeverity: 'critical',
});
expect(critical.length).toBe(1);
expect(critical.at(0)?.id).toEqual(id3);
});
it('high', async () => {
const high = await storage.getNotifications({
user,
minimumSeverity: 'high',
});
expect(high.map(idOnly)).toEqual([id3, id4]);
});
it('low', async () => {
const low = await storage.getNotifications({
user,
minimumSeverity: 'low',
});
expect(low.map(idOnly)).toEqual([id1, id2, id3, id4, id5]);
});
});
describe('getNotifications pagination', () => {
beforeEach(async () => {
await storage.saveNotification(testNotification1);
@@ -455,13 +517,14 @@ describe.each(databases.eachSupportedId())(
payload: {
title: 'New notification',
link: '/scaffolder/task/1234',
severity: 'normal',
severity: 'low',
},
} as any,
});
expect(existing).not.toBeNull();
expect(existing?.id).toEqual(id2);
expect(existing?.payload.title).toEqual('New notification');
expect(existing?.payload.severity).toEqual('low');
expect(existing?.read).toBeNull();
});
@@ -22,7 +22,10 @@ import {
NotificationModifyOptions,
NotificationsStore,
} from './NotificationsStore';
import { Notification } from '@backstage/plugin-notifications-common';
import {
Notification,
NotificationSeverity,
} from '@backstage/plugin-notifications-common';
import { Knex } from 'knex';
const migrationsDir = resolvePackagePath(
@@ -46,6 +49,21 @@ const NOTIFICATION_COLUMNS = [
'saved',
];
const severities: NotificationSeverity[] = [
'critical',
'high',
'normal',
'low',
];
export const normalizeSeverity = (input?: string): NotificationSeverity => {
let lower = (input ?? 'normal').toLowerCase() as NotificationSeverity;
if (severities.indexOf(lower) < 0) {
lower = 'normal';
}
return lower;
};
/** @internal */
export class DatabaseNotificationsStore implements NotificationsStore {
private isSQLite = false;
@@ -107,7 +125,7 @@ export class DatabaseNotificationsStore implements NotificationsStore {
link: notification.payload?.link,
title: notification.payload?.title,
description: notification.payload?.description,
severity: notification.payload?.severity,
severity: normalizeSeverity(notification.payload?.severity),
scope: notification.payload?.scope,
saved: notification.saved,
read: notification.read,
@@ -198,6 +216,12 @@ export class DatabaseNotificationsStore implements NotificationsStore {
query.whereNull('saved');
} // or match both if undefined
if (options.minimumSeverity !== undefined) {
const idx = severities.indexOf(options.minimumSeverity);
const equalOrHigher = severities.slice(0, idx + 1);
query.whereIn('notification.severity', equalOrHigher);
}
return query;
};
@@ -296,17 +320,20 @@ export class DatabaseNotificationsStore implements NotificationsStore {
return rows[0] as Notification;
}
async restoreExistingNotification(options: {
async restoreExistingNotification({
id,
notification,
}: {
id: string;
notification: Notification;
}) {
const updateColumns = {
title: options.notification.payload.title,
description: options.notification.payload.description,
link: options.notification.payload.link,
topic: options.notification.payload.topic,
title: notification.payload.title,
description: notification.payload.description,
link: notification.payload.link,
topic: notification.payload.topic,
updated: new Date(),
severity: options.notification.payload.severity,
severity: normalizeSeverity(notification.payload?.severity),
read: null,
};
@@ -320,7 +347,7 @@ export class DatabaseNotificationsStore implements NotificationsStore {
broadcastQuery.update({ ...updateColumns, read: undefined }),
]);
return await this.getNotification(options);
return await this.getNotification({ id });
}
async getNotification(options: { id: string }): Promise<Notification | null> {
@@ -16,6 +16,7 @@
import {
Notification,
NotificationSeverity,
NotificationStatus,
} from '@backstage/plugin-notifications-common';
@@ -32,6 +33,7 @@ export type NotificationGetOptions = {
read?: boolean;
saved?: boolean;
createdAfter?: Date;
minimumSeverity?: NotificationSeverity;
};
/** @internal */
@@ -18,6 +18,7 @@ import express, { Request } from 'express';
import Router from 'express-promise-router';
import {
DatabaseNotificationsStore,
normalizeSeverity,
NotificationGetOptions,
} from '../database';
import { v4 as uuid } from 'uuid';
@@ -236,6 +237,11 @@ export async function createRouter(
}
opts.createdAfter = new Date(sinceEpoch);
}
if (req.query.minimal_severity) {
opts.minimumSeverity = normalizeSeverity(
req.query.minimal_severity.toString(),
);
}
const [notifications, totalCount] = await Promise.all([
store.getNotifications(opts),
+2
View File
@@ -11,6 +11,7 @@ import { DiscoveryApi } from '@backstage/core-plugin-api';
import { FetchApi } from '@backstage/core-plugin-api';
import { JSX as JSX_2 } from 'react';
import { Notification as Notification_2 } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { NotificationStatus } from '@backstage/plugin-notifications-common';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
@@ -26,6 +27,7 @@ export type GetNotificationsOptions = {
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
minimumSeverity?: NotificationSeverity;
};
// @public (undocumented)
@@ -16,6 +16,7 @@
import { createApiRef } from '@backstage/core-plugin-api';
import {
Notification,
NotificationSeverity,
NotificationStatus,
} from '@backstage/plugin-notifications-common';
@@ -34,6 +35,7 @@ export type GetNotificationsOptions = {
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
minimumSeverity?: NotificationSeverity;
};
/** @public */
@@ -67,6 +67,9 @@ export class NotificationsClient implements NotificationsApi {
if (options?.createdAfter !== undefined) {
queryString.append('created_after', options.createdAfter.toISOString());
}
if (options?.minimumSeverity !== undefined) {
queryString.append('minimal_severity', options.minimumSeverity);
}
const urlSegment = `?${queryString}`;
return await this.request<GetNotificationsResponse>(urlSegment);
@@ -25,6 +25,7 @@ import {
Typography,
} from '@material-ui/core';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
export type SortBy = Required<
Pick<GetNotificationsOptions, 'sort' | 'sortOrder'>
@@ -39,6 +40,8 @@ export type NotificationsFiltersProps = {
onSortingChanged: (sortBy: SortBy) => void;
saved?: boolean;
onSavedChanged: (checked: boolean | undefined) => void;
severity: NotificationSeverity;
onSeverityChanged: (severity: NotificationSeverity) => void;
};
export const CreatedAfterOptions: {
@@ -108,6 +111,13 @@ const getSortByText = (sortBy?: SortBy): string => {
return 'newest';
};
const AllSeverityOptions: { [key in NotificationSeverity]: string } = {
critical: 'Critical',
high: 'High',
normal: 'Normal',
low: 'Low',
};
export const NotificationsFilters = ({
sorting,
onSortingChanged,
@@ -117,6 +127,8 @@ export const NotificationsFilters = ({
onCreatedAfterChanged,
saved,
onSavedChanged,
severity,
onSeverityChanged,
}: NotificationsFiltersProps) => {
const sortByText = getSortByText(sorting);
@@ -162,6 +174,14 @@ export const NotificationsFilters = ({
viewValue = 'read';
}
const handleOnSeverityChanged = (
event: React.ChangeEvent<{ name?: string; value: unknown }>,
) => {
const value: NotificationSeverity =
(event.target.value as NotificationSeverity) || 'normal';
onSeverityChanged(value);
};
return (
<>
<Grid container>
@@ -169,6 +189,7 @@ export const NotificationsFilters = ({
<Typography variant="h6">Filters</Typography>
<Divider variant="fullWidth" />
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-view">View</InputLabel>
@@ -185,14 +206,16 @@ export const NotificationsFilters = ({
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-view">
<InputLabel id="notifications-filter-created">
Created after
</InputLabel>
<Select
label="Created after"
labelId="notifications-filter-created"
placeholder="Notifications since"
value={createdAfter}
onChange={handleOnCreatedAfterChanged}
@@ -212,6 +235,7 @@ export const NotificationsFilters = ({
<Select
label="Sort by"
labelId="notifications-filter-sort"
placeholder="Field to sort by"
value={sortByText}
onChange={handleOnSortByChanged}
@@ -224,6 +248,25 @@ export const NotificationsFilters = ({
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-severity">Severity</InputLabel>
<Select
label="Severity"
labelId="notifications-filter-severity"
value={severity}
onChange={handleOnSeverityChanged}
>
{Object.keys(AllSeverityOptions).map((key: string) => (
<MenuItem value={key} key={key}>
{AllSeverityOptions[key as NotificationSeverity]}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</>
);
@@ -32,6 +32,7 @@ import {
SortByOptions,
} from '../NotificationsFilters';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
export const NotificationsPage = () => {
const [refresh, setRefresh] = React.useState(false);
@@ -45,6 +46,7 @@ export const NotificationsPage = () => {
const [sorting, setSorting] = React.useState<SortBy>(
SortByOptions.newest.sortBy,
);
const [severity, setSeverity] = React.useState<NotificationSeverity>('low');
const { error, value, retry, loading } = useNotificationsApi(
api => {
@@ -52,6 +54,7 @@ export const NotificationsPage = () => {
search: containsText,
limit: pageSize,
offset: pageNumber * pageSize,
minimumSeverity: severity,
...(sorting || {}),
};
if (unreadOnly !== undefined) {
@@ -76,6 +79,7 @@ export const NotificationsPage = () => {
pageSize,
sorting,
saved,
severity,
],
);
@@ -114,6 +118,8 @@ export const NotificationsPage = () => {
sorting={sorting}
saved={saved}
onSavedChanged={setSaved}
severity={severity}
onSeverityChanged={setSeverity}
/>
</Grid>
<Grid item xs={10}>
@@ -33,6 +33,7 @@ import MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts
import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;
import MarkAsSavedIcon from '@material-ui/icons/Label';
import { SeverityIcon } from './SeverityIcon';
const ThrottleDelayMs = 1000;
@@ -93,6 +94,12 @@ export const NotificationsTable = ({
const compactColumns = React.useMemo(
(): TableColumn<Notification>[] => [
{
width: '1rem',
render: (notification: Notification) => (
<SeverityIcon severity={notification.payload?.severity} />
),
},
{
customFilterAndSearch: () =>
true /* Keep it on backend due to pagination. If recent flickering is an issue, implement search here as well. */,
@@ -0,0 +1,39 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import NormalIcon from '@material-ui/icons/CheckOutlined';
import CriticalIcon from '@material-ui/icons/ErrorOutline';
import HighIcon from '@material-ui/icons/WarningOutlined';
import LowIcon from '@material-ui/icons/InfoOutlined';
export const SeverityIcon = ({
severity,
}: {
severity?: NotificationSeverity;
}) => {
switch (severity) {
case 'critical':
return <CriticalIcon htmlColor="#C9190B" />;
case 'high':
return <HighIcon htmlColor="#F0AB00" />;
case 'low':
return <LowIcon htmlColor="#2B9AF3" />;
case 'normal':
default:
return <NormalIcon htmlColor="#5BA352" />;
}
};
+4 -3
View File
@@ -3691,6 +3691,7 @@ __metadata:
nodemon: ^3.0.1
npm-packlist: ^5.0.0
ora: ^5.3.0
p-limit: ^3.1.0
p-queue: ^6.6.2
postcss: ^8.1.0
process: ^0.11.10
@@ -18735,12 +18736,12 @@ __metadata:
linkType: hard
"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.21":
version: 3.3.24
resolution: "@types/dockerode@npm:3.3.24"
version: 3.3.26
resolution: "@types/dockerode@npm:3.3.26"
dependencies:
"@types/docker-modem": "*"
"@types/node": "*"
checksum: 00329ba9225f5b57bfc0ba8c4dddb17100ebe13c5192fe8a14fce59eec456d258e814b6c78df26d7d7bb878fc38455f559f278490c9c8efad8f708335643b40e
checksum: ec1e83ef2d938813c8fa72f7c8744a9bf598369d8a0f340377ddff25c6e7d118379cd297eecbe4af2b7aa64bf5cd1bb66b32d1e3874a062433b967265be94f09
languageName: node
linkType: hard