rename sites to manager
This commit is contained in:
parent
5e866ca8d2
commit
ed025533cd
11 changed files with 15 additions and 15 deletions
14
projects/manager/package.json
Normal file
14
projects/manager/package.json
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "manager",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"chalk": "5.3.0",
|
||||
"symlink-dir": "6.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"cms-to-static": "node scripts/cms-to-static.js",
|
||||
"set-current": "node scripts/set-current.js"
|
||||
}
|
||||
}
|
191
projects/manager/scripts/cms-to-static.js
Normal file
191
projects/manager/scripts/cms-to-static.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
import chalk from 'chalk';
|
||||
|
||||
import readlinePromises from 'readline/promises';
|
||||
import { Readable } from 'stream';
|
||||
import {
|
||||
existsSync,
|
||||
createWriteStream,
|
||||
} from 'fs';
|
||||
import { finished } from 'stream/promises';
|
||||
import {
|
||||
mkdir,
|
||||
unlink,
|
||||
writeFile,
|
||||
} from 'fs/promises';
|
||||
|
||||
import { log } from '../src/log.js';
|
||||
|
||||
/**
|
||||
* Validates a string as a MediaEntry id
|
||||
* @param {string} str gets a valid id for a MediaEntry; it cannot have symbols that are not valid in paths
|
||||
* @returns {string} the validated string
|
||||
*/
|
||||
const getValidId = (str) => (str || '').replace(/( |\/|\\)/gm,'_');
|
||||
|
||||
/**
|
||||
* Saves the given url to the given destination
|
||||
* @param {string} url the url of the file to save
|
||||
* @param {string} destination the file destination
|
||||
*/
|
||||
const saveFileFromUrl = async (url, destination) => {
|
||||
if (existsSync(destination)) {
|
||||
log(`Deleting existing file ${chalk.italic(destination)}`, 'debug');
|
||||
await unlink(destination);
|
||||
log(`Deleted file ${chalk.italic(destination)}`, 'info');
|
||||
}
|
||||
|
||||
log(`Downloading file ${url}`, 'debug');
|
||||
const res = await fetch(url);
|
||||
log(`Saving file ${chalk.italic(url)} to ${chalk.italic(destination)}`, 'debug');
|
||||
const fileStream = createWriteStream(destination, { flags: 'wx' });
|
||||
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
||||
log(`File ${chalk.italic(url)} saved to ${chalk.italic(destination)}`, 'info');
|
||||
};
|
||||
|
||||
/**
|
||||
* Saves a media list's entries to the given destination and returns an updated list where the Strapi urls have been replaced
|
||||
* @param {MediaList} inVal the media list to save files from
|
||||
* @param {string} fetchUrl the Strapi API fetch url
|
||||
* @param {string} destination the parent destination where the media list is saving files/folders in
|
||||
* @param {string} destinationShort the shortened destination, where 'sites/{my-site}/' has been removed so that the file can be '/content/{file/path}'
|
||||
* @returns {Promise<MediaList>} the updated media list
|
||||
*/
|
||||
const saveVariantsToDestination = async (inVal, fetchUrl, destination, destinationShort) => {
|
||||
const rootUrl = fetchUrl.replace(/((http)|(https)):\/\//, '').split('/')[0];
|
||||
const outVal = JSON.parse(JSON.stringify(inVal));
|
||||
// Loop over each entry in the media list
|
||||
await Promise.all(Object.keys(outVal.entries).map(async entryId => {
|
||||
const entryPath = `${destination}/${entryId}`;
|
||||
if (!existsSync(entryPath)) {
|
||||
log(`Creating directory at ${chalk.italic(entryPath)}`, 'debug');
|
||||
await mkdir(entryPath);
|
||||
log(`Created directory ${chalk.italic(entryPath)}`, 'info');
|
||||
}
|
||||
|
||||
// Loop over each variant in the media entry
|
||||
await Promise.all(outVal.entries[entryId].variants.map(async (variant, index) => {
|
||||
// save the main file and update in the variant info
|
||||
const filePath = `${entryPath}/${variant.url.replace('/uploads/', '')}`;
|
||||
saveFileFromUrl(`http://${rootUrl}${variant.url}`, filePath);
|
||||
outVal.entries[entryId].variants[index].url = `/content/${destinationShort}/${entryId}/${variant.url.replace('/uploads/', '')}`;
|
||||
|
||||
// save the thumbnail and update in the variant info
|
||||
const thumbnailPath = `${entryPath}/${variant.thumbnailUrl.replace('/uploads/', '')}`;
|
||||
saveFileFromUrl(`http://${rootUrl}${variant.thumbnailUrl}`, thumbnailPath);
|
||||
outVal.entries[entryId].variants[index].thumbnailUrl = `/content/${destinationShort}/${entryId}/${variant.thumbnailUrl.replace('/uploads/', '')}`;
|
||||
|
||||
if (true) {
|
||||
await writeFile(`${entryPath}/info.json`, JSON.stringify(outVal.entries[entryId], null, 2));
|
||||
outVal.entries[entryId] = `/content/${destinationShort}/${entryId}/info.json`;
|
||||
}
|
||||
}));
|
||||
}));
|
||||
|
||||
return new Promise(resolve => resolve(outVal));
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps Strapi API response to a mackenzii media list
|
||||
* @param {StrapiMediaList} inVal the Strapi API response
|
||||
* @returns {Promise<MediaList>} the media list mapped from the Strapi API
|
||||
*/
|
||||
const mapStrapiResponseToMackenzii = async (inVal) => {
|
||||
const { data } = inVal;
|
||||
const outVal = {};
|
||||
|
||||
// handle list basic fields
|
||||
outVal.title = data.title;
|
||||
|
||||
// handle list entries
|
||||
outVal.entries = {};
|
||||
data.entries.forEach(entry => {
|
||||
// give it an id
|
||||
const id = `${getValidId(entry.date)}_${getValidId(entry.title)}_${getValidId(entry.documentId)}`;
|
||||
|
||||
// copy all values at first...
|
||||
outVal.entries[id] = {...entry};
|
||||
|
||||
// ...then delete strapi fields (as there are more to copy than delete)
|
||||
delete outVal.entries[id].id;
|
||||
delete outVal.entries[id].documentId;
|
||||
delete outVal.entries[id].createdAt;
|
||||
delete outVal.entries[id].updatedAt;
|
||||
delete outVal.entries[id].publishedAt;
|
||||
delete outVal.entries[id].locale;
|
||||
|
||||
// ...then handle variants
|
||||
outVal.entries[id].variants = entry.variants.map(variant => ({
|
||||
alternativeText: variant.alternativeText,
|
||||
caption: variant.caption,
|
||||
// the images will currently be mapped to strapi, to be handled with file grabber
|
||||
url: variant.url,
|
||||
thumbnailUrl: variant.formats['thumbnail'].url, // TBD: non-image assets
|
||||
}));
|
||||
|
||||
// ...also post-process tags into an array
|
||||
outVal.entries[id].tags = (outVal.entries[id].tags || '').split(/,| |;/).filter(val => val !== '');
|
||||
});
|
||||
|
||||
return new Promise(resolve => resolve(outVal));
|
||||
};
|
||||
|
||||
(async () => {
|
||||
console.log('NOTE: This is currently only built to support gallery lists. Article lists and other content are not supported');
|
||||
const rl = readlinePromises.createInterface(process.stdin, process.stdout);
|
||||
let fetchUrl = '';
|
||||
try {
|
||||
fetchUrl = await rl.question('Enter the URL to fetch from: ');
|
||||
} catch (err) {
|
||||
log('There was an error: ', 'error', 'cms-to-static::main::Q1', err);
|
||||
} finally {
|
||||
if (!fetchUrl) {
|
||||
log('An invalid URL was given. Quitting...', 'log', 'cms-to-static::main::Q1');
|
||||
rl.close();
|
||||
return;
|
||||
} else if (!fetchUrl.includes('?')) {
|
||||
fetchUrl += '?populate[0]=entries&populate[1]=entries.variants&populate[2]=tags&populate[3]=tags.value'
|
||||
}
|
||||
}
|
||||
|
||||
let destination = '';
|
||||
try {
|
||||
destination = await rl.question('Enter the directory to save the response\n(note: this should be a path relative to "sites";\n{your/input} will become sites/{your/input}):');
|
||||
destination = 'sites/' + destination;
|
||||
} catch (err) {
|
||||
log('There was an error: ', 'error', 'cms-to-static::main::Q2', err);
|
||||
} finally {
|
||||
rl.close();
|
||||
if (!destination) {
|
||||
log('An invalid path was given. Quitting...', 'log', 'cms-to-static::main::Q2');
|
||||
return;
|
||||
} else {
|
||||
if (!existsSync(destination)) {
|
||||
await mkdir(destination, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let res = {};
|
||||
try {
|
||||
res = await (await fetch(fetchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})).json();
|
||||
} catch (err) {
|
||||
log('There was an error: ', 'error', 'cms-to-static::main', err);
|
||||
}
|
||||
|
||||
log('Writing...', 'log', 'cms-to-static::main::writing');
|
||||
try {
|
||||
await writeFile(`${destination}/in.json`, JSON.stringify(res, null, 2));
|
||||
|
||||
mapStrapiResponseToMackenzii(res)
|
||||
.then(list => saveVariantsToDestination(list, fetchUrl, destination, destination.replace('sites/', '').split('/').slice(1).join('/')))
|
||||
.then(list => writeFile(`${destination}/out.json`, JSON.stringify(list, null, 2)));
|
||||
log('Done!', 'log', 'cms-to-static::main::writing');
|
||||
} catch (err) {
|
||||
log('There was an error: ', 'error', 'cms-to-static::main::writing', err);
|
||||
}
|
||||
})();
|
3
projects/manager/scripts/set-current.js
Normal file
3
projects/manager/scripts/set-current.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { setCurrent } from '../src/set-current.js';
|
||||
|
||||
setCurrent(process.env.npm_config_site);
|
103
projects/manager/src/log.js
Normal file
103
projects/manager/src/log.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Formats the given date for logging
|
||||
* @param {Date} date the date to format
|
||||
* @returns {string} the formatted date string in yyyy/mm/dd hh/mm/ss/SSS
|
||||
*/
|
||||
export const time = (date) => {
|
||||
/**
|
||||
* Stringifies the given number, prepends with 0s if the number string isn't long enough
|
||||
* @param {number} num the number to stringify
|
||||
* @param {number} expectedLength how long the number should be
|
||||
* @returns {string} the stringified number
|
||||
*/
|
||||
const stringifyNumber = (num, expectedLength) => {
|
||||
let str = `${num}`;
|
||||
while (str.length < expectedLength) {
|
||||
str = `0${str}`;
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = stringifyNumber(date.getMonth(), 2);
|
||||
const day = stringifyNumber(date.getDay(), 2);
|
||||
const hour = stringifyNumber(date.getHours(), 2);
|
||||
const minute = stringifyNumber(date.getMinutes(), 2);
|
||||
const second = stringifyNumber(date.getSeconds(), 2);
|
||||
const millisecond = stringifyNumber(date.getMilliseconds(), 2);
|
||||
|
||||
return `${year}/${month}/${day} ${hour}:${minute}:${second}.${millisecond}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message to the console
|
||||
* @param {string} message the message to log
|
||||
* @param {'debug'|'info'|'log'|'warn'|'error'} kind the kind of message being logged; denotes logging level
|
||||
* - debug is lowest level of message (0)
|
||||
* - info is the second lowest level of message (1)
|
||||
* - log is the default level level of message (2)
|
||||
* - warn is the second highest level of message (3)
|
||||
* - error is the highest level of message (4)
|
||||
* @param {string} context the context; if undefined, assume part of a parent context and indent the message
|
||||
* @param {string} extraData any extra data (e.g. if there was an error, pass the error object here)
|
||||
*/
|
||||
export const log = (message, kind, context, extraData) => {
|
||||
const logLevel = process.env.npm_config_logging || 2;
|
||||
const formatted = `${chalk.bold(`[${time(new Date())}] `)}${context ? context + ': ' : ' '}${message}`;
|
||||
switch (kind) {
|
||||
case 'debug': {
|
||||
if (logLevel > 0) {
|
||||
return;
|
||||
}
|
||||
if (extraData) {
|
||||
console.debug(`${chalk.gray.bold('[debug]')}${formatted}`, extraData);
|
||||
} else {
|
||||
console.debug(`${chalk.gray.bold('[debug]')}${formatted}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'info': {
|
||||
if (logLevel > 1) {
|
||||
return;
|
||||
}
|
||||
if (extraData) {
|
||||
console.info(`${chalk.white.bold('[info]')}${formatted}`, extraData);
|
||||
} else {
|
||||
console.info(`${chalk.white.bold('[info]')}${formatted}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'warn': {
|
||||
if (logLevel > 3) {
|
||||
return;
|
||||
}
|
||||
if (extraData) {
|
||||
console.warn(`${chalk.yellow.bold('[warn]')}${formatted}`, extraData);
|
||||
} else {
|
||||
console.warn(`${chalk.yellow.bold('[warn]')}${formatted}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
if (extraData) {
|
||||
console.error(`${chalk.red.bold('[error]')}${formatted}`, extraData);
|
||||
} else {
|
||||
console.error(`${chalk.red.bold('[error]')}${formatted}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (logLevel > 2) {
|
||||
return;
|
||||
}
|
||||
if (extraData) {
|
||||
console.log(`${chalk.blue.bold('[log]')}${formatted}`, extraData);
|
||||
} else {
|
||||
console.log(`${chalk.blue.bold('[log]')}${formatted}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
24
projects/manager/src/set-current.js
Normal file
24
projects/manager/src/set-current.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import symlinkDir from 'symlink-dir';
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdir, unlink, writeFile } from 'fs/promises';
|
||||
|
||||
export const setCurrent = async (site, ctx) => {
|
||||
const managerPath = ctx?.managerPath || '../manager';
|
||||
const frontendPath = ctx?.frontendPath || '../frontend';
|
||||
if (!!site) {
|
||||
await symlinkDir(`${managerPath}/sites/${site}`, `${frontendPath}/content`);
|
||||
if (!existsSync(`${frontendPath}/dist`)) {
|
||||
await mkdir(`${frontendPath}/dist`);
|
||||
}
|
||||
await symlinkDir(`${managerPath}/sites/${site}`, `${frontendPath}/dist/content`);
|
||||
|
||||
if (existsSync(`${managerPath}/sites/current.txt`)) {
|
||||
await unlink(`${managerPath}/sites/current.txt`);
|
||||
}
|
||||
await writeFile(`${managerPath}/sites/current.txt`, site, { encoding: 'utf-8' });
|
||||
|
||||
console.log('done');
|
||||
} else {
|
||||
console.error('Parameter "site" was not provided!');
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue