|
@@ -1,12 +1,14 @@
|
|
|
-import assert from 'assert';
|
|
|
|
|
import fs from 'fs';
|
|
import fs from 'fs';
|
|
|
import path from 'path';
|
|
import path from 'path';
|
|
|
import { promisify } from 'util';
|
|
import { promisify } from 'util';
|
|
|
|
|
|
|
|
-import { GrowiPluginType } from '~/consts';
|
|
|
|
|
|
|
+import { GrowiPluginType } from '@growi/core/dist/consts';
|
|
|
|
|
+
|
|
|
import type { GrowiPluginValidationData, GrowiTemplatePluginValidationData } from '~/model';
|
|
import type { GrowiPluginValidationData, GrowiTemplatePluginValidationData } from '~/model';
|
|
|
import { GrowiPluginValidationError } from '~/model';
|
|
import { GrowiPluginValidationError } from '~/model';
|
|
|
|
|
|
|
|
|
|
+import { isTemplateStatusValid, type TemplateStatus, type TemplateSummary } from '../../interfaces';
|
|
|
|
|
+
|
|
|
import { importPackageJson, validatePackageJson } from './package-json';
|
|
import { importPackageJson, validatePackageJson } from './package-json';
|
|
|
|
|
|
|
|
|
|
|
|
@@ -18,7 +20,7 @@ const statAsync = promisify(fs.stat);
|
|
|
* @param projectDirRoot
|
|
* @param projectDirRoot
|
|
|
*/
|
|
*/
|
|
|
export const validateTemplatePluginPackageJson = async(projectDirRoot: string): Promise<GrowiTemplatePluginValidationData> => {
|
|
export const validateTemplatePluginPackageJson = async(projectDirRoot: string): Promise<GrowiTemplatePluginValidationData> => {
|
|
|
- const data = await validatePackageJson(projectDirRoot, GrowiPluginType.TEMPLATE);
|
|
|
|
|
|
|
+ const data = await validatePackageJson(projectDirRoot, GrowiPluginType.Template);
|
|
|
|
|
|
|
|
const pkg = await importPackageJson(projectDirRoot);
|
|
const pkg = await importPackageJson(projectDirRoot);
|
|
|
|
|
|
|
@@ -40,18 +42,10 @@ export const validateTemplatePluginPackageJson = async(projectDirRoot: string):
|
|
|
};
|
|
};
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export type TemplateStatus = {
|
|
|
|
|
- id: string,
|
|
|
|
|
- locale: string,
|
|
|
|
|
- isValid: boolean,
|
|
|
|
|
- invalidReason?: string,
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
type TemplateDirStatus = {
|
|
type TemplateDirStatus = {
|
|
|
isTemplateExists: boolean,
|
|
isTemplateExists: boolean,
|
|
|
- isMetaDataFileExists: boolean,
|
|
|
|
|
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
|
- meta?: any,
|
|
|
|
|
|
|
+ meta?: { [key: string]: string },
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function getStats(tplDir: string): Promise<TemplateDirStatus> {
|
|
async function getStats(tplDir: string): Promise<TemplateDirStatus> {
|
|
@@ -65,37 +59,54 @@ async function getStats(tplDir: string): Promise<TemplateDirStatus> {
|
|
|
|
|
|
|
|
const result: TemplateDirStatus = {
|
|
const result: TemplateDirStatus = {
|
|
|
isTemplateExists,
|
|
isTemplateExists,
|
|
|
- isMetaDataFileExists,
|
|
|
|
|
|
|
+ meta: isMetaDataFileExists ? await import(metaDataPath) : undefined,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- if (isMetaDataFileExists) {
|
|
|
|
|
- result.meta = await import(metaDataPath);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-export const scanTemplateStatus = async(projectDirRoot: string, templateId: string, data: GrowiTemplatePluginValidationData): Promise<TemplateStatus[]> => {
|
|
|
|
|
|
|
+
|
|
|
|
|
+export const scanTemplateStatus = async(
|
|
|
|
|
+ projectDirRoot: string,
|
|
|
|
|
+ templateId: string,
|
|
|
|
|
+ data: GrowiTemplatePluginValidationData,
|
|
|
|
|
+ opts?: {
|
|
|
|
|
+ pluginId?: string,
|
|
|
|
|
+ },
|
|
|
|
|
+): Promise<TemplateStatus[]> => {
|
|
|
const status: TemplateStatus[] = [];
|
|
const status: TemplateStatus[] = [];
|
|
|
|
|
|
|
|
const tplRootDirPath = path.resolve(projectDirRoot, 'dist', templateId);
|
|
const tplRootDirPath = path.resolve(projectDirRoot, 'dist', templateId);
|
|
|
|
|
|
|
|
|
|
+ let isDefaultPushed = false;
|
|
|
for await (const locale of data.supportingLocales) {
|
|
for await (const locale of data.supportingLocales) {
|
|
|
const tplDir = path.resolve(tplRootDirPath, locale);
|
|
const tplDir = path.resolve(tplRootDirPath, locale);
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
|
|
+ const stats = await getStats(tplDir);
|
|
|
const {
|
|
const {
|
|
|
- isTemplateExists, isMetaDataFileExists, meta,
|
|
|
|
|
- } = await getStats(tplDir);
|
|
|
|
|
|
|
+ isTemplateExists, meta,
|
|
|
|
|
+ } = stats;
|
|
|
|
|
|
|
|
if (!isTemplateExists) throw new Error("'template.md does not exist.");
|
|
if (!isTemplateExists) throw new Error("'template.md does not exist.");
|
|
|
- if (!isMetaDataFileExists) throw new Error("'meta.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.");
|
|
if (meta?.title == null) throw new Error("'meta.md does not contain the title.");
|
|
|
|
|
|
|
|
- status.push({ id: templateId, locale, isValid: true });
|
|
|
|
|
|
|
+ const isDefault = !isDefaultPushed;
|
|
|
|
|
+ status.push({
|
|
|
|
|
+ pluginId: opts?.pluginId,
|
|
|
|
|
+ id: templateId,
|
|
|
|
|
+ locale,
|
|
|
|
|
+ isValid: true,
|
|
|
|
|
+ isDefault,
|
|
|
|
|
+ title: meta.title,
|
|
|
|
|
+ desc: meta.desc,
|
|
|
|
|
+ });
|
|
|
|
|
+ isDefaultPushed = true;
|
|
|
}
|
|
}
|
|
|
catch (err) {
|
|
catch (err) {
|
|
|
status.push({
|
|
status.push({
|
|
|
|
|
+ pluginId: opts?.pluginId,
|
|
|
id: templateId,
|
|
id: templateId,
|
|
|
locale,
|
|
locale,
|
|
|
isValid: false,
|
|
isValid: false,
|
|
@@ -110,26 +121,51 @@ export const scanTemplateStatus = async(projectDirRoot: string, templateId: stri
|
|
|
return status;
|
|
return status;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-export const scanAllTemplateStatus = async(projectDirRoot: string, data: GrowiTemplatePluginValidationData): Promise<TemplateStatus[]> => {
|
|
|
|
|
- const status: TemplateStatus[] = [];
|
|
|
|
|
|
|
+export const scanAllTemplateStatus = async(
|
|
|
|
|
+ projectDirRoot: string,
|
|
|
|
|
+ opts?: {
|
|
|
|
|
+ data?: GrowiTemplatePluginValidationData,
|
|
|
|
|
+ pluginId?: string,
|
|
|
|
|
+ returnsInvalidTemplates?: boolean,
|
|
|
|
|
+ },
|
|
|
|
|
+): Promise<TemplateSummary[]> => {
|
|
|
|
|
+
|
|
|
|
|
+ const data = opts?.data ?? await validateTemplatePluginPackageJson(projectDirRoot);
|
|
|
|
|
+
|
|
|
|
|
+ const summaries: TemplateSummary[] = [];
|
|
|
|
|
|
|
|
const distDirPath = path.resolve(projectDirRoot, 'dist');
|
|
const distDirPath = path.resolve(projectDirRoot, 'dist');
|
|
|
const distDirFiles = fs.readdirSync(distDirPath);
|
|
const distDirFiles = fs.readdirSync(distDirPath);
|
|
|
|
|
|
|
|
for await (const templateId of distDirFiles) {
|
|
for await (const templateId of distDirFiles) {
|
|
|
- status.push(...await scanTemplateStatus(projectDirRoot, templateId, data));
|
|
|
|
|
|
|
+ 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 status;
|
|
|
|
|
|
|
+ return summaries;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-
|
|
|
|
|
export const validateTemplatePlugin = async(projectDirRoot: string): Promise<boolean> => {
|
|
export const validateTemplatePlugin = async(projectDirRoot: string): Promise<boolean> => {
|
|
|
const data = await validateTemplatePluginPackageJson(projectDirRoot);
|
|
const data = await validateTemplatePluginPackageJson(projectDirRoot);
|
|
|
|
|
|
|
|
- const results = await scanAllTemplateStatus(projectDirRoot, data);
|
|
|
|
|
|
|
+ const results = await scanAllTemplateStatus(projectDirRoot, { data, returnsInvalidTemplates: true });
|
|
|
|
|
|
|
|
- if (results.length === 0) {
|
|
|
|
|
|
|
+ if (Object.keys(results).length === 0) {
|
|
|
throw new Error('This plugin does not have any templates');
|
|
throw new Error('This plugin does not have any templates');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -137,19 +173,15 @@ export const validateTemplatePlugin = async(projectDirRoot: string): Promise<boo
|
|
|
// key: id
|
|
// key: id
|
|
|
// value: isValid properties
|
|
// value: isValid properties
|
|
|
const idValidMap: { [id: string]: boolean[] } = {};
|
|
const idValidMap: { [id: string]: boolean[] } = {};
|
|
|
- results.forEach((status) => {
|
|
|
|
|
- const validMap = idValidMap[status.id] ?? [];
|
|
|
|
|
- validMap.push(status.isValid);
|
|
|
|
|
- idValidMap[status.id] = validMap;
|
|
|
|
|
|
|
+ results.forEach((summary) => {
|
|
|
|
|
+ idValidMap[summary.default.id] = Object.values(summary).map(s => s?.isValid ?? false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
for (const [id, validMap] of Object.entries(idValidMap)) {
|
|
for (const [id, validMap] of Object.entries(idValidMap)) {
|
|
|
- assert(validMap.length === data.supportingLocales.length);
|
|
|
|
|
-
|
|
|
|
|
// warn
|
|
// warn
|
|
|
if (!validMap.every(bool => bool)) {
|
|
if (!validMap.every(bool => bool)) {
|
|
|
// eslint-disable-next-line no-console
|
|
// eslint-disable-next-line no-console
|
|
|
- console.warn(`[WARN] Template '${id}' has invalid status`);
|
|
|
|
|
|
|
+ console.warn(`[WARN] Template '${id}' has some locales that status is invalid`);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// This means the template directory does not have any valid template
|
|
// This means the template directory does not have any valid template
|
|
@@ -160,3 +192,14 @@ export const validateTemplatePlugin = async(projectDirRoot: string): Promise<boo
|
|
|
|
|
|
|
|
return true;
|
|
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' });
|
|
|
|
|
+};
|