mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
#2227 Implement content scaffolding
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,3 +47,4 @@ access.log
|
|||||||
public/css/user.css
|
public/css/user.css
|
||||||
/plugins/
|
/plugins/
|
||||||
/data
|
/data
|
||||||
|
/default/scaffold
|
||||||
|
26
default/scaffold/README.md
Normal file
26
default/scaffold/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Content Scaffolding
|
||||||
|
|
||||||
|
Content files in this folder will be copied for all users (old and new) on the server startup.
|
||||||
|
|
||||||
|
1. You **must** create an `index.json` file in `/default/scaffold` for it to work. The syntax is the same as for default content.
|
||||||
|
2. All file paths should be relative to `/default/scaffold`, the use of subdirectories is allowed.
|
||||||
|
3. Scaffolded files are copied first, so they override any of the default files (presets/settings/etc.) that have the same file name.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "themes/Midnight.json",
|
||||||
|
"type": "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "backgrounds/city.png",
|
||||||
|
"type": "background"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename": "characters/Charlie.png",
|
||||||
|
"type": "character"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
@ -7,7 +7,9 @@ const { getConfigValue, color } = require('../util');
|
|||||||
const { jsonParser } = require('../express-common');
|
const { jsonParser } = require('../express-common');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
const contentDirectory = path.join(process.cwd(), 'default/content');
|
const contentDirectory = path.join(process.cwd(), 'default/content');
|
||||||
|
const scaffoldDirectory = path.join(process.cwd(), 'default/scaffold');
|
||||||
const contentIndexPath = path.join(contentDirectory, 'index.json');
|
const contentIndexPath = path.join(contentDirectory, 'index.json');
|
||||||
|
const scaffoldIndexPath = path.join(scaffoldDirectory, 'index.json');
|
||||||
const characterCardParser = require('../character-card-parser.js');
|
const characterCardParser = require('../character-card-parser.js');
|
||||||
|
|
||||||
const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []);
|
const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDomains', []);
|
||||||
@ -16,6 +18,8 @@ const WHITELIST_GENERIC_URL_DOWNLOAD_SOURCES = getConfigValue('whitelistImportDo
|
|||||||
* @typedef {Object} ContentItem
|
* @typedef {Object} ContentItem
|
||||||
* @property {string} filename
|
* @property {string} filename
|
||||||
* @property {string} type
|
* @property {string} type
|
||||||
|
* @property {string} [name]
|
||||||
|
* @property {string|null} [folder]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,9 +52,7 @@ const CONTENT_TYPES = {
|
|||||||
*/
|
*/
|
||||||
function getDefaultPresets(directories) {
|
function getDefaultPresets(directories) {
|
||||||
try {
|
try {
|
||||||
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
const contentIndex = getContentIndex();
|
||||||
const contentIndex = JSON.parse(contentIndexText);
|
|
||||||
|
|
||||||
const presets = [];
|
const presets = [];
|
||||||
|
|
||||||
for (const contentItem of contentIndex) {
|
for (const contentItem of contentIndex) {
|
||||||
@ -112,8 +114,12 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
contentLog.push(contentItem.filename);
|
if (!contentItem.folder) {
|
||||||
const contentPath = path.join(contentDirectory, contentItem.filename);
|
console.log(`Content file ${contentItem.filename} has no parent folder`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentPath = path.join(contentItem.folder, contentItem.filename);
|
||||||
|
|
||||||
if (!fs.existsSync(contentPath)) {
|
if (!fs.existsSync(contentPath)) {
|
||||||
console.log(`Content file ${contentItem.filename} is missing`);
|
console.log(`Content file ${contentItem.filename} is missing`);
|
||||||
@ -129,6 +135,7 @@ async function seedContentForUser(contentIndex, directories, forceCategories) {
|
|||||||
|
|
||||||
const basePath = path.parse(contentItem.filename).base;
|
const basePath = path.parse(contentItem.filename).base;
|
||||||
const targetPath = path.join(contentTarget, basePath);
|
const targetPath = path.join(contentTarget, basePath);
|
||||||
|
contentLog.push(contentItem.filename);
|
||||||
|
|
||||||
if (fs.existsSync(targetPath)) {
|
if (fs.existsSync(targetPath)) {
|
||||||
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
|
console.log(`Content file ${contentItem.filename} already exists in ${contentTarget}`);
|
||||||
@ -157,8 +164,7 @@ async function checkForNewContent(directoriesList, forceCategories = []) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
const contentIndex = getContentIndex();
|
||||||
const contentIndex = JSON.parse(contentIndexText);
|
|
||||||
let anyContentAdded = false;
|
let anyContentAdded = false;
|
||||||
|
|
||||||
for (const directories of directoriesList) {
|
for (const directories of directoriesList) {
|
||||||
@ -179,6 +185,38 @@ async function checkForNewContent(directoriesList, forceCategories = []) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets combined content index from the content and scaffold directories.
|
||||||
|
* @returns {ContentItem[]} Array of content index
|
||||||
|
*/
|
||||||
|
function getContentIndex() {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
if (fs.existsSync(scaffoldIndexPath)) {
|
||||||
|
const scaffoldIndexText = fs.readFileSync(scaffoldIndexPath, 'utf8');
|
||||||
|
const scaffoldIndex = JSON.parse(scaffoldIndexText);
|
||||||
|
if (Array.isArray(scaffoldIndex)) {
|
||||||
|
scaffoldIndex.forEach((item) => {
|
||||||
|
item.folder = scaffoldDirectory;
|
||||||
|
});
|
||||||
|
result.push(...scaffoldIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(contentIndexPath)) {
|
||||||
|
const contentIndexText = fs.readFileSync(contentIndexPath, 'utf8');
|
||||||
|
const contentIndex = JSON.parse(contentIndexText);
|
||||||
|
if (Array.isArray(contentIndex)) {
|
||||||
|
contentIndex.forEach((item) => {
|
||||||
|
item.folder = contentDirectory;
|
||||||
|
});
|
||||||
|
result.push(...contentIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the target directory for the specified asset type.
|
* Gets the target directory for the specified asset type.
|
||||||
* @param {ContentType} type Asset type
|
* @param {ContentType} type Asset type
|
||||||
|
Reference in New Issue
Block a user