Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into support/use-jotai

Yuki Takei 7 месяцев назад
Родитель
Сommit
856b6174ef

+ 2 - 0
apps/app/.eslintrc.js

@@ -39,7 +39,9 @@ module.exports = {
     'src/features/external-user-group/**',
     'src/features/page-bulk-export/**',
     'src/features/opentelemetry/**',
+    'src/stores-universal/**',
     'src/interfaces/**',
+    'src/utils/**',
     'src/states/**',
   ],
   settings: {

+ 50 - 19
apps/app/src/stores-universal/context.tsx

@@ -1,12 +1,14 @@
-import type EventEmitter from 'events';
-
 import { AcceptedUploadFileType } from '@growi/core';
 import { useSWRStatic } from '@growi/core/dist/swr';
+import type EventEmitter from 'events';
 import type { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import type { SupportedActionType } from '~/interfaces/activity';
-import { useIsUploadEnabled, useIsUploadAllFileAllowed } from '~/states/server-configurations';
+import {
+  useIsUploadAllFileAllowed,
+  useIsUploadEnabled,
+} from '~/states/server-configurations';
 
 import { useContextSWR } from './use-context-swr';
 
@@ -17,49 +19,78 @@ declare global {
 
 type Nullable<T> = T | null;
 
-
-export const useRegistrationWhitelist = (initialData?: Nullable<string[]>): SWRResponse<Nullable<string[]>, Error> => {
-  return useContextSWR<Nullable<string[]>, Error>('registrationWhitelist', initialData);
+export const useRegistrationWhitelist = (
+  initialData?: Nullable<string[]>,
+): SWRResponse<Nullable<string[]>, Error> => {
+  return useContextSWR<Nullable<string[]>, Error>(
+    'registrationWhitelist',
+    initialData,
+  );
 };
 
-export const useAuditLogEnabled = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useContextSWR<boolean, Error>('auditLogEnabled', initialData, { fallbackData: false });
+export const useAuditLogEnabled = (
+  initialData?: boolean,
+): SWRResponse<boolean, Error> => {
+  return useContextSWR<boolean, Error>('auditLogEnabled', initialData, {
+    fallbackData: false,
+  });
 };
 
-export const useActivityExpirationSeconds = (initialData?: number) : SWRResponse<number, Error> => {
+export const useActivityExpirationSeconds = (
+  initialData?: number,
+): SWRResponse<number, Error> => {
   return useContextSWR<number, Error>('activityExpirationSeconds', initialData);
 };
 
-export const useAuditLogAvailableActions = (initialData?: Array<SupportedActionType>) : SWRResponse<Array<SupportedActionType>, Error> => {
-  return useContextSWR<Array<SupportedActionType>, Error>('auditLogAvailableActions', initialData);
+export const useAuditLogAvailableActions = (
+  initialData?: Array<SupportedActionType>,
+): SWRResponse<Array<SupportedActionType>, Error> => {
+  return useContextSWR<Array<SupportedActionType>, Error>(
+    'auditLogAvailableActions',
+    initialData,
+  );
 };
 
-export const useIsBlinkedHeaderAtBoot = (initialData?: boolean): SWRResponse<boolean, Error> => {
+export const useIsBlinkedHeaderAtBoot = (
+  initialData?: boolean,
+): SWRResponse<boolean, Error> => {
   return useContextSWR('isBlinkedAtBoot', initialData, { fallbackData: false });
 };
 
-export const useCustomizeTitle = (initialData?: string): SWRResponse<string, Error> => {
+export const useCustomizeTitle = (
+  initialData?: string,
+): SWRResponse<string, Error> => {
   return useContextSWR('CustomizeTitle', initialData);
 };
 
-export const useIsCustomizedLogoUploaded = (initialData?: boolean): SWRResponse<boolean, Error> => {
+export const useIsCustomizedLogoUploaded = (
+  initialData?: boolean,
+): SWRResponse<boolean, Error> => {
   return useSWRStatic('isCustomizedLogoUploaded', initialData);
 };
 
-export const useGrowiAppIdForGrowiCloud = (initialData?: number): SWRResponse<number, Error> => {
+export const useGrowiAppIdForGrowiCloud = (
+  initialData?: number,
+): SWRResponse<number, Error> => {
   return useContextSWR('growiAppIdForGrowiCloud', initialData);
 };
 
-export const useIsEnableUnifiedMergeView = (initialData?: boolean): SWRResponse<boolean, Error> => {
-  return useSWRStatic<boolean, Error>('isEnableUnifiedMergeView', initialData, { fallbackData: false });
-
+export const useIsEnableUnifiedMergeView = (
+  initialData?: boolean,
+): SWRResponse<boolean, Error> => {
+  return useSWRStatic<boolean, Error>('isEnableUnifiedMergeView', initialData, {
+    fallbackData: false,
+  });
 };
 
 /** **********************************************************
  *                     Computed contexts
  *********************************************************** */
 
-export const useAcceptedUploadFileType = (): SWRResponse<AcceptedUploadFileType, Error> => {
+export const useAcceptedUploadFileType = (): SWRResponse<
+  AcceptedUploadFileType,
+  Error
+> => {
   const [isUploadEnabled] = useIsUploadEnabled();
   const [isUploadAllFileAllowed] = useIsUploadAllFileAllowed();
 

+ 28 - 13
apps/app/src/stores-universal/use-context-swr.tsx

@@ -1,29 +1,44 @@
-import assert from 'assert';
-
 import { useSWRStatic } from '@growi/core/dist/swr';
-import type {
-  Key, SWRConfiguration, SWRResponse,
-} from 'swr';
-
+import assert from 'assert';
+import type { Key, SWRConfiguration, SWRResponse } from 'swr';
 
 export function useContextSWR<Data, Error>(key: Key): SWRResponse<Data, Error>;
-export function useContextSWR<Data, Error>(key: Key, data: Data | undefined): SWRResponse<Data, Error>;
-export function useContextSWR<Data, Error>(key: Key, data: Data | undefined,
-  configuration: SWRConfiguration<Data, Error> | undefined): SWRResponse<Data, Error>;
+export function useContextSWR<Data, Error>(
+  key: Key,
+  data: Data | undefined,
+): SWRResponse<Data, Error>;
+export function useContextSWR<Data, Error>(
+  key: Key,
+  data: Data | undefined,
+  configuration: SWRConfiguration<Data, Error> | undefined,
+): SWRResponse<Data, Error>;
 
 export function useContextSWR<Data, Error>(
-    ...args: readonly [Key]
+  ...args:
+    | readonly [Key]
     | readonly [Key, Data | undefined]
-    | readonly [Key, Data | undefined, SWRConfiguration<Data, Error> | undefined]
+    | readonly [
+        Key,
+        Data | undefined,
+        SWRConfiguration<Data, Error> | undefined,
+      ]
 ): SWRResponse<Data, Error> {
   const [key, data, configuration] = args;
 
-  assert.notStrictEqual(configuration?.fetcher, null, 'useContextSWR does not support \'configuration.fetcher\'');
+  assert.notStrictEqual(
+    configuration?.fetcher,
+    null,
+    "useContextSWR does not support 'configuration.fetcher'",
+  );
 
   const swrResponse = useSWRStatic(key, data, configuration);
 
   // overwrite mutate
-  const result = Object.assign(swrResponse, { mutate: () => { throw Error('mutate can not be used in context') } });
+  const result = Object.assign(swrResponse, {
+    mutate: () => {
+      throw Error('mutate can not be used in context');
+    },
+  });
 
   return result;
 }

+ 21 - 13
apps/app/src/stores-universal/use-next-themes.tsx

@@ -9,31 +9,37 @@ export const Themes = {
   ...ColorScheme,
   SYSTEM: 'system',
 } as const;
-export type Themes = typeof Themes[keyof typeof Themes];
-
+export type Themes = (typeof Themes)[keyof typeof Themes];
 
 const ATTRIBUTE = 'data-bs-theme';
 
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
   const [forcedColorScheme] = useForcedColorScheme();
 
-  return <ThemeProvider {...props} forcedTheme={forcedColorScheme} attribute={ATTRIBUTE} />;
+  return (
+    <ThemeProvider
+      {...props}
+      forcedTheme={forcedColorScheme}
+      attribute={ATTRIBUTE}
+    />
+  );
 };
 
-type UseThemeExtendedProps = Omit<UseThemeProps, 'theme'|'resolvedTheme'> & {
-  theme: Themes,
-  resolvedTheme: ColorScheme,
-  useOsSettings: boolean,
-  isDarkMode: boolean,
-  isForcedByGrowiTheme: boolean,
-  resolvedThemeByAttributes?: ColorScheme,
-}
+type UseThemeExtendedProps = Omit<UseThemeProps, 'theme' | 'resolvedTheme'> & {
+  theme: Themes;
+  resolvedTheme: ColorScheme;
+  useOsSettings: boolean;
+  isDarkMode: boolean;
+  isForcedByGrowiTheme: boolean;
+  resolvedThemeByAttributes?: ColorScheme;
+};
 
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
   const props = useTheme();
   const [forcedColorScheme] = useForcedColorScheme();
 
-  const resolvedTheme = forcedColorScheme ?? props.resolvedTheme as ColorScheme;
+  const resolvedTheme =
+    forcedColorScheme ?? (props.resolvedTheme as ColorScheme);
 
   return Object.assign(props, {
     theme: props.theme as Themes,
@@ -41,6 +47,8 @@ export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
     useOsSettings: props.theme === Themes.SYSTEM,
     isDarkMode: resolvedTheme === ColorScheme.DARK,
     isForcedByGrowiTheme: forcedColorScheme != null,
-    resolvedThemeByAttributes: isClient() ? document.documentElement.getAttribute(ATTRIBUTE) as ColorScheme : undefined,
+    resolvedThemeByAttributes: isClient()
+      ? (document.documentElement.getAttribute(ATTRIBUTE) as ColorScheme)
+      : undefined,
   });
 };

+ 21 - 10
apps/app/src/utils/admin-page-util.ts

@@ -4,25 +4,37 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { CommonProps } from '~/pages/utils/commons';
 import {
-  getServerSideCommonProps, getNextI18NextConfig,
+  getNextI18NextConfig,
+  getServerSideCommonProps,
 } from '~/pages/utils/commons';
 
-
 /**
  * for Server Side Translations
  * @param context
  * @param props
  * @param namespacesRequired
  */
-async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props, namespacesRequired?: string[] | undefined): Promise<void> {
+async function injectNextI18NextConfigurations(
+  context: GetServerSidePropsContext,
+  props,
+  namespacesRequired?: string[] | undefined,
+): Promise<void> {
   // preload all languages because of language lists in user setting
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired, true);
+  const nextI18NextConfig = await getNextI18NextConfig(
+    serverSideTranslations,
+    context,
+    namespacesRequired,
+    true,
+  );
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 
-
-export const retrieveServerSideProps: any = async(
-    context: GetServerSidePropsContext, injectServerConfigurations?:(context: GetServerSidePropsContext, props) => Promise<void>,
+export const retrieveServerSideProps: any = async (
+  context: GetServerSidePropsContext,
+  injectServerConfigurations?: (
+    context: GetServerSidePropsContext,
+    props,
+  ) => Promise<void>,
 ) => {
   const req = context.req as CrowiRequest;
   const { user } = req;
@@ -44,9 +56,8 @@ export const retrieveServerSideProps: any = async(
     props.currentUser = user.toObject();
   }
 
-  props.isAccessDeniedForNonAdminUser = props.currentUser == null
-    ? true
-    : !props.currentUser.admin;
+  props.isAccessDeniedForNonAdminUser =
+    props.currentUser == null ? true : !props.currentUser.admin;
 
   await injectNextI18NextConfigurations(context, props, ['admin', 'commons']);
 

+ 113 - 67
apps/app/src/utils/axios-date-conversion.spec.ts

@@ -1,9 +1,7 @@
 import type { DateConvertible } from './axios';
 import { convertStringsToDates } from './axios';
 
-
 describe('convertStringsToDates', () => {
-
   // Test case 1: Basic conversion in a flat object
   test('should convert ISO date strings to Date objects in a flat object', () => {
     const dateString = '2023-01-15T10:00:00.000Z';
@@ -17,7 +15,10 @@ describe('convertStringsToDates', () => {
       createdAt: new Date(dateString),
       name: 'Test Item',
     };
-    const result = convertStringsToDates(input) as Record<string, DateConvertible>;
+    const result = convertStringsToDates(input) as Record<
+      string,
+      DateConvertible
+    >;
 
     expect(result.createdAt).toBeInstanceOf(Date);
 
@@ -61,18 +62,18 @@ describe('convertStringsToDates', () => {
       },
     };
     const result = convertStringsToDates(input) as {
-        data: {
-            item1: {
-                updatedAt: DateConvertible; // Assert 'updatedAt' later
-                value: number;
-            };
-            item2: {
-                nested: {
-                    deletedAt: DateConvertible; // Assert 'deletedAt' later
-                    isActive: boolean;
-                };
-            };
+      data: {
+        item1: {
+          updatedAt: DateConvertible; // Assert 'updatedAt' later
+          value: number;
         };
+        item2: {
+          nested: {
+            deletedAt: DateConvertible; // Assert 'deletedAt' later
+            isActive: boolean;
+          };
+        };
+      };
     };
     expect(result.data.item1.updatedAt).toBeInstanceOf(Date);
 
@@ -83,7 +84,9 @@ describe('convertStringsToDates', () => {
 
     if (result.data.item2.nested.deletedAt instanceof Date) {
       expect(result.data.item2.nested.deletedAt).toBeInstanceOf(Date);
-      expect(result.data.item2.nested.deletedAt.toISOString()).toEqual(dateString2);
+      expect(result.data.item2.nested.deletedAt.toISOString()).toEqual(
+        dateString2,
+      );
     }
 
     expect(result).toEqual(expected);
@@ -99,11 +102,15 @@ describe('convertStringsToDates', () => {
     ];
     const expected = [
       { id: 1, eventDate: new Date(dateString1) },
-      { id: 2, eventDate: new Date(dateString2), data: { nestedProp: 'value' } },
+      {
+        id: 2,
+        eventDate: new Date(dateString2),
+        data: { nestedProp: 'value' },
+      },
     ];
     const result = convertStringsToDates(input) as [
-      { id: number, eventDate: DateConvertible},
-      { id: number, eventDate: DateConvertible, data: { nestedProp: string }},
+      { id: number; eventDate: DateConvertible },
+      { id: number; eventDate: DateConvertible; data: { nestedProp: string } },
     ];
 
     expect(result[0].eventDate).toBeInstanceOf(Date);
@@ -174,12 +181,17 @@ describe('convertStringsToDates', () => {
     const dateString = '2023-01-15T10:00:00Z'; // No milliseconds
     const input = { createdAt: dateString };
     const expected = { createdAt: new Date(dateString) };
-    const result = convertStringsToDates(input) as Record<string, DateConvertible>;
+    const result = convertStringsToDates(input) as Record<
+      string,
+      DateConvertible
+    >;
 
     expect(result.createdAt).toBeInstanceOf(Date);
 
     if (result.createdAt instanceof Date) {
-      expect(result.createdAt.toISOString()).toEqual('2023-01-15T10:00:00.000Z');
+      expect(result.createdAt.toISOString()).toEqual(
+        '2023-01-15T10:00:00.000Z',
+      );
     }
 
     expect(result).toEqual(expected);
@@ -205,12 +217,12 @@ describe('convertStringsToDates', () => {
       },
     };
     const result = convertStringsToDates(input) as {
-      prop1: DateConvertible,
-      prop2: null,
+      prop1: DateConvertible;
+      prop2: null;
       prop3: {
-        nestedNull: null,
-        nestedDate: DateConvertible
-      }
+        nestedNull: null;
+        nestedDate: DateConvertible;
+      };
     };
     expect(result.prop1).toBeInstanceOf(Date);
     expect(result.prop3.nestedDate).toBeInstanceOf(Date);
@@ -236,21 +248,25 @@ describe('convertStringsToDates', () => {
     };
 
     const result = convertStringsToDates(input) as {
-      id: number,
-      eventTime: DateConvertible,
+      id: number;
+      eventTime: DateConvertible;
       details: {
-        lastActivity: DateConvertible
-      }
+        lastActivity: DateConvertible;
+      };
     };
 
     expect(result.eventTime).toBeInstanceOf(Date);
     if (result.eventTime instanceof Date) {
-      expect(result.eventTime.toISOString()).toEqual(new Date(dateStringWithOffset).toISOString());
+      expect(result.eventTime.toISOString()).toEqual(
+        new Date(dateStringWithOffset).toISOString(),
+      );
     }
 
     expect(result.details.lastActivity).toBeInstanceOf(Date);
     if (result.details.lastActivity instanceof Date) {
-      expect(result.details.lastActivity.toISOString()).toEqual(new Date('2025-06-12T05:00:00-04:00').toISOString());
+      expect(result.details.lastActivity.toISOString()).toEqual(
+        new Date('2025-06-12T05:00:00-04:00').toISOString(),
+      );
     }
 
     expect(result).toEqual(expected);
@@ -266,11 +282,16 @@ describe('convertStringsToDates', () => {
       startTime: new Date(dateStringWithNegativeOffset),
     };
 
-    const result = convertStringsToDates(input) as Record<string, DateConvertible>;
+    const result = convertStringsToDates(input) as Record<
+      string,
+      DateConvertible
+    >;
 
     expect(result.startTime).toBeInstanceOf(Date);
     if (result.startTime instanceof Date) {
-      expect(result.startTime.toISOString()).toEqual(new Date(dateStringWithNegativeOffset).toISOString());
+      expect(result.startTime.toISOString()).toEqual(
+        new Date(dateStringWithNegativeOffset).toISOString(),
+      );
     }
 
     expect(result).toEqual(expected);
@@ -286,11 +307,16 @@ describe('convertStringsToDates', () => {
       zeroOffsetDate: new Date(dateStringWithZeroOffset),
     };
 
-    const result = convertStringsToDates(input) as Record<string, DateConvertible>;
+    const result = convertStringsToDates(input) as Record<
+      string,
+      DateConvertible
+    >;
 
     expect(result.zeroOffsetDate).toBeInstanceOf(Date);
     if (result.zeroOffsetDate instanceof Date) {
-      expect(result.zeroOffsetDate.toISOString()).toEqual(new Date(dateStringWithZeroOffset).toISOString());
+      expect(result.zeroOffsetDate.toISOString()).toEqual(
+        new Date(dateStringWithZeroOffset).toISOString(),
+      );
     }
     expect(result).toEqual(expected);
   });
@@ -305,11 +331,16 @@ describe('convertStringsToDates', () => {
       detailedTime: new Date(dateStringWithMsAndOffset),
     };
 
-    const result = convertStringsToDates(input) as Record<string, DateConvertible>;
+    const result = convertStringsToDates(input) as Record<
+      string,
+      DateConvertible
+    >;
 
     expect(result.detailedTime).toBeInstanceOf(Date);
     if (result.detailedTime instanceof Date) {
-      expect(result.detailedTime.toISOString()).toEqual(new Date(dateStringWithMsAndOffset).toISOString());
+      expect(result.detailedTime.toISOString()).toEqual(
+        new Date(dateStringWithMsAndOffset).toISOString(),
+      );
     }
     expect(result).toEqual(expected);
   });
@@ -335,12 +366,12 @@ describe('convertStringsToDates', () => {
     const expected = JSON.parse(JSON.stringify(input));
 
     const result = convertStringsToDates(input) as {
-      date1: DateConvertible,
-      date2: DateConvertible,
-      date3: DateConvertible,
-      date4: DateConvertible,
-      date5: DateConvertible,
-      someOtherString: string,
+      date1: DateConvertible;
+      date2: DateConvertible;
+      date3: DateConvertible;
+      date4: DateConvertible;
+      date5: DateConvertible;
+      someOtherString: string;
     };
 
     // Assert that they remain strings (or whatever their original type was)
@@ -363,9 +394,7 @@ describe('convertStringsToDates', () => {
     expect(result).toEqual(expected);
   });
 
-
   describe('test circular reference occurrences', () => {
-
     // Test case 1: Circular references
     test('should handle circular references without crashing and preserve the cycle', () => {
       const dateString1 = '2023-02-20T12:30:00.000Z';
@@ -401,24 +430,24 @@ describe('convertStringsToDates', () => {
       const convertedOutput = convertStringsToDates(input) as {
         data: {
           item1: {
-            updatedAt: DateConvertible,
-            value: number,
-          },
+            updatedAt: DateConvertible;
+            value: number;
+          };
           item2: {
             nested1: {
-              deletedAt: DateConvertible,
-              isActive: boolean,
+              deletedAt: DateConvertible;
+              isActive: boolean;
               nested2: {
-                createdAt: DateConvertible,
-                parent: any,
-              },
-            },
+                createdAt: DateConvertible;
+                parent: any;
+              };
+            };
             anotherItem: {
-              someValue: number,
-              lastSeen: DateConvertible,
-            },
-          },
-        },
+              someValue: number;
+              lastSeen: DateConvertible;
+            };
+          };
+        };
       };
 
       // Expect the function not to have thrown an error
@@ -431,22 +460,36 @@ describe('convertStringsToDates', () => {
       // Check if the date conversion worked
       expect(convertedOutput.data.item1.updatedAt).toBeInstanceOf(Date);
       if (convertedOutput.data.item1.updatedAt instanceof Date) {
-        expect(convertedOutput.data.item1.updatedAt.toISOString()).toBe(dateString1);
+        expect(convertedOutput.data.item1.updatedAt.toISOString()).toBe(
+          dateString1,
+        );
       }
 
       expect(convertedOutput.data.item2.nested1.deletedAt).toBeInstanceOf(Date);
       if (convertedOutput.data.item2.nested1.deletedAt instanceof Date) {
-        expect(convertedOutput.data.item2.nested1.deletedAt.toISOString()).toBe(dateString2);
+        expect(convertedOutput.data.item2.nested1.deletedAt.toISOString()).toBe(
+          dateString2,
+        );
       }
 
-      expect(convertedOutput.data.item2.nested1.nested2.createdAt).toBeInstanceOf(Date);
-      if (convertedOutput.data.item2.nested1.nested2.createdAt instanceof Date) {
-        expect(convertedOutput.data.item2.nested1.nested2.createdAt.toISOString()).toBe(dateString3);
+      expect(
+        convertedOutput.data.item2.nested1.nested2.createdAt,
+      ).toBeInstanceOf(Date);
+      if (
+        convertedOutput.data.item2.nested1.nested2.createdAt instanceof Date
+      ) {
+        expect(
+          convertedOutput.data.item2.nested1.nested2.createdAt.toISOString(),
+        ).toBe(dateString3);
       }
 
-      expect(convertedOutput.data.item2.anotherItem.lastSeen).toBeInstanceOf(Date);
+      expect(convertedOutput.data.item2.anotherItem.lastSeen).toBeInstanceOf(
+        Date,
+      );
       if (convertedOutput.data.item2.anotherItem.lastSeen instanceof Date) {
-        expect(convertedOutput.data.item2.anotherItem.lastSeen.toISOString()).toBe(new Date(input.data.item2.anotherItem.lastSeen).toISOString());
+        expect(
+          convertedOutput.data.item2.anotherItem.lastSeen.toISOString(),
+        ).toBe(new Date(input.data.item2.anotherItem.lastSeen).toISOString());
       }
     });
 
@@ -456,7 +499,10 @@ describe('convertStringsToDates', () => {
       obj.self = obj;
       obj.createdAt = '2023-02-01T00:00:00Z';
 
-      const converted = convertStringsToDates(obj) as Record<string, DateConvertible>;
+      const converted = convertStringsToDates(obj) as Record<
+        string,
+        DateConvertible
+      >;
 
       expect(converted).toBeDefined();
       expect(converted.self).toBe(obj);

+ 44 - 34
apps/app/src/utils/axios.ts

@@ -6,18 +6,30 @@ import qs from 'qs';
 // eslint-disable-next-line no-restricted-imports
 export * from 'axios';
 
-const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(Z|[+-]\d{2}:\d{2})$/;
-
-export type DateConvertible = string | number | boolean | Date | null | undefined | DateConvertible[] | { [key: string]: DateConvertible };
+const isoDateRegex =
+  /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(Z|[+-]\d{2}:\d{2})$/;
+
+export type DateConvertible =
+  | string
+  | number
+  | boolean
+  | Date
+  | null
+  | undefined
+  | DateConvertible[]
+  | { [key: string]: DateConvertible };
 
 /**
-* Converts string to dates recursively.
-*
-* @param data - Data to be transformed to Date if applicable.
-* @param seen - Set containing data that has been through the function before.
-* @returns - Data containing transformed Dates.
-*/
-function convertStringsToDatesRecursive(data: DateConvertible, seen: Set<unknown>): DateConvertible {
+ * Converts string to dates recursively.
+ *
+ * @param data - Data to be transformed to Date if applicable.
+ * @param seen - Set containing data that has been through the function before.
+ * @returns - Data containing transformed Dates.
+ */
+function convertStringsToDatesRecursive(
+  data: DateConvertible,
+  seen: Set<unknown>,
+): DateConvertible {
   if (typeof data !== 'object' || data === null) {
     if (typeof data === 'string' && isoDateRegex.test(data)) {
       return new Date(data);
@@ -32,7 +44,7 @@ function convertStringsToDatesRecursive(data: DateConvertible, seen: Set<unknown
   seen.add(data);
 
   if (Array.isArray(data)) {
-    return data.map(item => convertStringsToDatesRecursive(item, seen));
+    return data.map((item) => convertStringsToDatesRecursive(item, seen));
   }
 
   const newData: Record<string, DateConvertible> = {};
@@ -42,13 +54,9 @@ function convertStringsToDatesRecursive(data: DateConvertible, seen: Set<unknown
 
     if (typeof value === 'string' && isoDateRegex.test(value)) {
       newData[key] = new Date(value);
-    }
-
-    else if (typeof value === 'object' && value !== null) {
+    } else if (typeof value === 'object' && value !== null) {
       newData[key] = convertStringsToDatesRecursive(value, seen);
-    }
-
-    else {
+    } else {
       newData[key] = value;
     }
   }
@@ -58,9 +66,15 @@ function convertStringsToDatesRecursive(data: DateConvertible, seen: Set<unknown
 
 // Function overloads for better type inference
 export function convertStringsToDates(data: string): string | Date;
-export function convertStringsToDates<T extends DateConvertible>(data: T): DateConvertible;
-export function convertStringsToDates<T extends DateConvertible[]>(data: T): DateConvertible[];
-export function convertStringsToDates<T extends Record<string, DateConvertible>>(data: T): Record<string, DateConvertible>;
+export function convertStringsToDates<T extends DateConvertible>(
+  data: T,
+): DateConvertible;
+export function convertStringsToDates<T extends DateConvertible[]>(
+  data: T,
+): DateConvertible[];
+export function convertStringsToDates<
+  T extends Record<string, DateConvertible>,
+>(data: T): Record<string, DateConvertible>;
 export function convertStringsToDates(data: DateConvertible): DateConvertible {
   return convertStringsToDatesRecursive(data, new Set());
 }
@@ -70,34 +84,30 @@ let baseTransformers = axios.defaults.transformResponse;
 
 if (baseTransformers == null) {
   baseTransformers = [];
-}
-
-else if (!Array.isArray(baseTransformers)) {
+} else if (!Array.isArray(baseTransformers)) {
   // If it's a single transformer function, wrap it in an array
   baseTransformers = [baseTransformers];
 }
 
-
 const customAxios = axios.create({
   headers: {
     'X-Requested-With': 'XMLHttpRequest',
     'Content-Type': 'application/json',
   },
 
-  transformResponse: baseTransformers.concat(
-    (data) => {
-      return convertStringsToDates(data);
-    },
-  ),
+  transformResponse: baseTransformers.concat((data) => {
+    return convertStringsToDates(data);
+  }),
 });
 
 // serialize Date config: https://github.com/axios/axios/issues/1548#issuecomment-548306666
 customAxios.interceptors.request.use((config) => {
-  config.paramsSerializer = params => qs.stringify(params, {
-    serializeDate: (date: Date) => {
-      return formatISO(date, { representation: 'complete' });
-    },
-  });
+  config.paramsSerializer = (params) =>
+    qs.stringify(params, {
+      serializeDate: (date: Date) => {
+        return formatISO(date, { representation: 'complete' });
+      },
+    });
   return config;
 });
 

+ 2 - 1
apps/app/src/utils/gravatar.ts

@@ -1,6 +1,7 @@
 import md5 from 'md5';
 
-export const GRAVATAR_DEFAULT = 'https://gravatar.com/avatar/00000000000000000000000000000000?s=24';
+export const GRAVATAR_DEFAULT =
+  'https://gravatar.com/avatar/00000000000000000000000000000000?s=24';
 
 export const generateGravatarSrc = (email?: string): string => {
   const hash = md5((email ?? '').trim().toLowerCase());

+ 7 - 7
apps/app/src/utils/logger/index.ts

@@ -1,17 +1,17 @@
-import type Logger from 'bunyan';
-import { createLogger, type UniversalBunyanConfig } from 'universal-bunyan';
-
 import configForDev from '^/config/logger/config.dev';
 import configForProd from '^/config/logger/config.prod';
+import type Logger from 'bunyan';
+import { createLogger, type UniversalBunyanConfig } from 'universal-bunyan';
 
 const isProduction = process.env.NODE_ENV === 'production';
-const config = (isProduction ? configForProd : configForDev) as UniversalBunyanConfig;
+const config = (
+  isProduction ? configForProd : configForDev
+) as UniversalBunyanConfig;
 
-const loggerFactory = function(name: string): Logger {
-  return createLogger({
+const loggerFactory = (name: string): Logger =>
+  createLogger({
     name,
     config,
   });
-};
 
 export default loggerFactory;

+ 4 - 4
apps/app/src/utils/next.config.utils.js

@@ -24,10 +24,10 @@ exports.listScopedPackages = (scopes, opts = defaultOpts) => {
 
   nodeModulesPaths.forEach((nodeModulesPath) => {
     fs.readdirSync(nodeModulesPath)
-      .filter(name => scopes.includes(name))
+      .filter((name) => scopes.includes(name))
       .forEach((scope) => {
         fs.readdirSync(path.resolve(nodeModulesPath, scope))
-          .filter(name => !name.startsWith('.'))
+          .filter((name) => !name.startsWith('.'))
           .forEach((folderName) => {
             const packageJsonPath = path.resolve(
               nodeModulesPath,
@@ -57,8 +57,8 @@ exports.listPrefixedPackages = (prefixes, opts = defaultOpts) => {
 
   nodeModulesPaths.forEach((nodeModulesPath) => {
     fs.readdirSync(nodeModulesPath)
-      .filter(name => prefixes.some(prefix => name.startsWith(prefix)))
-      .filter(name => !name.startsWith('.'))
+      .filter((name) => prefixes.some((prefix) => name.startsWith(prefix)))
+      .filter((name) => !name.startsWith('.'))
       .forEach((folderName) => {
         const packageJsonPath = path.resolve(
           nodeModulesPath,

+ 6 - 3
apps/app/src/utils/object-utils.ts

@@ -1,9 +1,12 @@
 // remove property if value is null
 
-export const removeNullPropertyFromObject = <T extends object>(object: T): T => {
-
+export const removeNullPropertyFromObject = <T extends object>(
+  object: T,
+): T => {
   for (const [key, value] of Object.entries(object)) {
-    if (value == null) { delete object[key] }
+    if (value == null) {
+      delete object[key];
+    }
   }
 
   return object;

+ 10 - 7
apps/app/src/utils/page-delete-config.ts

@@ -1,10 +1,8 @@
 import type {
-  IPageDeleteConfigValueToProcessValidation,
   IPageDeleteConfigValue,
+  IPageDeleteConfigValueToProcessValidation,
 } from '~/interfaces/page-delete-config';
-import {
-  PageDeleteConfigValue as Value,
-} from '~/interfaces/page-delete-config';
+import { PageDeleteConfigValue as Value } from '~/interfaces/page-delete-config';
 
 /**
  * Return true if "configForRecursive" is stronger than "configForSingle"
@@ -14,7 +12,8 @@ import {
  * @returns boolean
  */
 export const validateDeleteConfigs = (
-    configForSingle: IPageDeleteConfigValueToProcessValidation, configForRecursive: IPageDeleteConfigValueToProcessValidation,
+  configForSingle: IPageDeleteConfigValueToProcessValidation,
+  configForRecursive: IPageDeleteConfigValueToProcessValidation,
 ): boolean => {
   if (configForSingle === Value.Anyone) {
     switch (configForRecursive) {
@@ -55,8 +54,12 @@ export const validateDeleteConfigs = (
  * @returns [(value for single), (value for recursive)]
  */
 export const prepareDeleteConfigValuesForCalc = (
-    confForSingle: IPageDeleteConfigValueToProcessValidation | undefined, confForRecursive: IPageDeleteConfigValue | undefined,
-): [IPageDeleteConfigValueToProcessValidation, IPageDeleteConfigValueToProcessValidation] => {
+  confForSingle: IPageDeleteConfigValueToProcessValidation | undefined,
+  confForRecursive: IPageDeleteConfigValue | undefined,
+): [
+  IPageDeleteConfigValueToProcessValidation,
+  IPageDeleteConfigValueToProcessValidation,
+] => {
   // convert undefined to default values
   const confForSingleToReturn = confForSingle ?? Value.Anyone;
   const confForRecursiveToReturn = confForRecursive ?? Value.Inherit;

+ 6 - 2
apps/app/src/utils/page-operation.ts

@@ -1,5 +1,9 @@
 import type { IPageOperationProcessData } from '~/interfaces/page-operation';
 
-export const shouldRecoverPagePaths = (processData: IPageOperationProcessData): boolean => {
-  return processData.Rename?.Sub != null ? processData.Rename.Sub.isProcessable : false;
+export const shouldRecoverPagePaths = (
+  processData: IPageOperationProcessData,
+): boolean => {
+  return processData.Rename?.Sub != null
+    ? processData.Rename.Sub.isProcessable
+    : false;
 };

+ 1 - 2
apps/app/src/utils/project-dir-utils.ts

@@ -1,11 +1,10 @@
 /* eslint-disable import/prefer-default-export */
 
+import { isServer } from '@growi/core/dist/utils/browser-utils';
 import fs from 'fs';
 import path from 'path';
 import process from 'process';
 
-import { isServer } from '@growi/core/dist/utils/browser-utils';
-
 const isCurrentDirRoot = isServer() && fs.existsSync('./next.config.js');
 
 export const projectRoot = isCurrentDirRoot

+ 12 - 12
apps/app/src/utils/promise.spec.ts

@@ -1,14 +1,14 @@
 import { batchProcessPromiseAll } from './promise';
 
 describe('batchProcessPromiseAll', () => {
-  it('processes items in batch', async() => {
+  it('processes items in batch', async () => {
     const batch1 = [1, 2, 3, 4, 5];
     const batch2 = [6, 7, 8, 9, 10];
     const batch3 = [11, 12];
     const all = [...batch1, ...batch2, ...batch3];
 
     const actualProcessedBatches: number[][] = [];
-    const result = await batchProcessPromiseAll(all, 5, async(num, i, arr) => {
+    const result = await batchProcessPromiseAll(all, 5, async (num, i, arr) => {
       if (arr != null && i === 0) {
         actualProcessedBatches.push(arr);
       }
@@ -18,17 +18,13 @@ describe('batchProcessPromiseAll', () => {
     const expected = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120];
 
     expect(result).toStrictEqual(expected);
-    expect(actualProcessedBatches).toStrictEqual([
-      batch1,
-      batch2,
-      batch3,
-    ]);
+    expect(actualProcessedBatches).toStrictEqual([batch1, batch2, batch3]);
   });
 
   describe('error handling', () => {
     const all = [1, 2, 3, 4, 5, 6, 7, 8, '9', 10];
 
-    const multiplyBy10 = async(num) => {
+    const multiplyBy10 = async (num) => {
       if (typeof num !== 'number') {
         throw new Error('Is not number');
       }
@@ -36,15 +32,19 @@ describe('batchProcessPromiseAll', () => {
     };
 
     describe('when throwIfRejected is true', () => {
-      it('throws error when there is a Promise rejection', async() => {
-        await expect(batchProcessPromiseAll(all, 5, multiplyBy10)).rejects.toThrow('Is not number');
+      it('throws error when there is a Promise rejection', async () => {
+        await expect(
+          batchProcessPromiseAll(all, 5, multiplyBy10),
+        ).rejects.toThrow('Is not number');
       });
     });
 
     describe('when throwIfRejected is false', () => {
-      it('doesn\'t throw error when there is a Promise rejection', async() => {
+      it("doesn't throw error when there is a Promise rejection", async () => {
         const expected = [10, 20, 30, 40, 50, 60, 70, 80, 100];
-        await expect(batchProcessPromiseAll(all, 5, multiplyBy10, false)).resolves.toStrictEqual(expected);
+        await expect(
+          batchProcessPromiseAll(all, 5, multiplyBy10, false),
+        ).resolves.toStrictEqual(expected);
       });
     });
   });

+ 5 - 4
apps/app/src/utils/promise.ts

@@ -7,7 +7,7 @@
  * @param throwIfRejected whether or not to throw Error when there is a rejected Promise
  * @returns result of fn applied to each item
  */
-export const batchProcessPromiseAll = async<I, O>(
+export const batchProcessPromiseAll = async <I, O>(
   items: Array<I>,
   limit: number,
   fn: (item: I, index?: number, array?: Array<I>) => Promise<O>,
@@ -19,13 +19,14 @@ export const batchProcessPromiseAll = async<I, O>(
     const end = Math.min(start + limit, items.length);
 
     // eslint-disable-next-line no-await-in-loop
-    const slicedResults = await Promise.allSettled(items.slice(start, end).map(fn));
+    const slicedResults = await Promise.allSettled(
+      items.slice(start, end).map(fn),
+    );
 
     slicedResults.forEach((result) => {
       if (result.status === 'fulfilled') {
         results.push(result.value);
-      }
-      else if (throwIfRejected && result.reason instanceof Error) {
+      } else if (throwIfRejected && result.reason instanceof Error) {
         throw result.reason;
       }
     });

+ 2 - 2
apps/app/src/utils/swr-utils.ts

@@ -7,7 +7,7 @@ export const swrGlobalConfiguration = Object.assign(
   // set the request scoped cache provider in server
   isServer()
     ? {
-      provider: (cache: any) => new Map<string, any>(cache),
-    }
+        provider: (cache: any) => new Map<string, any>(cache),
+      }
     : {},
 );

+ 0 - 2
apps/app/src/utils/to-array-from-csv.spec.ts

@@ -1,7 +1,6 @@
 import { toArrayFromCsv } from './to-array-from-csv';
 
 describe('To array from csv', () => {
-
   test('case 1', () => {
     const result = toArrayFromCsv('dev,general');
     expect(result).toStrictEqual(['dev', 'general']);
@@ -26,5 +25,4 @@ describe('To array from csv', () => {
     const result = toArrayFromCsv(',dev,general');
     expect(result).toStrictEqual(['dev', 'general']);
   });
-
 });

+ 2 - 2
apps/app/src/utils/to-array-from-csv.ts

@@ -6,8 +6,8 @@ export const toArrayFromCsv = (text: string): string[] => {
 
   const array = text
     .split(',')
-    .map(el => el.trim())
-    .filter(el => el !== '');
+    .map((el) => el.trim())
+    .filter((el) => el !== '');
 
   return array;
 };

+ 1 - 4
apps/app/src/utils/vo/transfer-key.ts

@@ -2,7 +2,6 @@
  * VO for TransferKey which has appSiteUrlOrigin and key as its public member
  */
 export class TransferKey {
-
   private static _internalSeperator = '__grw_internal_tranferkey__';
 
   public appSiteUrlOrigin: string;
@@ -37,8 +36,7 @@ export class TransferKey {
     let appSiteUrlOrigin: string;
     try {
       appSiteUrlOrigin = new URL(appSiteUrl).origin;
-    }
-    catch (e) {
+    } catch (e) {
       throw Error(generalErrorPhrase + (e as Error));
     }
 
@@ -54,5 +52,4 @@ export class TransferKey {
   static generateKeyString(key: string, appSiteUrlOrigin: string): string {
     return `${key}${TransferKey._internalSeperator}${appSiteUrlOrigin}`;
   }
-
 }

+ 0 - 2
biome.json

@@ -35,9 +35,7 @@
       "!apps/app/src/server/**",
       "!apps/app/src/services/**",
       "!apps/app/src/stores/**",
-      "!apps/app/src/stores-universal/**",
       "!apps/app/src/styles/**",
-      "!apps/app/src/utils/**",
       "!apps/app/test/integration/service/**",
       "!apps/app/test-with-vite/**"
     ]