diff --git a/config.yaml b/config.yaml index 19a6b3d..70cacd4 100644 --- a/config.yaml +++ b/config.yaml @@ -1,6 +1,7 @@ folder: operator: ./operator/ release: ./release/ + background: background directory: _assets operators: chen: !include config/chen.yaml diff --git a/libs/alpha_composite.js b/libs/alpha_composite.js index ee399f3..edd1ebd 100644 --- a/libs/alpha_composite.js +++ b/libs/alpha_composite.js @@ -2,15 +2,6 @@ import sharp from "sharp"; import path from "path"; export default class AlphaComposite { - #config - #operatorName - #operatorSourceFolder - - constructor(config, operatorName, rootDir) { - this.#config = config - this.#operatorName = operatorName - this.#operatorSourceFolder = path.join(rootDir, this.#config.folder.operator, this.#operatorName) - } async process(filename, extractedDir) { const image = sharp(path.join(extractedDir, filename)) diff --git a/libs/readme.js b/libs/append.js similarity index 68% rename from libs/readme.js rename to libs/append.js index ad05b82..6903fcf 100644 --- a/libs/readme.js +++ b/libs/append.js @@ -1,11 +1,18 @@ import path from 'path' import { appendSync, readSync } from './file.js' -export function append(config, operatorName, rootDir) { +export function appendReadme(config, operatorName, rootDir) { const operatorConfig = config.operators[operatorName] const projectJson = JSON.parse(readSync(path.join(rootDir, config.folder.operator, operatorName, 'project.json'))) appendSync( `\n| ${operatorConfig.title.split(' - ')[0].split('Arknights: ')[1]} | [Link](https://arknights.halyul.dev/${operatorConfig.link}/) | [Link](https://steamcommunity.com/sharedfiles/filedetails/?id=${projectJson.workshopid}) |`, path.join(rootDir, 'README.md') ) +} + +export function appendMainConfig(operatorName, rootDir) { + appendSync( + `\n ${operatorName}: !include config/${operatorName}.yaml`, + path.join(rootDir, 'config.yaml') + ) } \ No newline at end of file diff --git a/libs/assets_processor.js b/libs/assets_processor.js index 6816eb6..e87a6b3 100644 --- a/libs/assets_processor.js +++ b/libs/assets_processor.js @@ -35,7 +35,6 @@ export default class AssetsProcessor { const fallbackFilename = `${this.#config.operators[this.#operatorName].fallback_name}.png` const fallbackBuffer = await this.#alphaCompositer.process(fallbackFilename, extractedDir) await write(fallbackBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename)) - await copy(path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename), path.join(publicAssetsDir, fallbackFilename)) return { dimensions, assetsJson diff --git a/libs/background.js b/libs/background.js new file mode 100644 index 0000000..2f4b98b --- /dev/null +++ b/libs/background.js @@ -0,0 +1,63 @@ +import path from 'path'; +import fs from 'fs'; +import sharp from "sharp"; + +export default class Background { + #config + #rootDir + #backgroundFolder + #extractFolder + #files + + constructor(config, rootDir) { + this.#config = config; + this.#rootDir = rootDir; + this.#backgroundFolder = path.join(rootDir, config.folder.operator, '_share', config.folder.background); + this.#extractFolder = path.join(this.#backgroundFolder, 'extracted'); + } + + async process() { + this.#files = fs.readdirSync(this.#extractFolder).filter((f) => { + return f.endsWith('.png') && f.includes('_left'); + }) + if (this.#files.length + 2 !== fs.readdirSync(this.#backgroundFolder).length) { + await Promise.all(this.#files.map(async (f) => { + const filenamePrefix = path.parse(f).name.replace('_left', ''); + await this.#composite(filenamePrefix, '.png'); + })) + } else { + console.log('Background images already exist, skip generation.') + } + } + + async #composite(filenamePrefix, fileExt) { + const image = sharp(path.join(this.#extractFolder, `${filenamePrefix}_left${fileExt}`)) + const metadata = await image.metadata() + + image + .resize(2 * metadata.width, metadata.height, { + kernel: sharp.kernel.nearest, + fit: 'contain', + position: 'left top', + background: { r: 255, g: 255, b: 255, alpha: 0 } + }) + .composite([ + { + input: path.join(this.#extractFolder, `${filenamePrefix}_right${fileExt}`), + top: 0, + left: metadata.width, + }, + ]) + .toFile(path.join(this.#backgroundFolder, `${filenamePrefix}${fileExt}`)); + } + + getFilesToCopy(publicAssetsDir) { + return this.#files.map((f) => { + return { + filename: f.replace('_left', ''), + source: path.join(this.#backgroundFolder), + target: path.join(publicAssetsDir, this.#config.folder.background) + }; + }) + } +} \ No newline at end of file diff --git a/libs/exec.js b/libs/exec.js index 4f6c4c7..54e087c 100644 --- a/libs/exec.js +++ b/libs/exec.js @@ -19,10 +19,8 @@ export function runDev(rootDir) { })() } -export function runBuild(rootDir) { - ; (async () => { - await build({ - root: rootDir, - }) - })() +export async function runBuild(rootDir) { + await build({ + root: rootDir, + }) } \ No newline at end of file diff --git a/libs/initializer.js b/libs/initializer.js index 5970ff6..84d498d 100644 --- a/libs/initializer.js +++ b/libs/initializer.js @@ -1,7 +1,9 @@ import path from 'path' import { mkdir, copy } from './file.js' +import { appendMainConfig } from './append.js' export default function init(operatorName, __dirname, extractedDir) { mkdir(extractedDir) copy(path.join(__dirname, 'config', '_template.yaml'), path.join(__dirname, 'config', `${operatorName}.yaml`)) + appendMainConfig(operatorName, __dirname) } \ No newline at end of file diff --git a/libs/project_json.js b/libs/project_json.js index f366fd0..5efb601 100644 --- a/libs/project_json.js +++ b/libs/project_json.js @@ -38,6 +38,7 @@ export default class ProjectJson { if (matcher.match() !== null) { this.#json.description = matcher.process() } + // TODO: move the template generation to here this.#json = { ...this.#json, description: this.#json.description, diff --git a/runner.js b/runner.js index 1e5c1ec..511fbd7 100644 --- a/runner.js +++ b/runner.js @@ -9,105 +9,119 @@ import AssetsProcessor from './libs/assets_processor.js' import init from './libs/initializer.js' import directory from './libs/directory.js' import { buildAll, runDev, runBuild } from './libs/exec.js' -import { append } from './libs/readme.js' +import { appendReadme } from './libs/append.js' +import Background from './libs/background.js' -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const config = getConfig(__dirname) +async function main() { + const __dirname = path.dirname(fileURLToPath(import.meta.url)) + const config = getConfig(__dirname) -const op = process.argv[2] -const OPERATOR_NAME = process.argv[3]; + const op = process.argv[2] + const OPERATOR_NAME = process.argv[3]; -/** - * Skip all, no need for OPERATOR_NAME - * --build-all: build all assets - * --directory: build directory.json - */ -switch (op) { - case 'build-all': - buildAll(config) - process.exit(0) - case 'directory': - directory(config, __dirname) - process.exit(0) - default: - break + const background = new Background(config, __dirname) + await background.process() + + /** + * Skip all, no need for OPERATOR_NAME + * build-all: build all assets + * directory: build directory.json + */ + switch (op) { + case 'build-all': + buildAll(config) + process.exit(0) + case 'directory': + directory(config, __dirname) + process.exit(0) + default: + break + } + + assert(OPERATOR_NAME !== undefined, 'Please set the operator name.') + + const OPERATOR_SOURCE_FOLDER = path.join(__dirname, config.folder.operator) + const OPERATOR_RELEASE_FOLDER = path.join(__dirname, config.folder.release, OPERATOR_NAME) + const SHOWCASE_PUBLIC_FOLDER = path.join(__dirname, "public") + const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(SHOWCASE_PUBLIC_FOLDER, "assets") + const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted') + const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, '_share') + + /** + * Skip assets generation part + * init: init folder and config for an operator + * readme: append a new line to README.md + */ + switch (op) { + case 'init': + init(OPERATOR_NAME, __dirname, EXTRACTED_FOLDER) + process.exit(0) + case 'readme': + appendReadme(config, OPERATOR_NAME, __dirname) + process.exit(0) + default: + break + } + + rmdir(OPERATOR_RELEASE_FOLDER) + + const projectJson = new ProjectJson(config, OPERATOR_NAME, __dirname, OPERATOR_SHARE_FOLDER) + projectJson.load().then((content) => { + write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json')) + }) + + const assetsProcessor = new AssetsProcessor(config, OPERATOR_NAME, __dirname) + assetsProcessor.process(SHOWCASE_PUBLIC_ASSSETS_FOLDER, EXTRACTED_FOLDER).then((content) => { + write(JSON.stringify(content.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `${config.operators[OPERATOR_NAME].filename}.json`)) + }) + + const envGenerator = new EnvGenerator(config, OPERATOR_NAME, __dirname) + envGenerator.generate().then((content) => { + write(content, path.join(__dirname, '.env')) + }) + + const filesToCopy = [ + ...background.getFilesToCopy(SHOWCASE_PUBLIC_ASSSETS_FOLDER), + { + filename: 'preview.jpg', + source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), + target: path.join(OPERATOR_RELEASE_FOLDER) + }, + { + filename: 'operator_bg.png', + source: path.join(OPERATOR_SOURCE_FOLDER, config.folder.background), + target: path.join(SHOWCASE_PUBLIC_FOLDER) + }, + { + filename: `${config.operators[OPERATOR_NAME].logo}.png`, + source: path.join(OPERATOR_SHARE_FOLDER, 'logo'), + target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) + }, + { + filename: `${config.operators[OPERATOR_NAME].fallback_name}.png`, + source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), + target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) + }, + ] + filesToCopy.forEach(async (file) => { + await copy(path.join(file.source, file.filename), path.join(file.target, file.filename)) + }) + + /** + * dev: run dev server + * build: build assets + */ + switch (op) { + case 'dev': + runDev(__dirname) + break + case 'build': + await runBuild(__dirname) + case 'generate': + default: + rmdir(SHOWCASE_PUBLIC_FOLDER) + break + } } -assert(OPERATOR_NAME !== undefined, 'Please set the environment variable O to the operator name.') - -const OPERATOR_SOURCE_FOLDER = path.join(__dirname, config.folder.operator) -const OPERATOR_RELEASE_FOLDER = path.join(__dirname, config.folder.release, OPERATOR_NAME) -const SHOWCASE_PUBLIC_FOLDER = path.join(__dirname, "public") -const SHOWCASE_PUBLIC_ASSSETS_FOLDER = path.join(SHOWCASE_PUBLIC_FOLDER, "assets") -const EXTRACTED_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, 'extracted') -const OPERATOR_SHARE_FOLDER = path.join(OPERATOR_SOURCE_FOLDER, '_share') -rmdir(SHOWCASE_PUBLIC_FOLDER) - -/** - * Skip assets generation part - * --init: init folder and config for an operator - */ -switch (op) { - case 'init': - init(OPERATOR_NAME, __dirname, EXTRACTED_FOLDER) - process.exit(0) - case 'readme': - append(config, OPERATOR_NAME, __dirname) - process.exit(0) - default: - break -} - -rmdir(OPERATOR_RELEASE_FOLDER) - -const projectJson = new ProjectJson(config, OPERATOR_NAME, __dirname, OPERATOR_SHARE_FOLDER) -projectJson.load().then((content) => { - write(JSON.stringify(content, null, 2), path.join(OPERATOR_RELEASE_FOLDER, 'project.json')) -}) - -const assetsProcessor = new AssetsProcessor(config, OPERATOR_NAME, __dirname) -assetsProcessor.process(SHOWCASE_PUBLIC_ASSSETS_FOLDER, EXTRACTED_FOLDER).then((content) => { - write(JSON.stringify(content.assetsJson, null), path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME, `${config.operators[OPERATOR_NAME].filename}.json`)) -}) - -const envGenerator = new EnvGenerator(config, OPERATOR_NAME, __dirname) -envGenerator.generate().then((content) => { - write(content, path.join(__dirname, '.env')) -}) -const filesToCopy = [ - { - filename: 'preview.jpg', - source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), - target: path.join(OPERATOR_RELEASE_FOLDER) - }, - { - filename: 'operator_bg.png', - source: OPERATOR_SHARE_FOLDER, - target: path.join(SHOWCASE_PUBLIC_FOLDER) - }, - { - filename: `${config.operators[OPERATOR_NAME].logo}.png`, - source: path.join(OPERATOR_SHARE_FOLDER, 'logo'), - target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) - }, - { - filename: `${config.operators[OPERATOR_NAME].fallback_name}.png`, - source: path.join(OPERATOR_SOURCE_FOLDER, OPERATOR_NAME), - target: path.join(SHOWCASE_PUBLIC_ASSSETS_FOLDER) - }, -] -filesToCopy.forEach((file) => { - copy(path.join(file.source, file.filename), path.join(file.target, file.filename)) -}) - -switch (op) { - case 'dev': - runDev(__dirname) - break - case 'build': - runBuild(__dirname) - break - case 'generate': - default: - break -} \ No newline at end of file +main(); \ No newline at end of file