@orchestrator-ui/orchestrator-ui-components 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @orchestrator-ui/orchestrator-ui-components
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 3533525: Updated TS-Config to improve developer experience
8
+
3
9
  ## 0.2.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchestrator-ui/orchestrator-ui-components",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "test": "jest",
@@ -31,7 +31,7 @@
31
31
  "@testing-library/jest-dom": "^5.16.1",
32
32
  "@testing-library/react": "^14.0.0",
33
33
  "@testing-library/react-hooks": "^8.0.1",
34
- "@testing-library/user-event": "^13.5.0",
34
+ "@testing-library/user-event": "^14.4.3",
35
35
  "@types/jest": "^27.4.0",
36
36
  "esbuild": "^0.14.10",
37
37
  "esbuild-jest": "^0.5.0",
@@ -3,6 +3,7 @@ import type { Meta } from '@storybook/react';
3
3
  import React, { ReactElement } from 'react';
4
4
  import { OrchestratorPageHeader } from './OrchestratorPageHeader';
5
5
  import Logo from '../../../../../.storybook/mockdata/logo-orchestrator.svg';
6
+ import Image from 'next/image';
6
7
 
7
8
  const Story: Meta<typeof OrchestratorPageHeader> = {
8
9
  component: OrchestratorPageHeader,
@@ -14,7 +15,7 @@ function getAppLogo(navigationLogo: number): ReactElement {
14
15
  return (
15
16
  <EuiFlexGroup alignItems="center" css={{ height: navigationLogo }}>
16
17
  <EuiFlexItem>
17
- <img
18
+ <Image
18
19
  src={Logo}
19
20
  alt="Orchestrator Logo"
20
21
  width={134}
@@ -3,6 +3,7 @@ import { OrchestratorPageTemplate } from './OrchestratorPageTemplate';
3
3
  import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
4
4
  import React, { ReactElement } from 'react';
5
5
  import Logo from '../../../../../.storybook/mockdata/logo-orchestrator.svg';
6
+ import Image from 'next/image';
6
7
 
7
8
  const Story: Meta<typeof OrchestratorPageTemplate> = {
8
9
  component: OrchestratorPageTemplate,
@@ -14,7 +15,7 @@ function getAppLogo(navigationLogo: number): ReactElement {
14
15
  return (
15
16
  <EuiFlexGroup alignItems="center" css={{ height: navigationLogo }}>
16
17
  <EuiFlexItem>
17
- <img
18
+ <Image
18
19
  src={Logo}
19
20
  alt="Orchestrator Logo"
20
21
  width={134}
@@ -74,6 +74,7 @@ export const ProductBlock = (resourceTypes: ResourceTypeBase, id: number) => {
74
74
  </EuiFlexItem>
75
75
  <EuiFlexItem grow={false}>
76
76
  <EuiButtonIcon
77
+ aria-label="Close"
77
78
  size={'m'}
78
79
  iconType={'cross'}
79
80
  onClick={() => toggleSelectedId(id)}
@@ -100,7 +100,7 @@ export const DataGridTable = <T,>({
100
100
  ) => EuiDataGridControlColumn = ({ id, width, rowCellRender }) => ({
101
101
  id,
102
102
  width,
103
- headerCellRender: (props) => null,
103
+ headerCellRender: () => null,
104
104
  rowCellRender: ({ rowIndex }: { rowIndex: number }) => {
105
105
  const { pageSize, pageIndex } = pagination;
106
106
  const rowIndexOnPage = rowIndex - pageIndex * pageSize;
@@ -1,2 +1,5 @@
1
1
  export const DEFAULT_PAGE_SIZES = [5, 10, 15, 20, 25, 100];
2
2
  export const DEFAULT_PAGE_SIZE = 10;
3
+
4
+ export const METADATA_PRODUCTBLOCKS_TABLE_LOCAL_STORAGE_KEY =
5
+ 'metadataProductBlocksTable';
@@ -0,0 +1 @@
1
+ export * from './productBlocksQuery';
@@ -0,0 +1,46 @@
1
+ import { gql } from 'graphql-request';
2
+ import { parse } from 'graphql';
3
+ import type { TypedDocumentNode } from '@graphql-typed-document-node/core';
4
+
5
+ import { SortOrder } from '../types';
6
+
7
+ import type { ProductBlockDefinition } from '../types';
8
+ import { GraphqlQueryVariables, ProductBlockDefinitionsResult } from '../types';
9
+
10
+ export const DEFAULT_SORT_FIELD: keyof ProductBlockDefinition = 'name';
11
+ export const DEFAULT_SORT_ORDER: SortOrder = SortOrder.DESC;
12
+
13
+ export const GET_PRODUCTS_BLOCKS_GRAPHQL_QUERY: TypedDocumentNode<
14
+ ProductBlockDefinitionsResult,
15
+ GraphqlQueryVariables<ProductBlockDefinition>
16
+ > = parse(gql`
17
+ query MetadataProductBlocks(
18
+ $first: IntType!
19
+ $after: IntType!
20
+ $sortBy: [GraphqlSort!]
21
+ ) {
22
+ productBlocks(first: $first, after: $after, sortBy: $sortBy) {
23
+ page {
24
+ productBlockId
25
+ name
26
+ tag
27
+ description
28
+ status
29
+ createdAt
30
+ endDate
31
+ resourceTypes {
32
+ description
33
+ resourceType
34
+ resourceTypeId
35
+ }
36
+ }
37
+ pageInfo {
38
+ endCursor
39
+ hasNextPage
40
+ hasPreviousPage
41
+ startCursor
42
+ totalItems
43
+ }
44
+ }
45
+ }
46
+ `);
@@ -12,7 +12,7 @@ import { DEFAULT_PAGE_SIZE } from '../components';
12
12
  export type DataDisplayParams<Type> = {
13
13
  pageSize: number;
14
14
  pageIndex: number;
15
- sortBy?: GraphQLSort<Type>;
15
+ sortBy: GraphQLSort<Type>;
16
16
  esQueryString?: string; // The filter param is going to send to the backend as is for parsing
17
17
  };
18
18
 
package/src/index.ts CHANGED
@@ -6,3 +6,4 @@ export * from './contexts';
6
6
  export * from './types';
7
7
  export * from './hooks';
8
8
  export * from './messages';
9
+ export * from './pages';
@@ -5,7 +5,8 @@
5
5
  "common": {
6
6
  "addToFavorites": "Add to favorites",
7
7
  "product": "Product",
8
- "deselect": "Deselect"
8
+ "deselect": "Deselect",
9
+ "close": "Close"
9
10
  },
10
11
  "metadata": {
11
12
  "tabs": {
@@ -20,9 +21,20 @@
20
21
  "description": "Description",
21
22
  "tag": "Tag",
22
23
  "productType": "Type",
23
- "status": "status",
24
+ "status": "Status",
24
25
  "productBlocks": "Product blocks",
25
26
  "createdAt": "Created"
27
+ },
28
+ "productBlocks": {
29
+ "id": "ID",
30
+ "name": "Name",
31
+ "description": "Description",
32
+ "tag": "Tag",
33
+ "status": "status",
34
+ "resourceTypes": "Resource types",
35
+ "createdAt": "Created",
36
+ "endDate": "End date",
37
+ "parentIds": "Parents"
26
38
  }
27
39
  },
28
40
  "subscriptions": {
@@ -0,0 +1 @@
1
+ export * from './metadata';
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import type { ReactNode } from 'react';
3
+ import { EuiSpacer, EuiPageHeader, EuiTab, EuiTabs } from '@elastic/eui';
4
+ import { useRouter } from 'next/router';
5
+ import { useTranslations } from 'next-intl';
6
+
7
+ interface MetadataLayoutProps {
8
+ children: ReactNode;
9
+ tabs?: MetaDataTab[];
10
+ }
11
+
12
+ export interface MetaDataTab {
13
+ id: number;
14
+ translationKey: string;
15
+ path: string;
16
+ }
17
+
18
+ const metaDataTabs: MetaDataTab[] = [
19
+ {
20
+ id: 1,
21
+ translationKey: 'products',
22
+ path: '/metadata/products',
23
+ },
24
+ {
25
+ id: 2,
26
+ translationKey: 'productBlocks',
27
+ path: '/metadata/productblocks',
28
+ },
29
+ {
30
+ id: 3,
31
+ translationKey: 'resourceTypes',
32
+ path: '/metadata/resource-types',
33
+ },
34
+ {
35
+ id: 4,
36
+ translationKey: 'fixedInputs',
37
+ path: '/metadata/fixed-inputs',
38
+ },
39
+ {
40
+ id: 5,
41
+ translationKey: 'workflows',
42
+ path: '/metadata/workflows',
43
+ },
44
+ ];
45
+
46
+ export const WFOMetadataPageLayout = ({
47
+ children,
48
+ tabs = metaDataTabs,
49
+ }: MetadataLayoutProps) => {
50
+ const router = useRouter();
51
+ const t = useTranslations('metadata.tabs');
52
+ const currentPath = router.pathname;
53
+
54
+ return (
55
+ <>
56
+ <EuiSpacer />
57
+
58
+ <EuiPageHeader pageTitle="Metadata" />
59
+ <EuiSpacer size="m" />
60
+ <EuiTabs>
61
+ {tabs.map(({ id, translationKey: name, path }) => (
62
+ <EuiTab
63
+ key={id}
64
+ isSelected={path === currentPath}
65
+ onClick={() => router.push(path)}
66
+ >
67
+ {t(name)}
68
+ </EuiTab>
69
+ ))}
70
+ </EuiTabs>
71
+ <EuiSpacer size="xxl" />
72
+ {children}
73
+ </>
74
+ );
75
+ };
@@ -0,0 +1,167 @@
1
+ import React from 'react';
2
+ import { useTranslations } from 'next-intl';
3
+ import type { Pagination } from '@elastic/eui/src/components';
4
+
5
+ import {
6
+ DEFAULT_PAGE_SIZE,
7
+ DEFAULT_PAGE_SIZES,
8
+ METADATA_PRODUCTBLOCKS_TABLE_LOCAL_STORAGE_KEY,
9
+ } from '../../components';
10
+ import {
11
+ WFOStatusBadge,
12
+ WFOProductBlockBadge,
13
+ TableWithFilter,
14
+ } from '../../components';
15
+ import {
16
+ getTableConfigFromLocalStorage,
17
+ getDataSortHandler,
18
+ getPageChangeHandler,
19
+ getEsQueryStringHandler,
20
+ } from '../../components';
21
+ import type { TableColumns, DataSorting } from '../../components';
22
+
23
+ import { parseDateToLocaleString } from '../../utils';
24
+ import type { ProductBlockDefinition } from '../../types';
25
+ import { SortOrder } from '../../types';
26
+
27
+ import { useDataDisplayParams, useQueryWithGraphql } from '../../hooks';
28
+
29
+ import { GET_PRODUCTS_BLOCKS_GRAPHQL_QUERY } from '../../graphqlQueries';
30
+
31
+ import { WFOMetadataPageLayout } from './WFOMetadataPageLayout';
32
+
33
+ export const PRODUCT_BLOCK_FIELD_ID: keyof ProductBlockDefinition =
34
+ 'productBlockId';
35
+ export const PRODUCT_BLOCK_FIELD_NAME: keyof ProductBlockDefinition = 'name';
36
+
37
+ export const PRODUCT_BLOCK_FIELD_TAG: keyof ProductBlockDefinition = 'tag';
38
+ export const PRODUCT_BLOCK_FIELD_DESCRIPTION: keyof ProductBlockDefinition =
39
+ 'description';
40
+ export const PRODUCT_BLOCK_FIELD_STATUS: keyof ProductBlockDefinition =
41
+ 'status';
42
+ export const PRODUCT_BLOCK_FIELD_CREATED_AT: keyof ProductBlockDefinition =
43
+ 'createdAt';
44
+ export const PRODUCT_BLOCK_FIELD_END_DATE: keyof ProductBlockDefinition =
45
+ 'endDate';
46
+ export const PRODUCT_BLOCK_FIELD_RESOURCE_TYPES: keyof ProductBlockDefinition =
47
+ 'resourceTypes';
48
+
49
+ export const WFOProductBlocksPage = () => {
50
+ const t = useTranslations('metadata.productBlocks');
51
+
52
+ const initialPageSize =
53
+ getTableConfigFromLocalStorage(
54
+ METADATA_PRODUCTBLOCKS_TABLE_LOCAL_STORAGE_KEY,
55
+ )?.selectedPageSize ?? DEFAULT_PAGE_SIZE;
56
+
57
+ const { dataDisplayParams, setDataDisplayParam } =
58
+ useDataDisplayParams<ProductBlockDefinition>({
59
+ pageSize: initialPageSize,
60
+ sortBy: {
61
+ field: PRODUCT_BLOCK_FIELD_NAME,
62
+ order: SortOrder.ASC,
63
+ },
64
+ });
65
+
66
+ const tableColumns: TableColumns<ProductBlockDefinition> = {
67
+ productBlockId: {
68
+ field: PRODUCT_BLOCK_FIELD_ID,
69
+ name: t('id'),
70
+ width: '110',
71
+ },
72
+ name: {
73
+ field: PRODUCT_BLOCK_FIELD_NAME,
74
+ name: t('name'),
75
+ width: '110',
76
+ },
77
+ description: {
78
+ field: PRODUCT_BLOCK_FIELD_DESCRIPTION,
79
+ name: t('description'),
80
+ width: '400',
81
+ },
82
+ tag: {
83
+ field: PRODUCT_BLOCK_FIELD_TAG,
84
+ name: t('tag'),
85
+ },
86
+ status: {
87
+ field: PRODUCT_BLOCK_FIELD_STATUS,
88
+ name: t('status'),
89
+ width: '90',
90
+ render: (value) => (
91
+ <WFOStatusBadge status={value.toLocaleLowerCase()} />
92
+ ),
93
+ },
94
+ resourceTypes: {
95
+ field: PRODUCT_BLOCK_FIELD_RESOURCE_TYPES,
96
+ name: t('resourceTypes'),
97
+ render: (resourceTypes) => (
98
+ <>
99
+ {resourceTypes.map((resourceType, index) => (
100
+ <WFOProductBlockBadge key={index}>
101
+ {resourceType.resourceType}
102
+ </WFOProductBlockBadge>
103
+ ))}
104
+ </>
105
+ ),
106
+ },
107
+ createdAt: {
108
+ field: PRODUCT_BLOCK_FIELD_CREATED_AT,
109
+ name: t('createdAt'),
110
+ render: parseDateToLocaleString,
111
+ },
112
+ endDate: {
113
+ field: PRODUCT_BLOCK_FIELD_END_DATE,
114
+ name: t('endDate'),
115
+ render: parseDateToLocaleString,
116
+ },
117
+ };
118
+
119
+ const { data, isFetching } = useQueryWithGraphql(
120
+ GET_PRODUCTS_BLOCKS_GRAPHQL_QUERY,
121
+ {
122
+ first: dataDisplayParams.pageSize,
123
+ after: dataDisplayParams.pageIndex * dataDisplayParams.pageSize,
124
+ sortBy: dataDisplayParams.sortBy,
125
+ },
126
+ 'productBlocks',
127
+ true,
128
+ );
129
+
130
+ const dataSorting: DataSorting<ProductBlockDefinition> = {
131
+ field: dataDisplayParams.sortBy?.field ?? PRODUCT_BLOCK_FIELD_NAME,
132
+ sortOrder: dataDisplayParams.sortBy?.order ?? SortOrder.ASC,
133
+ };
134
+
135
+ const totalItems = data?.productBlocks.pageInfo.totalItems;
136
+
137
+ const pagination: Pagination = {
138
+ pageSize: dataDisplayParams.pageSize,
139
+ pageIndex: dataDisplayParams.pageIndex,
140
+ pageSizeOptions: DEFAULT_PAGE_SIZES,
141
+ totalItemCount: totalItems ? totalItems : 0,
142
+ };
143
+
144
+ return (
145
+ <WFOMetadataPageLayout>
146
+ <TableWithFilter<ProductBlockDefinition>
147
+ data={data ? data.productBlocks.page : []}
148
+ tableColumns={tableColumns}
149
+ dataSorting={dataSorting}
150
+ onUpdateDataSort={getDataSortHandler<ProductBlockDefinition>(
151
+ dataDisplayParams,
152
+ setDataDisplayParam,
153
+ )}
154
+ onUpdatePage={getPageChangeHandler<ProductBlockDefinition>(
155
+ setDataDisplayParam,
156
+ )}
157
+ onUpdateEsQueryString={getEsQueryStringHandler<ProductBlockDefinition>(
158
+ setDataDisplayParam,
159
+ )}
160
+ pagination={pagination}
161
+ isLoading={isFetching}
162
+ esQueryString={dataDisplayParams.esQueryString}
163
+ localStorageKey={METADATA_PRODUCTBLOCKS_TABLE_LOCAL_STORAGE_KEY}
164
+ />
165
+ </WFOMetadataPageLayout>
166
+ );
167
+ };
@@ -0,0 +1 @@
1
+ export * from './WFOProductBlocksPage';
package/src/types.ts CHANGED
@@ -36,8 +36,37 @@ export type ProductBlockBase = {
36
36
  resourceTypes: ResourceTypeBase;
37
37
  };
38
38
 
39
+ export interface ResourceTypeDefinition {
40
+ description: string;
41
+ resourceType: string;
42
+ resourceTypeId: string;
43
+ }
44
+
45
+ export interface ProductBlockDefinition {
46
+ productBlockId: string;
47
+ name: string;
48
+ tag: string;
49
+ description: string;
50
+ status: string;
51
+ createdAt: Date | null;
52
+ endDate: Date | null;
53
+ resourceTypes: ResourceTypeDefinition[];
54
+ }
55
+
39
56
  export type FixedInputsBase = GenericField;
40
57
 
58
+ export interface FixedInputDefinition {
59
+ fixedInputId: string;
60
+ name: string;
61
+ value: string;
62
+ productId: string;
63
+ createdAt: string;
64
+
65
+ // Display only?
66
+ description: string;
67
+ required: boolean;
68
+ }
69
+
41
70
  export type ExternalServiceBase = {
42
71
  externalServiceKey: string;
43
72
  externalServiceId: string;
@@ -100,24 +129,22 @@ export interface Process {
100
129
  is_task: boolean;
101
130
  }
102
131
 
103
- //// Utility types
104
-
105
- export interface Product {
132
+ export interface ProductDefinition {
133
+ productId: string;
106
134
  name: string;
107
135
  description: string;
108
136
  tag: string;
137
+ createdAt: string;
109
138
  productType: string;
110
139
  status: string;
111
- productBlocks: ProductBlock[];
112
- createdAt: Date | null;
113
- }
114
-
115
- interface ProductBlock {
116
- name: string;
140
+ productBlocks: Pick<ProductBlockDefinition, 'name'>[];
141
+ fixedInputs: Pick<FixedInputDefinition, 'name' | 'value'>[];
117
142
  }
118
143
 
119
144
  export type Field<Type> = keyof Type;
120
145
 
146
+ //// Utility types
147
+
121
148
  export enum SortOrder {
122
149
  ASC = 'ASC',
123
150
  DESC = 'DESC',
@@ -140,6 +167,28 @@ export type GraphqlQueryVariables<Type> = {
140
167
  filterBy?: GraphqlFilter<Type>[];
141
168
  };
142
169
 
170
+ type GraphQLPageInfo = {
171
+ hasNextPage: boolean;
172
+ hasPreviousPage: boolean;
173
+ startCursor: number;
174
+ totalItems: number;
175
+ endCursor: number;
176
+ };
177
+
178
+ export interface ProductDefinitionsResult {
179
+ products: {
180
+ page: ProductDefinition[];
181
+ pageInfo: GraphQLPageInfo;
182
+ };
183
+ }
184
+
185
+ export interface ProductBlockDefinitionsResult {
186
+ productBlocks: {
187
+ page: ProductBlockDefinition[];
188
+ pageInfo: GraphQLPageInfo;
189
+ };
190
+ }
191
+
143
192
  export interface CacheOption {
144
193
  value: string;
145
194
  label: string;
package/tsconfig.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "extends": "@orchestrator-ui/tsconfig/base.json",
3
3
  "compilerOptions": {
4
- "strictNullChecks": true
4
+ "strictNullChecks": true,
5
+ "rootDir": "./src",
6
+ "outDir": "./dist"
5
7
  },
6
- "include": ["**/*.ts", "**/*.tsx"],
8
+ "include": ["./src/**/*.ts", "./src/**/*.tsx"],
7
9
  "exclude": [
8
10
  "node_modules",
9
11
  "**/*.stories.ts",
@@ -1,11 +0,0 @@
1
- $ tsup src/index.ts --format esm --dts
2
- CLI Building entry: src/index.ts
3
- CLI Using tsconfig: tsconfig.json
4
- CLI tsup v7.1.0
5
- CLI Target: node16
6
- ESM Build start
7
- ESM dist\index.js 111.65 KB
8
- ESM ⚡️ Build success in 90ms
9
- DTS Build start
10
- DTS ⚡️ Build success in 3670ms
11
- DTS dist\index.d.ts 24.53 KB
@@ -1,15 +0,0 @@
1
- $ eslint "src/**/*.ts*"
2
-
3
- C:\development\surf\orchestrator-ui\packages\orchestrator-ui-components\src\components\OrchestratorPageTemplate\OrchestratorPageHeader\OrchestratorPageHeader.stories.tsx
4
- 17:17 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
5
-
6
- C:\development\surf\orchestrator-ui\packages\orchestrator-ui-components\src\components\OrchestratorPageTemplate\OrchestratorPageTemplate\OrchestratorPageTemplate.stories.tsx
7
- 17:17 warning Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element @next/next/no-img-element
8
-
9
- C:\development\surf\orchestrator-ui\packages\orchestrator-ui-components\src\components\Table\DataGridTable\DataGridTable.tsx
10
- 103:28 warning 'props' is defined but never used @typescript-eslint/no-unused-vars
11
-
12
- ✖ 3 problems (0 errors, 3 warnings)
13
-
14
- (node:11940) [DEP0128] DeprecationWarning: Invalid 'main' field in 'C:\development\surf\orchestrator-ui\node_modules\@orchestrator-ui\eslint-config-custom\package.json' of './dist/index.js'. Please either fix that or report it to the module author
15
- (Use `node --trace-deprecation ...` to show where the warning was created)
@@ -1,19 +0,0 @@
1
- yarn run v1.22.19
2
- $ jest
3
- PASS Orchestrator UI Components Tests src/utils/getTypedFieldFromObject.spec.ts
4
- PASS Orchestrator UI Components Tests src/components/Table/DataGridTable/dataGridColumns.spec.ts
5
- PASS Orchestrator UI Components Tests src/utils/uuid.spec.ts
6
- PASS Orchestrator UI Components Tests src/utils/string.spec.ts
7
- PASS Orchestrator UI Components Tests src/components/Table/utils/tableConfigPersistence.spec.ts
8
- PASS Orchestrator UI Components Tests src/components/Table/utils/columns.spec.ts
9
- PASS Orchestrator UI Components Tests src/utils/getTokenName.spec.ts
10
- PASS Orchestrator UI Components Tests src/components/Table/utils/tableUtils.spec.ts
11
- PASS Orchestrator UI Components Tests src/utils/date.spec.ts
12
- PASS Orchestrator UI Components Tests src/components/SubscriptionsTabs/getSubscriptionsTabTypeFromString.spec.ts
13
-
14
- Test Suites: 10 passed, 10 total
15
- Tests: 50 passed, 50 total
16
- Snapshots: 0 total
17
- Time: 4.16 s
18
- Ran all test suites.
19
- Done in 5.13s.
@@ -1 +0,0 @@
1
- $ tsc --noEmit