Procházet zdrojové kódy

Merge branch 'master' into fix/123238-unable-to-perform-bookmark-operations-from-bookmark-item-control

Shun Miyazawa před 2 roky
rodič
revize
e79c8567d8

+ 9 - 0
apps/app/.env.test

@@ -0,0 +1,9 @@
+##
+## Handled by vite
+## https://vitejs.dev/guide/env-and-mode.html
+##
+## > To prevent accidentally leaking env variables to the client, only variables prefixed with
+## > VITE_ are exposed to your Vite-processed code. e.g. for the following env variables:
+##
+VITE_MONGOMS_VERSION="6.0.6"
+# VITE_MONGOMS_DEBUG=1

+ 5 - 1
apps/app/package.json

@@ -35,9 +35,11 @@
     "prelint:swagger2openapi": "yarn openapi:v3",
     "test": "run-p test:*",
     "test:jest": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --logHeapUsage",
-    "test:vitest": "vitest run config src --coverage",
+    "test:vitest": "run-p vitest:run vitest:run:integ",
     "jest:run": "cross-env NODE_ENV=test jest --passWithNoTests -- ",
     "reg:run": "reg-suit run",
+    "vitest:run": "vitest run config src --coverage",
+    "vitest:run:integ": "vitest run -c vitest.config.integ.ts src --coverage",
     "//// misc": "",
     "console": "yarn cross-env NODE_ENV=development yarn ts-node --experimental-repl-await src/server/console.js",
     "swagger-jsdoc": "swagger-jsdoc -o tmp/swagger.json -d config/swagger-definition.js",
@@ -131,6 +133,7 @@
     "mongoose-unique-validator": "^2.0.3",
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
+    "mustache": "^4.2.0",
     "next": "^13.3.0",
     "next-i18next": "^13.2.1",
     "next-superjson": "^0.0.4",
@@ -229,6 +232,7 @@
     "jquery.cookie": "~1.4.1",
     "load-css-file": "^1.0.0",
     "material-icons": "^1.11.3",
+    "mongodb-memory-server": "^8.12.2",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",
     "penpal": "^4.0.0",

+ 27 - 8
apps/app/src/components/TemplateModal.tsx → apps/app/src/components/TemplateModal/index.tsx

@@ -1,6 +1,8 @@
-import React, { useCallback, useState } from 'react';
+import React, {
+  useCallback, useEffect, useState,
+} from 'react';
 
-import { ITemplate } from '@growi/core';
+import type { ITemplate } from '@growi/core/dist/interfaces/template';
 import { useTranslation } from 'next-i18next';
 import {
   Modal,
@@ -12,8 +14,13 @@ import {
 import { useTemplateModal } from '~/stores/modal';
 import { usePreviewOptions } from '~/stores/renderer';
 import { useTemplates } from '~/stores/template';
+import loggerFactory from '~/utils/logger';
 
-import Preview from './PageEditor/Preview';
+import Preview from '../PageEditor/Preview';
+
+import { useFormatter } from './use-formatter';
+
+const logger = loggerFactory('growi:components:TemplateModal');
 
 
 type TemplateRadioButtonProps = {
@@ -44,6 +51,7 @@ const TemplateRadioButton = ({ template, onChange, isSelected }: TemplateRadioBu
 export const TemplateModal = (): JSX.Element => {
   const { t } = useTranslation();
 
+
   const { data: templateModalStatus, close } = useTemplateModal();
 
   const { data: rendererOptions } = usePreviewOptions();
@@ -51,16 +59,27 @@ export const TemplateModal = (): JSX.Element => {
 
   const [selectedTemplate, setSelectedTemplate] = useState<ITemplate>();
 
+  const { format } = useFormatter();
+
   const submitHandler = useCallback((template?: ITemplate) => {
-    if (templateModalStatus == null) { return }
+    if (templateModalStatus == null || selectedTemplate == null) {
+      return;
+    }
+
     if (templateModalStatus.onSubmit == null || template == null) {
       close();
       return;
     }
 
-    templateModalStatus.onSubmit(template.markdown);
+    templateModalStatus.onSubmit(format(selectedTemplate));
     close();
-  }, [close, templateModalStatus]);
+  }, [close, format, selectedTemplate, templateModalStatus]);
+
+  useEffect(() => {
+    if (!templateModalStatus?.isOpened) {
+      setSelectedTemplate(undefined);
+    }
+  }, [templateModalStatus?.isOpened]);
 
   if (templates == null || templateModalStatus == null) {
     return <></>;
@@ -86,13 +105,13 @@ export const TemplateModal = (): JSX.Element => {
           </div>
         </div>
 
-        { rendererOptions != null && (
+        { rendererOptions != null && selectedTemplate != null && (
           <>
             <hr />
             <h3>Preview</h3>
             <div className='card'>
               <div className="card-body" style={{ maxHeight: '60vh', overflowY: 'auto' }}>
-                <Preview rendererOptions={rendererOptions} markdown={selectedTemplate?.markdown}/>
+                <Preview rendererOptions={rendererOptions} markdown={format(selectedTemplate)}/>
               </div>
             </div>
           </>

+ 101 - 0
apps/app/src/components/TemplateModal/use-formatter.spec.tsx

@@ -0,0 +1,101 @@
+import type { ITemplate } from '@growi/core/dist/interfaces/template';
+import { mock } from 'vitest-mock-extended';
+
+import { useFormatter } from './use-formatter';
+
+
+const mocks = vi.hoisted(() => {
+  return {
+    useCurrentPagePathMock: vi.fn(() => { return {} }),
+  };
+});
+
+vi.mock('~/stores/page', () => {
+  return { useCurrentPagePath: mocks.useCurrentPagePathMock };
+});
+
+
+describe('useFormatter', () => {
+
+  describe('format()', () => {
+
+    it('returns an empty string when the argument is undefined', () => {
+      // setup
+      const mastacheMock = {
+        render: vi.fn(),
+      };
+      vi.doMock('mustache', () => mastacheMock);
+
+      // when
+      const { format } = useFormatter();
+      // call with undefined
+      const markdown = format(undefined);
+
+      // then
+      expect(markdown).toBe('');
+      expect(mastacheMock.render).not.toHaveBeenCalled();
+    });
+
+  });
+
+  it('returns markdown as-is when mustache.render throws an error', () => {
+    // setup
+    const mastacheMock = {
+      render: vi.fn(() => { throw new Error() }),
+    };
+    vi.doMock('mustache', () => mastacheMock);
+
+    // when
+    const { format } = useFormatter();
+    const template = mock<ITemplate>();
+    template.markdown = 'markdown body';
+    const markdown = format(template);
+
+    // then
+    expect(markdown).toBe('markdown body');
+  });
+
+  it('returns markdown formatted when currentPagePath is undefined', () => {
+    // when
+    const { format } = useFormatter();
+    const template = mock<ITemplate>();
+    template.markdown = `
+title: {{{title}}}{{^title}}(empty){{/title}}
+path: {{{path}}}
+`;
+    const markdown = format(template);
+
+    // then
+    expect(markdown).toBe(`
+title: (empty)
+path: /
+`);
+  });
+
+  it('returns markdown formatted', () => {
+    // setup
+    mocks.useCurrentPagePathMock.mockImplementation(() => {
+      return { data: '/Sandbox' };
+    });
+    // 2023/5/31 15:01:xx
+    vi.setSystemTime(new Date(2023, 4, 31, 15, 1));
+
+    // when
+    const { format } = useFormatter();
+    const template = mock<ITemplate>();
+    template.markdown = `
+title: {{{title}}}
+path: {{{path}}}
+date: {{yyyy}}/{{MM}}/{{dd}} {{HH}}:{{mm}}
+`;
+    const markdown = format(template);
+
+    // then
+    expect(markdown).toBe(`
+title: Sandbox
+path: /Sandbox
+date: 2023/05/31 15:01
+`);
+  });
+
+});

+ 48 - 0
apps/app/src/components/TemplateModal/use-formatter.tsx

@@ -0,0 +1,48 @@
+import path from 'path';
+
+import type { ITemplate } from '@growi/core/dist/interfaces/template';
+import dateFnsFormat from 'date-fns/format';
+import mustache from 'mustache';
+
+import { useCurrentPagePath } from '~/stores/page';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:components:TemplateModal:use-formatter');
+
+
+type FormatMethod = (selectedTemplate?: ITemplate) => string;
+type FormatterData = {
+  format: FormatMethod,
+}
+
+export const useFormatter = (): FormatterData => {
+  const { data: currentPagePath } = useCurrentPagePath();
+
+  const format: FormatMethod = (selectedTemplate) => {
+    if (selectedTemplate == null) {
+      return '';
+    }
+
+    // replace placeholder
+    let markdown = selectedTemplate.markdown;
+    const now = new Date();
+    try {
+      markdown = mustache.render(selectedTemplate.markdown, {
+        title: path.basename(currentPagePath ?? '/'),
+        path: currentPagePath ?? '/',
+        yyyy: dateFnsFormat(now, 'yyyy'),
+        MM: dateFnsFormat(now, 'MM'),
+        dd: dateFnsFormat(now, 'dd'),
+        HH: dateFnsFormat(now, 'HH'),
+        mm: dateFnsFormat(now, 'mm'),
+      });
+    }
+    catch (err) {
+      logger.warn('An error occured while ejs processing.', err);
+    }
+
+    return markdown;
+  };
+
+  return { format };
+};

+ 2 - 4
apps/app/src/server/crowi/index.js

@@ -23,7 +23,7 @@ import GrowiPlugin from '../models/growi-plugin';
 import PageRedirect from '../models/page-redirect';
 import Tag from '../models/tag';
 import UserGroup from '../models/user-group';
-import AclService from '../service/acl';
+import { aclService as aclServiceSingletonInstance } from '../service/acl';
 import AppService from '../service/app';
 import AttachmentService from '../service/attachment';
 import { configManager as configManagerSingletonInstance } from '../service/config-manager';
@@ -612,9 +612,7 @@ Crowi.prototype.setUpXss = async function() {
  * setup AclService
  */
 Crowi.prototype.setUpAcl = async function() {
-  if (this.aclService == null) {
-    this.aclService = new AclService(this.configManager);
-  }
+  this.aclService = aclServiceSingletonInstance;
 };
 
 /**

+ 0 - 221
apps/app/src/server/service/acl.integ.test.ts

@@ -1,221 +0,0 @@
-// import {
-//   vi,
-//   beforeAll, beforeEach, afterEach,
-//   describe, test, expect,
-// } from 'vitest';
-
-// import { aclService } from './acl';
-// import { configManager } from './config-manager';
-
-
-// describe('AclService', () => {
-//   test("has consts 'isLabeledStatement'", () => {
-//     expect(aclService.labels.SECURITY_RESTRICT_GUEST_MODE_DENY).toBe('Deny');
-//     expect(aclService.labels.SECURITY_RESTRICT_GUEST_MODE_READONLY).toBe('Readonly');
-//     expect(aclService.labels.SECURITY_REGISTRATION_MODE_OPEN).toBe('Open');
-//     expect(aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED).toBe('Restricted');
-//     expect(aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED).toBe('Closed');
-//   });
-// });
-
-// describe('AclService test', () => {
-
-//   const initialEnv = process.env;
-
-//   beforeAll(async() => {
-//     await configManager.loadConfigs();
-//   });
-
-//   afterEach(() => {
-//     process.env = initialEnv;
-//   });
-
-//   describe('isAclEnabled()', () => {
-
-//     test('to be false when FORCE_WIKI_MODE is undefined', async() => {
-//       delete process.env.FORCE_WIKI_MODE;
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isAclEnabled();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe(undefined);
-//       expect(result).toBe(true);
-//     });
-
-//     test('to be false when FORCE_WIKI_MODE is dummy string', async() => {
-//       process.env.FORCE_WIKI_MODE = 'dummy string';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isAclEnabled();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('dummy string');
-//       expect(result).toBe(true);
-//     });
-
-//     test('to be true when FORCE_WIKI_MODE=private', async() => {
-//       process.env.FORCE_WIKI_MODE = 'private';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isAclEnabled();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('private');
-//       expect(result).toBe(true);
-//     });
-
-//     test('to be false when FORCE_WIKI_MODE=public', async() => {
-//       process.env.FORCE_WIKI_MODE = 'public';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isAclEnabled();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('public');
-//       expect(result).toBe(false);
-//     });
-
-//   });
-
-
-//   describe('isWikiModeForced()', () => {
-
-//     test('to be false when FORCE_WIKI_MODE is undefined', async() => {
-//       delete process.env.FORCE_WIKI_MODE;
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isWikiModeForced();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe(undefined);
-//       expect(result).toBe(false);
-//     });
-
-//     test('to be false when FORCE_WIKI_MODE is dummy string', async() => {
-//       process.env.FORCE_WIKI_MODE = 'dummy string';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isWikiModeForced();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('dummy string');
-//       expect(result).toBe(false);
-//     });
-
-//     test('to be true when FORCE_WIKI_MODE=private', async() => {
-//       process.env.FORCE_WIKI_MODE = 'private';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isWikiModeForced();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('private');
-//       expect(result).toBe(true);
-//     });
-
-//     test('to be false when FORCE_WIKI_MODE=public', async() => {
-//       process.env.FORCE_WIKI_MODE = 'public';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isWikiModeForced();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('public');
-//       expect(result).toBe(true);
-//     });
-
-//   });
-
-
-//   describe('isGuestAllowedToRead()', () => {
-//     let getConfigSpy;
-
-//     beforeEach(async() => {
-//       // prepare spy for ConfigManager.getConfig
-//       getConfigSpy = vi.spyOn(configManager, 'getConfig');
-//     });
-
-//     test('to be false when FORCE_WIKI_MODE=private', async() => {
-//       process.env.FORCE_WIKI_MODE = 'private';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isGuestAllowedToRead();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('private');
-//       expect(getConfigSpy).not.toHaveBeenCalledWith('crowi', 'security:restrictGuestMode');
-//       expect(result).toBe(false);
-//     });
-
-//     test('to be true when FORCE_WIKI_MODE=public', async() => {
-//       process.env.FORCE_WIKI_MODE = 'public';
-
-//       // reload
-//       await configManager.loadConfigs();
-
-//       const result = aclService.isGuestAllowedToRead();
-
-//       const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
-//       expect(wikiMode).toBe('public');
-//       expect(getConfigSpy).not.toHaveBeenCalledWith('crowi', 'security:restrictGuestMode');
-//       expect(result).toBe(true);
-//     });
-
-//     /* eslint-disable indent */
-//     describe.each`
-//       restrictGuestMode   | expected
-//       ${undefined}        | ${false}
-//       ${'Deny'}           | ${false}
-//       ${'Readonly'}       | ${true}
-//       ${'Open'}           | ${false}
-//       ${'Restricted'}     | ${false}
-//       ${'closed'}         | ${false}
-//     `('to be $expected', ({ restrictGuestMode, expected }) => {
-//       test(`when FORCE_WIKI_MODE is undefined and 'security:restrictGuestMode' is '${restrictGuestMode}`, async() => {
-
-//         // reload
-//         await configManager.loadConfigs();
-
-//         // setup mock implementation
-//         getConfigSpy.mockImplementation((ns, key) => {
-//           if (ns === 'crowi' && key === 'security:restrictGuestMode') {
-//             return restrictGuestMode;
-//           }
-//           if (ns === 'crowi' && key === 'security:wikiMode') {
-//             return undefined;
-//           }
-//           throw new Error('Unexpected behavior.');
-//         });
-
-//         const result = aclService.isGuestAllowedToRead();
-
-//         expect(getConfigSpy).toHaveBeenCalledTimes(2);
-//         expect(getConfigSpy).toHaveBeenCalledWith('crowi', 'security:wikiMode');
-//         expect(getConfigSpy).toHaveBeenCalledWith('crowi', 'security:restrictGuestMode');
-//         expect(result).toBe(expected);
-//       });
-//     });
-
-//   });
-
-
-// });

+ 51 - 38
apps/app/test/integration/service/acl.test.js → apps/app/src/server/service/acl.integ.ts

@@ -1,15 +1,28 @@
-const { getInstance } = require('../setup-crowi');
+import { aclService } from './acl';
+import { configManager } from './config-manager';
+
+
+describe('AclService', () => {
+  test("has consts 'isLabeledStatement'", () => {
+    expect(aclService.labels.SECURITY_RESTRICT_GUEST_MODE_DENY).toBe('Deny');
+    expect(aclService.labels.SECURITY_RESTRICT_GUEST_MODE_READONLY).toBe('Readonly');
+    expect(aclService.labels.SECURITY_REGISTRATION_MODE_OPEN).toBe('Open');
+    expect(aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED).toBe('Restricted');
+    expect(aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED).toBe('Closed');
+  });
+});
 
 describe('AclService test', () => {
-  let crowi;
 
   const initialEnv = process.env;
 
-  beforeEach(async() => {
-    crowi = await getInstance();
-    process.env = initialEnv;
+  beforeAll(async() => {
+    await configManager.loadConfigs();
   });
 
+  afterEach(() => {
+    process.env = initialEnv;
+  });
 
   describe('isAclEnabled()', () => {
 
@@ -17,11 +30,11 @@ describe('AclService test', () => {
       delete process.env.FORCE_WIKI_MODE;
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isAclEnabled();
+      const result = aclService.isAclEnabled();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe(undefined);
       expect(result).toBe(true);
     });
@@ -30,11 +43,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'dummy string';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isAclEnabled();
+      const result = aclService.isAclEnabled();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('dummy string');
       expect(result).toBe(true);
     });
@@ -43,11 +56,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'private';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isAclEnabled();
+      const result = aclService.isAclEnabled();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('private');
       expect(result).toBe(true);
     });
@@ -56,11 +69,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'public';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isAclEnabled();
+      const result = aclService.isAclEnabled();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('public');
       expect(result).toBe(false);
     });
@@ -74,11 +87,11 @@ describe('AclService test', () => {
       delete process.env.FORCE_WIKI_MODE;
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isWikiModeForced();
+      const result = aclService.isWikiModeForced();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe(undefined);
       expect(result).toBe(false);
     });
@@ -87,11 +100,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'dummy string';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isWikiModeForced();
+      const result = aclService.isWikiModeForced();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('dummy string');
       expect(result).toBe(false);
     });
@@ -100,11 +113,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'private';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isWikiModeForced();
+      const result = aclService.isWikiModeForced();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('private');
       expect(result).toBe(true);
     });
@@ -113,11 +126,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'public';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isWikiModeForced();
+      const result = aclService.isWikiModeForced();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('public');
       expect(result).toBe(true);
     });
@@ -130,18 +143,18 @@ describe('AclService test', () => {
 
     beforeEach(async() => {
       // prepare spy for ConfigManager.getConfig
-      getConfigSpy = jest.spyOn(crowi.configManager, 'getConfig');
+      getConfigSpy = vi.spyOn(configManager, 'getConfig');
     });
 
     test('to be false when FORCE_WIKI_MODE=private', async() => {
       process.env.FORCE_WIKI_MODE = 'private';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isGuestAllowedToRead();
+      const result = aclService.isGuestAllowedToRead();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('private');
       expect(getConfigSpy).not.toHaveBeenCalledWith('crowi', 'security:restrictGuestMode');
       expect(result).toBe(false);
@@ -151,11 +164,11 @@ describe('AclService test', () => {
       process.env.FORCE_WIKI_MODE = 'public';
 
       // reload
-      await crowi.configManager.loadConfigs();
+      await configManager.loadConfigs();
 
-      const result = crowi.aclService.isGuestAllowedToRead();
+      const result = aclService.isGuestAllowedToRead();
 
-      const wikiMode = crowi.configManager.getConfig('crowi', 'security:wikiMode');
+      const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
       expect(wikiMode).toBe('public');
       expect(getConfigSpy).not.toHaveBeenCalledWith('crowi', 'security:restrictGuestMode');
       expect(result).toBe(true);
@@ -174,7 +187,7 @@ describe('AclService test', () => {
       test(`when FORCE_WIKI_MODE is undefined and 'security:restrictGuestMode' is '${restrictGuestMode}`, async() => {
 
         // reload
-        await crowi.configManager.loadConfigs();
+        await configManager.loadConfigs();
 
         // setup mock implementation
         getConfigSpy.mockImplementation((ns, key) => {
@@ -187,7 +200,7 @@ describe('AclService test', () => {
           throw new Error('Unexpected behavior.');
         });
 
-        const result = crowi.aclService.isGuestAllowedToRead();
+        const result = aclService.isGuestAllowedToRead();
 
         expect(getConfigSpy).toHaveBeenCalledTimes(2);
         expect(getConfigSpy).toHaveBeenCalledWith('crowi', 'security:wikiMode');

+ 19 - 27
apps/app/src/server/service/acl.js → apps/app/src/server/service/acl.ts

@@ -1,16 +1,25 @@
 import loggerFactory from '~/utils/logger';
 
-// eslint-disable-next-line no-unused-vars
+import { configManager } from './config-manager';
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 const logger = loggerFactory('growi:service:AclService');
 
+export interface AclService {
+  get labels(): { [key: string]: string },
+  isAclEnabled(): boolean,
+  isWikiModeForced(): boolean,
+  isGuestAllowedToRead(): boolean,
+  getGuestModeValue(): string,
+}
+
 /**
  * the service class of AclService
  */
-class AclService {
+class AclServiceImpl implements AclService {
 
-  constructor(configManager) {
-    this.configManager = configManager;
-    this.labels = {
+  get labels() {
+    return {
       SECURITY_RESTRICT_GUEST_MODE_DENY: 'Deny',
       SECURITY_RESTRICT_GUEST_MODE_READONLY: 'Readonly',
       SECURITY_REGISTRATION_MODE_OPEN: 'Open',
@@ -23,7 +32,7 @@ class AclService {
    * @returns Whether Access Control is enabled or not
    */
   isAclEnabled() {
-    const wikiMode = this.configManager.getConfig('crowi', 'security:wikiMode');
+    const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
     return wikiMode !== 'public';
   }
 
@@ -31,7 +40,7 @@ class AclService {
    * @returns Whether wiki mode is set
    */
   isWikiModeForced() {
-    const wikiMode = this.configManager.getConfig('crowi', 'security:wikiMode');
+    const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
     const isPrivateOrPublic = wikiMode === 'private' || wikiMode === 'public';
 
     return isPrivateOrPublic;
@@ -41,7 +50,7 @@ class AclService {
    * @returns Whether guest users are allowed to read public pages
    */
   isGuestAllowedToRead() {
-    const wikiMode = this.configManager.getConfig('crowi', 'security:wikiMode');
+    const wikiMode = configManager.getConfig('crowi', 'security:wikiMode');
 
     // return false if private wiki mode
     if (wikiMode === 'private') {
@@ -52,7 +61,7 @@ class AclService {
       return true;
     }
 
-    const guestMode = this.configManager.getConfig('crowi', 'security:restrictGuestMode');
+    const guestMode = configManager.getConfig('crowi', 'security:restrictGuestMode');
 
     // 'Readonly' => returns true (allow access to guests)
     // 'Deny', null, undefined, '', ... everything else => returns false (requires login)
@@ -65,23 +74,6 @@ class AclService {
       : this.labels.SECURITY_RESTRICT_GUEST_MODE_DENY;
   }
 
-  getRestrictGuestModeLabels() {
-    const labels = {};
-    labels[this.labels.SECURITY_RESTRICT_GUEST_MODE_DENY] = 'security_settings.guest_mode.deny';
-    labels[this.labels.SECURITY_RESTRICT_GUEST_MODE_READONLY] = 'security_settings.guest_mode.readonly';
-
-    return labels;
-  }
-
-  getRegistrationModeLabels() {
-    const labels = {};
-    labels[this.labels.SECURITY_REGISTRATION_MODE_OPEN] = 'security_settings.registration_mode.open';
-    labels[this.labels.SECURITY_REGISTRATION_MODE_RESTRICTED] = 'security_settings.registration_mode.restricted';
-    labels[this.labels.SECURITY_REGISTRATION_MODE_CLOSED] = 'security_settings.registration_mode.closed';
-
-    return labels;
-  }
-
 }
 
-module.exports = AclService;
+export const aclService = new AclServiceImpl();

+ 9 - 5
apps/app/src/stores/modal.tsx

@@ -627,13 +627,17 @@ export const useBookmarkFolderDeleteModal = (status?: DeleteBookmarkFolderModalS
 /*
  * TemplateModal
  */
-type TemplateModalStatus = {
+
+type TemplateSelectedCallback = (templateText: string) => void;
+type TemplateModalOptions = {
+  onSubmit?: TemplateSelectedCallback,
+}
+type TemplateModalStatus = TemplateModalOptions & {
   isOpened: boolean,
-  onSubmit?: (templateText: string) => void
 }
 
 type TemplateModalUtils = {
-  open(onSubmit: (templateText: string) => void): void,
+  open(opts: TemplateModalOptions): void,
   close(): void,
 }
 
@@ -643,8 +647,8 @@ export const useTemplateModal = (): SWRResponse<TemplateModalStatus, Error> & Te
   const swrResponse = useStaticSWR<TemplateModalStatus, Error>('templateModal', undefined, { fallbackData: initialStatus });
 
   return Object.assign(swrResponse, {
-    open: (onSubmit: (templateText: string) => void) => {
-      swrResponse.mutate({ isOpened: true, onSubmit });
+    open: (opts: TemplateModalOptions) => {
+      swrResponse.mutate({ isOpened: true, onSubmit: opts.onSubmit });
     },
     close: () => {
       swrResponse.mutate({ isOpened: false });

+ 100 - 20
apps/app/src/stores/template.tsx

@@ -7,43 +7,123 @@ const presetTemplates: ITemplate[] = [
   // preset 1
   {
     id: '__preset1__',
-    name: '[Preset] WESEEK Inner Wiki Style',
-    markdown: `# 関連ページ
+    name: '日報',
+    markdown: `# {{yyyy}}/{{MM}}/{{dd}} 日報
+
+## 今日の目標
+- 目標1
+    - 〇〇の完了
+- 目標2
+    - 〇〇を〇件達成
+
+
+## 内容
+- 10:00 ~ 10:20 今日のタスク確認
+- 10:20 ~ 11:00 全体会議
+
 
-$lsx()
+## 進捗
+- 目標1
+    - 完了
+- 目標2
+    - 〇〇件達成
 
-# `,
+
+## メモ
+- 改善できることの振り返り
+
+
+## 翌営業日の目標
+- 目標1
+    - 〇〇の完了
+- 目標2
+    - 〇〇を〇件達成
+`,
   },
 
   // preset 2
   {
     id: '__preset2__',
-    name: '[Preset] Qiita Style',
-    markdown: `# <会議体名>
+    name: '議事録',
+    markdown: `# {{{title}}}{{^title}}<会議名>{{/title}}
+
 ## 日時
-yyyy/mm/dd hh:mm〜hh:mm
+{{yyyy}}/{{MM}}/{{dd}} {{HH}}:{{mm}}〜hh:mm
 
-## 場所
 
-## 出席
+## 参加
 -
 
 ## 議題
-1. [議題1](#link)
+1.
 2.
-3.
 
-## 議事内容
-### <a name="link"></a>議題1
 
-## 決定事項
-- 決定事項1
+## 1.
+### 内容
+
+
+### 決定事項
+
+
+### Next Action
+
+
+## 2.
+### 内容
+
+
+### 決定事項
+
+
+### Next Action
+
 
-## アクション事項
-- [ ] アクション
+## 次回会議
+- 会議内容
+- 会議時間
+    - {{yyyy}}/{{MM}}/dd
+`,
+  },
+
+  // preset 3
+  {
+    id: '__preset3__',
+    name: '企画書',
+    markdown: `# {{{title}}}{{^title}}<企画タイトル>{{/title}}
+
+## 目的
+
+
+## 現状の課題
+
+
+## 概要
+#### 企画の内容
+
+#### スケジュール
+
+
+## 効果
+#### メリット
+
+#### 数値目標
+
+
+## 参考資料
+
+`,
+  },
+
+  // preset 4
+  {
+    id: '__preset4__',
+    name: '関連ページの一覧表示',
+    markdown: `# 関連ページ
 
-## 次回
-yyyy/mm/dd (予定、時間は追って連絡)`,
+## 子ページ一覧
+$lsx(depth=1)
+`,
   },
 ];
 
@@ -52,7 +132,7 @@ export const useTemplates = (): SWRResponse<ITemplate[], Error> => {
     'templates',
     () => [
       ...presetTemplates,
-      ...Object.values(getGrowiFacade().customTemplates ?? {}),
+      ...Object.values<ITemplate>(getGrowiFacade().customTemplates ?? {}),
     ],
     {
       fallbackData: presetTemplates,

+ 5 - 0
apps/app/test-with-vite/.eslintrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  extends: [
+    'plugin:vitest/recommended',
+  ],
+};

+ 26 - 0
apps/app/test-with-vite/setup/mongoms.ts

@@ -0,0 +1,26 @@
+import { MongoMemoryServer } from 'mongodb-memory-server';
+import mongoose from 'mongoose';
+
+import { mongoOptions } from '~/server/util/mongoose-utils';
+
+
+beforeAll(async() => {
+  // set debug flag
+  process.env.MONGOMS_DEBUG = process.env.VITE_MONGOMS_DEBUG;
+
+  // set version
+  const mongoServer = await MongoMemoryServer.create({
+    instance: {
+      dbName: 'growi_test',
+    },
+    binary: {
+      version: process.env.VITE_MONGOMS_VERSION,
+      downloadDir: 'node_modules/.cache/mongodb-binaries',
+    },
+  });
+  await mongoose.connect(mongoServer.getUri(), mongoOptions);
+});
+
+afterAll(async() => {
+  await mongoose.disconnect();
+});

+ 11 - 0
apps/app/test-with-vite/tsconfig.json

@@ -0,0 +1,11 @@
+{
+  "$schema": "http://json.schemastore.org/tsconfig",
+  "extends": "../../../tsconfig.base.json",
+  "compilerOptions": {
+    "baseUrl": "../",
+    "paths": {
+      "~/*": ["./src/*"],
+      "^/*": ["./*"],
+    }
+  }
+}

+ 0 - 19
apps/app/test/integration/models/config.test.js

@@ -1,19 +0,0 @@
-const { getInstance } = require('../setup-crowi');
-
-describe('Config model test', () => {
-  // eslint-disable-next-line no-unused-vars
-  let crowi;
-
-  beforeAll(async() => {
-    crowi = await getInstance();
-  });
-
-  describe('.CONSTANTS', () => {
-    test('AclService has constants', async() => {
-      expect(crowi.aclService.labels.SECURITY_REGISTRATION_MODE_OPEN).toBe('Open');
-      expect(crowi.aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED).toBe('Restricted');
-      expect(crowi.aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED).toBe('Closed');
-    });
-  });
-
-});

+ 1 - 1
apps/app/tsconfig.json

@@ -21,7 +21,7 @@
   "include": [
     "next-env.d.ts",
     "config",
-    "src"
+    "src",
   ],
   "ts-node": {
     "transpileOnly": true,

+ 22 - 0
apps/app/vitest.config.integ.ts

@@ -0,0 +1,22 @@
+import { defineConfig, mergeConfig } from 'vitest/config';
+
+import configShared from './vitest.config';
+
+export default mergeConfig(
+  configShared,
+  defineConfig({
+    test: {
+      include: [
+        '**/*.integ.ts',
+      ],
+      setupFiles: [
+        './test-with-vite/setup/mongoms.ts',
+      ],
+      coverage: {
+        exclude: [
+          '**/*{.,-}integ.ts',
+        ],
+      },
+    },
+  }),
+);

+ 5 - 3
apps/app/vitest.config.ts

@@ -1,13 +1,15 @@
 import tsconfigPaths from 'vite-tsconfig-paths';
-import { defineProject } from 'vitest/config';
+import { defineConfig } from 'vitest/config';
 
-export default defineProject({
+export default defineConfig({
   plugins: [
     tsconfigPaths(),
   ],
   test: {
     environment: 'node',
-    exclude: ['**/test/**'],
+    exclude: [
+      '**/test/**',
+    ],
     clearMocks: true,
     globals: true,
   },

+ 2 - 2
bin/data-migrations/src/migrations/v60x/plantuml.js

@@ -7,7 +7,7 @@ module.exports = [
    * @type {MigrationModule}
    */
   (body) => {
-    const oldDrawioRegExp = /:::\s?drawio\n(.+?)\n:::/g; // drawio old format
-    return body.replace(oldDrawioRegExp, '``` drawio\n$1\n```');
+    const oldPlantUmlRegExp = /@startuml\n([\s\S]*?)\n@enduml/g; // plantUML old format
+    return body.replace(oldPlantUmlRegExp, '``` plantuml\n$1\n```');
   },
 ];

+ 2 - 2
packages/core/vitest.config.ts

@@ -1,7 +1,7 @@
 import tsconfigPaths from 'vite-tsconfig-paths';
-import { defineProject } from 'vitest/config';
+import { defineConfig } from 'vitest/config';
 
-export default defineProject({
+export default defineConfig({
   plugins: [
     tsconfigPaths(),
   ],

+ 2 - 2
packages/slack/vitest.config.ts

@@ -1,7 +1,7 @@
 import tsconfigPaths from 'vite-tsconfig-paths';
-import { defineProject } from 'vitest/config';
+import { defineConfig } from 'vitest/config';
 
-export default defineProject({
+export default defineConfig({
   plugins: [
     tsconfigPaths(),
   ],

+ 76 - 4
yarn.lock

@@ -4802,6 +4802,13 @@ async-each@^1.0.0:
   resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
   integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
 
+async-mutex@^0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df"
+  integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==
+  dependencies:
+    tslib "^2.3.1"
+
 async-retry@^1.3.1, async-retry@^1.3.3:
   version "1.3.3"
   resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280"
@@ -5561,6 +5568,11 @@ camelcase@^6.2.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
   integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
 
+camelcase@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+  integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
 can-use-dom@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/can-use-dom/-/can-use-dom-0.1.0.tgz#22cc4a34a0abc43950f42c6411024a3f6366b45a"
@@ -8463,7 +8475,7 @@ finalhandler@~1.1.2:
     statuses "~1.5.0"
     unpipe "~1.0.0"
 
-find-cache-dir@^3.3.1:
+find-cache-dir@^3.3.1, find-cache-dir@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b"
   integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==
@@ -8846,6 +8858,11 @@ get-pkg-repo@^4.0.0:
     meow "^7.0.0"
     through2 "^2.0.0"
 
+get-port@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.1.1.tgz#0469ed07563479de6efb986baf053dcd7d4e3193"
+  integrity sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==
+
 get-stdin@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@@ -9698,6 +9715,14 @@ https-proxy-agent@^5.0.0:
     agent-base "6"
     debug "4"
 
+https-proxy-agent@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+  integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
+  dependencies:
+    agent-base "6"
+    debug "4"
+
 human-signals@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@@ -11819,6 +11844,11 @@ md5-file@4.0.0:
   resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-4.0.0.tgz#f3f7ba1e2dd1144d5bf1de698d0e5f44a4409584"
   integrity sha512-UC0qFwyAjn4YdPpKaDNw6gNxRf7Mcx7jC1UGCY4boCzgvU2Aoc1mOGzTtrjjLKhM5ivsnhoKpQVxKPp+1j1qwg==
 
+md5-file@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-5.0.0.tgz#e519f631feca9c39e7f9ea1780b63c4745012e20"
+  integrity sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==
+
 md5-hex@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c"
@@ -12688,7 +12718,35 @@ mongodb-connection-string-url@^2.5.4:
     "@types/whatwg-url" "^8.2.1"
     whatwg-url "^11.0.0"
 
-mongodb@4.16.0:
+mongodb-memory-server-core@8.12.2:
+  version "8.12.2"
+  resolved "https://registry.yarnpkg.com/mongodb-memory-server-core/-/mongodb-memory-server-core-8.12.2.tgz#730cc4c01c457657e16e4e4a8a642b9ff8382fea"
+  integrity sha512-bls+lroejnhbZTOm5KHtxtf9PK6xASEAsCvZCPoXrNk1f10p0jDw7Xb4GUqVi0ZuVmuLZBNgmzYeHmb3WUgvLg==
+  dependencies:
+    async-mutex "^0.3.2"
+    camelcase "^6.3.0"
+    debug "^4.3.4"
+    find-cache-dir "^3.3.2"
+    get-port "^5.1.1"
+    https-proxy-agent "^5.0.1"
+    md5-file "^5.0.0"
+    mongodb "^4.13.0"
+    new-find-package-json "^2.0.0"
+    semver "^7.3.8"
+    tar-stream "^2.1.4"
+    tslib "^2.5.0"
+    uuid "^9.0.0"
+    yauzl "^2.10.0"
+
+mongodb-memory-server@^8.12.2:
+  version "8.12.2"
+  resolved "https://registry.yarnpkg.com/mongodb-memory-server/-/mongodb-memory-server-8.12.2.tgz#09d7f7fb7877d309c486f3e703390633bfd137f4"
+  integrity sha512-WM3uJnKWqhJxu3LlHfvRXRrhc+kiEGdGDHMrAG0N1E2fWbRlvSnUKau7Jdcf7cIA5HlRC/K8uVe0DCym45KfAA==
+  dependencies:
+    mongodb-memory-server-core "8.12.2"
+    tslib "^2.5.0"
+
+mongodb@4.16.0, mongodb@^4.13.0:
   version "4.16.0"
   resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.16.0.tgz#8b0043de7b577c6a7e0ce44a2ca7315b9c0a7927"
   integrity sha512-0EB113Fsucaq1wsY0dOhi1fmZOwFtLOtteQkiqOXGklvWMnSH3g2QS53f0KTP+/6qOkuoXE2JksubSZNmxeI+g==
@@ -12837,7 +12895,7 @@ multer@^1.4.2, multer@~1.4.0:
     type-is "^1.6.4"
     xtend "^4.0.0"
 
-mustache@4.2.0:
+mustache@4.2.0, mustache@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
   integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
@@ -12950,6 +13008,13 @@ nested-error-stacks@^2.0.0:
   resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61"
   integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==
 
+new-find-package-json@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/new-find-package-json/-/new-find-package-json-2.0.0.tgz#96553638781db35061f351e8ccb4d07126b6407d"
+  integrity sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==
+  dependencies:
+    debug "^4.3.4"
+
 next-i18next@^13.2.1:
   version "13.2.2"
   resolved "https://registry.yarnpkg.com/next-i18next/-/next-i18next-13.2.2.tgz#9609546fab1d1d5f9b227e86c5ca23d0cbbbddb4"
@@ -15531,6 +15596,13 @@ semver@>=7.3.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semv
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.3.8:
+  version "7.5.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
+  integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
+  dependencies:
+    lru-cache "^6.0.0"
+
 send@0.17.1:
   version "0.17.1"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
@@ -16675,7 +16747,7 @@ tape@^5.0.0:
     string.prototype.trim "^1.2.5"
     through "^2.3.8"
 
-tar-stream@^2.2.0:
+tar-stream@^2.1.4, tar-stream@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287"
   integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==