119 lines
3.1 KiB
JavaScript
119 lines
3.1 KiB
JavaScript
/* eslint-env node */
|
|
|
|
const fs = require('node:fs/promises');
|
|
const path = require('node:path');
|
|
|
|
const defaultResourcePaths = {
|
|
credits: path.resolve(__dirname, '..', 'src', 'data', 'credits.json'),
|
|
};
|
|
|
|
function isRecord(value) {
|
|
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
}
|
|
|
|
function isOptionalString(value) {
|
|
return value == null || typeof value === 'string';
|
|
}
|
|
|
|
function isCreditsLogo(value) {
|
|
return (
|
|
isRecord(value) &&
|
|
typeof value.title === 'string' &&
|
|
typeof value.subtitle === 'string' &&
|
|
isOptionalString(value.image)
|
|
);
|
|
}
|
|
|
|
function isCreditsContributor(value) {
|
|
return (
|
|
isRecord(value) &&
|
|
typeof value.name === 'string' &&
|
|
typeof value.role === 'string' &&
|
|
isOptionalString(value.credit) &&
|
|
isOptionalString(value.url)
|
|
);
|
|
}
|
|
|
|
function isCreditsTechnology(value) {
|
|
return (
|
|
isRecord(value) &&
|
|
typeof value.name === 'string' &&
|
|
isOptionalString(value.role) &&
|
|
isOptionalString(value.url)
|
|
);
|
|
}
|
|
|
|
function isCreditsContent(value) {
|
|
return (
|
|
isRecord(value) &&
|
|
isCreditsLogo(value.mainLogo) &&
|
|
Array.isArray(value.contributors) &&
|
|
value.contributors.every(isCreditsContributor) &&
|
|
Array.isArray(value.technologies) &&
|
|
value.technologies.every(isCreditsTechnology) &&
|
|
Array.isArray(value.specialThanks) &&
|
|
value.specialThanks.every(item => typeof item === 'string')
|
|
);
|
|
}
|
|
|
|
function getResourcePath(resourcePaths, resource) {
|
|
const resourcePath = resourcePaths[resource];
|
|
|
|
if (resourcePath == null) {
|
|
throw new Error(`Unsupported toolkit content resource: ${String(resource)}`);
|
|
}
|
|
|
|
return resourcePath;
|
|
}
|
|
|
|
function validateResourceContent(resource, content) {
|
|
if (resource === 'credits' && isCreditsContent(content)) {
|
|
return content;
|
|
}
|
|
|
|
throw new Error(`Invalid toolkit content for resource: ${resource}`);
|
|
}
|
|
|
|
function createJsonContentStore(resourcePaths = defaultResourcePaths) {
|
|
async function readJsonResource(resource) {
|
|
const resourcePath = getResourcePath(resourcePaths, resource);
|
|
const source = await fs.readFile(resourcePath, 'utf8');
|
|
const content = validateResourceContent(resource, JSON.parse(source));
|
|
const stats = await fs.stat(resourcePath);
|
|
|
|
return {
|
|
content,
|
|
resource,
|
|
revision: String(Math.round(stats.mtimeMs)),
|
|
};
|
|
}
|
|
|
|
async function writeJsonResource(resource, content) {
|
|
const resourcePath = getResourcePath(resourcePaths, resource);
|
|
const validatedContent = validateResourceContent(resource, content);
|
|
const temporaryPath = `${resourcePath}.${process.pid}.${Date.now()}.tmp`;
|
|
const formattedContent = `${JSON.stringify(validatedContent, null, 2)}\n`;
|
|
|
|
await fs.writeFile(temporaryPath, formattedContent, 'utf8');
|
|
await fs.rename(temporaryPath, resourcePath);
|
|
|
|
return readJsonResource(resource);
|
|
}
|
|
|
|
return {
|
|
readJsonResource,
|
|
resourcePaths,
|
|
writeJsonResource,
|
|
};
|
|
}
|
|
|
|
const defaultStore = createJsonContentStore();
|
|
|
|
module.exports = {
|
|
createJsonContentStore,
|
|
defaultResourcePaths,
|
|
isCreditsContent,
|
|
readJsonResource: defaultStore.readJsonResource,
|
|
writeJsonResource: defaultStore.writeJsonResource,
|
|
};
|