Yuki Takei 2 лет назад
Родитель
Сommit
33526a9fe4

+ 5 - 10
apps/app/src/features/growi-plugin/server/services/growi-plugin.ts

@@ -3,7 +3,7 @@ import path from 'path';
 
 
 import { GrowiPluginType, type GrowiThemeMetadata, type ViteManifest } from '@growi/core';
 import { GrowiPluginType, type GrowiThemeMetadata, type ViteManifest } from '@growi/core';
 import type { GrowiPluginPackageData } from '@growi/pluginkit';
 import type { GrowiPluginPackageData } from '@growi/pluginkit';
-import { importPackageJson, validatePackageJson } from '@growi/pluginkit/dist/v4/server';
+import { importPackageJson, validateGrowiDirective } from '@growi/pluginkit/dist/v4/server';
 // eslint-disable-next-line no-restricted-imports
 // eslint-disable-next-line no-restricted-imports
 import axios from 'axios';
 import axios from 'axios';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
@@ -224,19 +224,14 @@ export class GrowiPluginService implements IGrowiPluginService {
     const packageRootPath = opts?.packageRootPath ?? path.resolve(pluginStoringPath, ghOrganizationName, ghReposName);
     const packageRootPath = opts?.packageRootPath ?? path.resolve(pluginStoringPath, ghOrganizationName, ghReposName);
 
 
     // validate
     // validate
-    await validatePackageJson(packageRootPath);
+    const data = await validateGrowiDirective(packageRootPath);
 
 
-    const packageData = importPackageJson(packageRootPath);
+    const packageData = opts?.parentPackageData ?? importPackageJson(packageRootPath);
 
 
-    const { growiPlugin } = packageData;
+    const { growiPlugin } = data;
     const {
     const {
       name: packageName, description: packageDesc, author: packageAuthor,
       name: packageName, description: packageDesc, author: packageAuthor,
-    } = opts?.parentPackageData ?? packageData;
-
-
-    if (growiPlugin == null) {
-      throw new Error('This package does not include \'growiPlugin\' section.');
-    }
+    } = packageData;
 
 
     // detect sub plugins for monorepo
     // detect sub plugins for monorepo
     if (growiPlugin.isMonorepo && growiPlugin.packages != null) {
     if (growiPlugin.isMonorepo && growiPlugin.packages != null) {

+ 1 - 1
packages/pluginkit/src/v4/server/utils/package-json/import.spec.ts → packages/pluginkit/src/v4/server/utils/common/import-package-json.spec.ts

@@ -1,6 +1,6 @@
 import path from 'path';
 import path from 'path';
 
 
-import { importPackageJson } from './import';
+import { importPackageJson } from './import-package-json';
 
 
 it('importPackageJson() returns an object', async() => {
 it('importPackageJson() returns an object', async() => {
   // when
   // when

+ 0 - 0
packages/pluginkit/src/v4/server/utils/package-json/import.ts → packages/pluginkit/src/v4/server/utils/common/import-package-json.ts


+ 2 - 0
packages/pluginkit/src/v4/server/utils/common/index.ts

@@ -0,0 +1,2 @@
+export * from './import-package-json';
+export * from './validate-growi-plugin-directive';

+ 10 - 10
packages/pluginkit/src/v4/server/utils/package-json/validate.spec.ts → packages/pluginkit/src/v4/server/utils/common/validate-growi-plugin-directive.spec.ts

@@ -3,7 +3,7 @@ import { GrowiPluginType } from '@growi/core/dist/consts';
 import examplePkg from '../../../../../test/fixtures/example-package/template1/package.json';
 import examplePkg from '../../../../../test/fixtures/example-package/template1/package.json';
 
 
 
 
-import { validatePackageJson } from './validate';
+import { validateGrowiDirective } from './validate-growi-plugin-directive';
 
 
 const mocks = vi.hoisted(() => {
 const mocks = vi.hoisted(() => {
   return {
   return {
@@ -11,18 +11,18 @@ const mocks = vi.hoisted(() => {
   };
   };
 });
 });
 
 
-vi.mock('./import', () => {
+vi.mock('./import-package-json', () => {
   return { importPackageJson: mocks.importPackageJsonMock };
   return { importPackageJson: mocks.importPackageJsonMock };
 });
 });
 
 
-describe('validatePackageJson()', () => {
+describe('validateGrowiDirective()', () => {
 
 
   it('returns a data object', async() => {
   it('returns a data object', async() => {
     // setup
     // setup
     mocks.importPackageJsonMock.mockReturnValue(examplePkg);
     mocks.importPackageJsonMock.mockReturnValue(examplePkg);
 
 
     // when
     // when
-    const data = validatePackageJson('package.json');
+    const data = validateGrowiDirective('package.json');
 
 
     // then
     // then
     expect(data).not.toBeNull();
     expect(data).not.toBeNull();
@@ -33,7 +33,7 @@ describe('validatePackageJson()', () => {
     mocks.importPackageJsonMock.mockReturnValue(examplePkg);
     mocks.importPackageJsonMock.mockReturnValue(examplePkg);
 
 
     // when
     // when
-    const data = validatePackageJson('package.json', GrowiPluginType.Template);
+    const data = validateGrowiDirective('package.json', GrowiPluginType.Template);
 
 
     // then
     // then
     expect(data).not.toBeNull();
     expect(data).not.toBeNull();
@@ -46,7 +46,7 @@ describe('validatePackageJson()', () => {
       mocks.importPackageJsonMock.mockReturnValue({});
       mocks.importPackageJsonMock.mockReturnValue({});
 
 
       // when
       // when
-      const caller = () => { validatePackageJson('package.json') };
+      const caller = () => { validateGrowiDirective('package.json') };
 
 
       // then
       // then
       expect(caller).toThrow("The package.json does not have 'growiPlugin' directive.");
       expect(caller).toThrow("The package.json does not have 'growiPlugin' directive.");
@@ -61,7 +61,7 @@ describe('validatePackageJson()', () => {
       });
       });
 
 
       // when
       // when
-      const caller = () => { validatePackageJson('package.json') };
+      const caller = () => { validateGrowiDirective('package.json') };
 
 
       // then
       // then
       expect(caller).toThrow("The growiPlugin directive must have a valid 'schemaVersion' directive.");
       expect(caller).toThrow("The growiPlugin directive must have a valid 'schemaVersion' directive.");
@@ -76,7 +76,7 @@ describe('validatePackageJson()', () => {
       });
       });
 
 
       // when
       // when
-      const caller = () => { validatePackageJson('package.json') };
+      const caller = () => { validateGrowiDirective('package.json') };
 
 
       // then
       // then
       expect(caller).toThrow("The growiPlugin directive must have a valid 'schemaVersion' directive.");
       expect(caller).toThrow("The growiPlugin directive must have a valid 'schemaVersion' directive.");
@@ -91,7 +91,7 @@ describe('validatePackageJson()', () => {
       });
       });
 
 
       // when
       // when
-      const caller = () => { validatePackageJson('package.json') };
+      const caller = () => { validateGrowiDirective('package.json') };
 
 
       // then
       // then
       expect(caller).toThrow("The growiPlugin directive does not have 'types' directive.");
       expect(caller).toThrow("The growiPlugin directive does not have 'types' directive.");
@@ -107,7 +107,7 @@ describe('validatePackageJson()', () => {
       });
       });
 
 
       // when
       // when
-      const caller = () => { validatePackageJson('package.json', GrowiPluginType.Script) };
+      const caller = () => { validateGrowiDirective('package.json', GrowiPluginType.Script) };
 
 
       // then
       // then
       expect(caller).toThrow("The growiPlugin directive does not have expected plugin type in 'types' directive.");
       expect(caller).toThrow("The growiPlugin directive does not have expected plugin type in 'types' directive.");

+ 2 - 2
packages/pluginkit/src/v4/server/utils/package-json/validate.ts → packages/pluginkit/src/v4/server/utils/common/validate-growi-plugin-directive.ts

@@ -2,10 +2,10 @@ import { GrowiPluginType } from '@growi/core/dist/consts';
 
 
 import { type GrowiPluginValidationData, GrowiPluginValidationError } from '../../../../model';
 import { type GrowiPluginValidationData, GrowiPluginValidationError } from '../../../../model';
 
 
-import { importPackageJson } from './import';
+import { importPackageJson } from './import-package-json';
 
 
 
 
-export const validatePackageJson = (projectDirRoot: string, expectedPluginType?: GrowiPluginType): GrowiPluginValidationData => {
+export const validateGrowiDirective = (projectDirRoot: string, expectedPluginType?: GrowiPluginType): GrowiPluginValidationData => {
   const pkg = importPackageJson(projectDirRoot);
   const pkg = importPackageJson(projectDirRoot);
 
 
   const { growiPlugin } = pkg;
   const { growiPlugin } = pkg;

+ 1 - 1
packages/pluginkit/src/v4/server/utils/index.ts

@@ -1,2 +1,2 @@
-export * from './package-json';
+export * from './common';
 export * from './template';
 export * from './template';

+ 0 - 2
packages/pluginkit/src/v4/server/utils/package-json/index.ts

@@ -1,2 +0,0 @@
-export * from './import';
-export * from './validate';

+ 0 - 204
packages/pluginkit/src/v4/server/utils/template.ts

@@ -1,204 +0,0 @@
-import fs, { readFileSync } from 'fs';
-import path from 'path';
-import { promisify } from 'util';
-
-import { GrowiPluginType } from '@growi/core/dist/consts';
-
-import type { GrowiPluginValidationData, GrowiTemplatePluginValidationData } from '../../../model';
-import { GrowiPluginValidationError } from '../../../model';
-import { isTemplateStatusValid, type TemplateStatus, type TemplateSummary } from '../../interfaces';
-
-import { validatePackageJson } from './package-json';
-
-
-const statAsync = promisify(fs.stat);
-
-
-/**
- * An utility for template plugin which wrap 'validatePackageJson' of './package-json.ts' module
- * @param projectDirRoot
- */
-export const validateTemplatePluginPackageJson = (projectDirRoot: string): GrowiTemplatePluginValidationData => {
-  const data = validatePackageJson(projectDirRoot, GrowiPluginType.Template);
-
-  const { growiPlugin } = data;
-
-  // check supporting locales
-  const supportingLocales: string[] | undefined = growiPlugin.locales;
-  if (supportingLocales == null || supportingLocales.length === 0) {
-    throw new GrowiPluginValidationError<GrowiPluginValidationData & { supportingLocales?: string[] }>(
-      "Template plugin must have 'supportingLocales' and that must have one or more locales",
-      {
-        ...data,
-        supportingLocales,
-      },
-    );
-  }
-
-  return {
-    ...data,
-    supportingLocales,
-  };
-};
-
-
-type TemplateDirStatus = {
-  isTemplateExists: boolean,
-  meta?: { [key: string]: string },
-}
-
-async function getStats(tplDir: string): Promise<TemplateDirStatus> {
-  const markdownPath = path.resolve(tplDir, 'template.md');
-  const statForMarkdown = await statAsync(markdownPath);
-  const isTemplateExists = statForMarkdown.isFile();
-
-  const metaDataPath = path.resolve(tplDir, 'meta.json');
-  const statForMetaDataFile = await statAsync(metaDataPath);
-  const isMetaDataFileExists = statForMetaDataFile.isFile();
-
-  const result: TemplateDirStatus = {
-    isTemplateExists,
-    meta: isMetaDataFileExists ? JSON.parse(readFileSync(metaDataPath, 'utf-8')) : undefined,
-  };
-
-  return result;
-}
-
-
-export const scanTemplateStatus = async(
-    projectDirRoot: string,
-    templateId: string,
-    data: GrowiTemplatePluginValidationData,
-    opts?: {
-      pluginId?: string,
-    },
-): Promise<TemplateStatus[]> => {
-  const status: TemplateStatus[] = [];
-
-  const tplRootDirPath = path.resolve(projectDirRoot, 'dist', templateId);
-
-  let isDefaultPushed = false;
-  for await (const locale of data.supportingLocales) {
-    const tplDir = path.resolve(tplRootDirPath, locale);
-
-    try {
-      const stats = await getStats(tplDir);
-      const {
-        isTemplateExists, meta,
-      } = stats;
-
-      if (!isTemplateExists) throw new Error("'template.md does not exist.");
-      if (meta == null) throw new Error("'meta.md does not exist.");
-      if (meta?.title == null) throw new Error("'meta.md does not contain the title.");
-
-      const isDefault = !isDefaultPushed;
-      status.push({
-        pluginId: opts?.pluginId,
-        id: templateId,
-        locale,
-        isValid: true,
-        isDefault,
-        title: meta.title,
-        desc: meta.desc,
-      });
-      isDefaultPushed = true;
-    }
-    catch (err) {
-      status.push({
-        pluginId: opts?.pluginId,
-        id: templateId,
-        locale,
-        isValid: false,
-        invalidReason: err.message,
-      });
-    }
-  }
-
-  // eslint-disable-next-line no-console
-  console.debug(`Template directory (${projectDirRoot}) has scanned`, { status });
-
-  return status;
-};
-
-export const scanAllTemplateStatus = async(
-    projectDirRoot: string,
-    opts?: {
-      data?: GrowiTemplatePluginValidationData,
-      pluginId?: string,
-      returnsInvalidTemplates?: boolean,
-    },
-): Promise<TemplateSummary[]> => {
-
-  const data = opts?.data ?? validateTemplatePluginPackageJson(projectDirRoot);
-
-  const summaries: TemplateSummary[] = [];
-
-  const distDirPath = path.resolve(projectDirRoot, 'dist');
-  const distDirFiles = fs.readdirSync(distDirPath);
-
-  for await (const templateId of distDirFiles) {
-    const status = (await scanTemplateStatus(projectDirRoot, templateId, data, { pluginId: opts?.pluginId }))
-      // omit invalid templates if `returnsInvalidTemplates` is true
-      .filter(s => (opts?.returnsInvalidTemplates ? true : s.isValid));
-
-    // determine default locale
-    const defaultTemplateStatus = status.find(s => 'isDefault' in s && s.isDefault);
-
-    if (defaultTemplateStatus == null || !isTemplateStatusValid(defaultTemplateStatus)) {
-      continue;
-    }
-
-    summaries.push({
-      // for the 'default' key
-      default: defaultTemplateStatus,
-      // for each locale keys
-      ...Object.fromEntries(status.map(templateStatus => [templateStatus.locale, templateStatus])),
-    });
-  }
-
-  return summaries;
-};
-
-export const validateTemplatePlugin = async(projectDirRoot: string): Promise<boolean> => {
-  const data = validateTemplatePluginPackageJson(projectDirRoot);
-
-  const results = await scanAllTemplateStatus(projectDirRoot, { data, returnsInvalidTemplates: true });
-
-  if (Object.keys(results).length === 0) {
-    throw new Error('This plugin does not have any templates');
-  }
-
-  // construct map
-  // key: id
-  // value: isValid properties
-  const idValidMap: { [id: string]: boolean[] } = {};
-  results.forEach((summary) => {
-    idValidMap[summary.default.id] = Object.values(summary).map(s => s?.isValid ?? false);
-  });
-
-  for (const [id, validMap] of Object.entries(idValidMap)) {
-    // warn
-    if (!validMap.every(bool => bool)) {
-      // eslint-disable-next-line no-console
-      console.warn(`[WARN] Template '${id}' has some locales that status is invalid`);
-    }
-
-    // This means the template directory does not have any valid template
-    if (!validMap.some(bool => bool)) {
-      return false;
-    }
-  }
-
-  return true;
-};
-
-export const getMarkdown = async(projectDirRoot: string, templateId: string, locale: string): Promise<string> => {
-  const tplDir = path.resolve(projectDirRoot, 'dist', templateId, locale);
-
-  const { isTemplateExists } = await getStats(tplDir);
-
-  if (!isTemplateExists) throw new Error("'template.md does not exist.");
-
-  const markdownPath = path.resolve(tplDir, 'template.md');
-  return fs.readFileSync(markdownPath, { encoding: 'utf-8' });
-};

+ 16 - 0
packages/pluginkit/src/v4/server/utils/template/get-markdown.ts

@@ -0,0 +1,16 @@
+import fs from 'fs';
+import path from 'path';
+
+import { getStatus } from './get-status';
+
+
+export const getMarkdown = async(projectDirRoot: string, templateId: string, locale: string): Promise<string> => {
+  const tplDir = path.resolve(projectDirRoot, 'dist', templateId, locale);
+
+  const { isTemplateExists } = await getStatus(tplDir);
+
+  if (!isTemplateExists) throw new Error("'template.md does not exist.");
+
+  const markdownPath = path.resolve(tplDir, 'template.md');
+  return fs.readFileSync(markdownPath, { encoding: 'utf-8' });
+};

+ 29 - 0
packages/pluginkit/src/v4/server/utils/template/get-status.ts

@@ -0,0 +1,29 @@
+import fs, { readFileSync } from 'fs';
+import path from 'path';
+import { promisify } from 'util';
+
+
+const statAsync = promisify(fs.stat);
+
+
+type TemplateDirStatus = {
+  isTemplateExists: boolean,
+  meta?: { [key: string]: string },
+}
+
+export async function getStatus(tplDir: string): Promise<TemplateDirStatus> {
+  const markdownPath = path.resolve(tplDir, 'template.md');
+  const statForMarkdown = await statAsync(markdownPath);
+  const isTemplateExists = statForMarkdown.isFile();
+
+  const metaDataPath = path.resolve(tplDir, 'meta.json');
+  const statForMetaDataFile = await statAsync(metaDataPath);
+  const isMetaDataFileExists = statForMetaDataFile.isFile();
+
+  const result: TemplateDirStatus = {
+    isTemplateExists,
+    meta: isMetaDataFileExists ? JSON.parse(readFileSync(metaDataPath, 'utf-8')) : undefined,
+  };
+
+  return result;
+}

+ 4 - 0
packages/pluginkit/src/v4/server/utils/template/index.ts

@@ -0,0 +1,4 @@
+export * from './get-markdown';
+export * from './scan';
+export * from './validate-all-locales';
+export * from './validate-growi-plugin-directive';

+ 103 - 0
packages/pluginkit/src/v4/server/utils/template/scan.ts

@@ -0,0 +1,103 @@
+import fs from 'fs';
+import path from 'path';
+
+import type { GrowiTemplatePluginValidationData } from '../../../../model';
+import { isTemplateStatusValid, type TemplateStatus, type TemplateSummary } from '../../../interfaces';
+
+import { getStatus } from './get-status';
+import { validateTemplatePluginGrowiDirective } from './validate-growi-plugin-directive';
+
+
+export const scanTemplate = async(
+    projectDirRoot: string,
+    templateId: string,
+    data: GrowiTemplatePluginValidationData,
+    opts?: {
+      pluginId?: string,
+    },
+): Promise<TemplateStatus[]> => {
+  const status: TemplateStatus[] = [];
+
+  const tplRootDirPath = path.resolve(projectDirRoot, 'dist', templateId);
+
+  let isDefaultPushed = false;
+  for await (const locale of data.supportingLocales) {
+    const tplDir = path.resolve(tplRootDirPath, locale);
+
+    try {
+      const stats = await getStatus(tplDir);
+      const {
+        isTemplateExists, meta,
+      } = stats;
+
+      if (!isTemplateExists) throw new Error("'template.md does not exist.");
+      if (meta == null) throw new Error("'meta.md does not exist.");
+      if (meta?.title == null) throw new Error("'meta.md does not contain the title.");
+
+      const isDefault = !isDefaultPushed;
+      status.push({
+        pluginId: opts?.pluginId,
+        id: templateId,
+        locale,
+        isValid: true,
+        isDefault,
+        title: meta.title,
+        desc: meta.desc,
+      });
+      isDefaultPushed = true;
+    }
+    catch (err) {
+      status.push({
+        pluginId: opts?.pluginId,
+        id: templateId,
+        locale,
+        isValid: false,
+        invalidReason: err.message,
+      });
+    }
+  }
+
+  // eslint-disable-next-line no-console
+  console.debug(`Template directory (${projectDirRoot}) has scanned`, { status });
+
+  return status;
+};
+
+export const scanAllTemplates = async(
+    projectDirRoot: string,
+    opts?: {
+      data?: GrowiTemplatePluginValidationData,
+      pluginId?: string,
+      returnsInvalidTemplates?: boolean,
+    },
+): Promise<TemplateSummary[]> => {
+
+  const data = opts?.data ?? validateTemplatePluginGrowiDirective(projectDirRoot);
+
+  const summaries: TemplateSummary[] = [];
+
+  const distDirPath = path.resolve(projectDirRoot, 'dist');
+  const distDirFiles = fs.readdirSync(distDirPath);
+
+  for await (const templateId of distDirFiles) {
+    const status = (await scanTemplate(projectDirRoot, templateId, data, { pluginId: opts?.pluginId }))
+      // omit invalid templates if `returnsInvalidTemplates` is true
+      .filter(s => (opts?.returnsInvalidTemplates ? true : s.isValid));
+
+    // determine default locale
+    const defaultTemplateStatus = status.find(s => 'isDefault' in s && s.isDefault);
+
+    if (defaultTemplateStatus == null || !isTemplateStatusValid(defaultTemplateStatus)) {
+      continue;
+    }
+
+    summaries.push({
+      // for the 'default' key
+      default: defaultTemplateStatus,
+      // for each locale keys
+      ...Object.fromEntries(status.map(templateStatus => [templateStatus.locale, templateStatus])),
+    });
+  }
+
+  return summaries;
+};

+ 36 - 0
packages/pluginkit/src/v4/server/utils/template/validate-all-locales.ts

@@ -0,0 +1,36 @@
+import { scanAllTemplates } from './scan';
+import { validateTemplatePluginGrowiDirective } from './validate-growi-plugin-directive';
+
+
+export const validateAllTemplateLocales = async(projectDirRoot: string): Promise<boolean> => {
+  const data = validateTemplatePluginGrowiDirective(projectDirRoot);
+
+  const results = await scanAllTemplates(projectDirRoot, { data, returnsInvalidTemplates: true });
+
+  if (Object.keys(results).length === 0) {
+    throw new Error('This plugin does not have any templates');
+  }
+
+  // construct map
+  // key: id
+  // value: isValid properties
+  const idValidMap: { [id: string]: boolean[] } = {};
+  results.forEach((summary) => {
+    idValidMap[summary.default.id] = Object.values(summary).map(s => s?.isValid ?? false);
+  });
+
+  for (const [id, validMap] of Object.entries(idValidMap)) {
+    // warn
+    if (!validMap.every(bool => bool)) {
+      // eslint-disable-next-line no-console
+      console.warn(`[WARN] Template '${id}' has some locales that status is invalid`);
+    }
+
+    // This means the template directory does not have any valid template
+    if (!validMap.some(bool => bool)) {
+      return false;
+    }
+  }
+
+  return true;
+};

+ 33 - 0
packages/pluginkit/src/v4/server/utils/template/validate-growi-plugin-directive.ts

@@ -0,0 +1,33 @@
+import { GrowiPluginType } from '@growi/core/dist/consts';
+
+import type { GrowiPluginValidationData, GrowiTemplatePluginValidationData } from '../../../../model';
+import { GrowiPluginValidationError } from '../../../../model';
+import { validateGrowiDirective } from '../common';
+
+
+/**
+ * An utility for template plugin which wrap 'validateGrowiDirective' of './common' module
+ * @param projectDirRoot
+ */
+export const validateTemplatePluginGrowiDirective = (projectDirRoot: string): GrowiTemplatePluginValidationData => {
+  const data = validateGrowiDirective(projectDirRoot, GrowiPluginType.Template);
+
+  const { growiPlugin } = data;
+
+  // check supporting locales
+  const supportingLocales: string[] | undefined = growiPlugin.locales;
+  if (supportingLocales == null || supportingLocales.length === 0) {
+    throw new GrowiPluginValidationError<GrowiPluginValidationData & { supportingLocales?: string[] }>(
+      "Template plugin must have 'supportingLocales' and that must have one or more locales",
+      {
+        ...data,
+        supportingLocales,
+      },
+    );
+  }
+
+  return {
+    ...data,
+    supportingLocales,
+  };
+};