UNPKG

@bscotch/sprite-source

Version:

Art pipeline scripting module for GameMaker sprites.

153 lines 6.28 kB
import { __decorate, __metadata } from "tslib"; import { existsSafe, pathy } from '@bscotch/pathy'; import { sequential } from '@bscotch/utility'; import { SpriteCache } from './SpriteCache.js'; import { spriteSourceConfigSchema, } from './SpriteSource.schemas.js'; import { FIO_RETRY_DELAY, MAX_FIO_RETRIES, retryOptions } from './constants.js'; import { SpriteSourceError, assert, deletePngChildren, getDirs, rethrow, } from './utility.js'; export class SpriteSource extends SpriteCache { get configFile() { return this.stitchDir .join('sprites.source.json') .withValidator(spriteSourceConfigSchema); } async resolveStaged(staging) { const dir = pathy(staging.dir, this.spritesRoot); if (!(await existsSafe(dir, retryOptions))) { this.issues.push(new SpriteSourceError(`Staging directory does not exist: ${dir}`)); return; } // Identify all "SpriteDirs". Stored as a set so we // can remove the ones we process. const spriteDirs = new Set(await this.getSpriteDirs(await getDirs(dir.absolute))); for (const transform of staging.transforms) { // filter to matching sprites const pattern = transform.include ? new RegExp(transform.include) : undefined; const sprites = []; for (const sprite of spriteDirs) { if (!pattern || sprite.path.relative.match(pattern)) { sprites.push(sprite); spriteDirs.delete(sprite); } } const waits = []; for (const sprite of sprites) { let outDirPath = sprite.path.relative; if (transform.renames) { for (const rename of transform.renames) { outDirPath = outDirPath.replace(new RegExp(rename.from), rename.to); } } const outDir = pathy(outDirPath, this.spritesRoot); // Crop it! const cropWait = transform.crop ? sprite.crop() : Promise.resolve(); // Bleed it! const bleedWait = cropWait.then(() => transform.bleed ? sprite.bleed() : Promise.resolve()); // Purge the output location! const deleteWait = bleedWait.then(() => transform.synced ? deletePngChildren(outDir).then((deleted) => { this.logs.push(...deleted.map((path) => ({ action: 'deleted', path: path.absolute, }))); }) : Promise.resolve()); // Save to destination (including renames) const moveWait = deleteWait.then(() => sprite.moveTo(outDir)); waits.push(moveWait); } await Promise.allSettled(waits); // Delete any folders-of-empty-folders const dirs = await getDirs(dir.absolute); for (const dir of dirs) { if ((await dir.exists()) && (await dir.isEmptyDirectory())) { await dir.delete({ recursive: true, force: true, retryDelay: FIO_RETRY_DELAY, maxRetries: MAX_FIO_RETRIES, }); } } } } async loadConfig(overrides = {}) { // Validate options. Show error out if invalid. try { overrides = spriteSourceConfigSchema.parse(overrides); } catch (err) { rethrow(err, 'Invalid SpriteSource options'); } assert(await this.spritesRoot.isDirectory(), 'Source must be an existing directory.'); // Update the config await this.stitchDir.ensureDirectory(); const config = await this.configFile.read({ fallback: {}, maxRetries: MAX_FIO_RETRIES, retryDelayMillis: FIO_RETRY_DELAY, }); const snapshot = JSON.stringify(config); if (overrides?.ignore !== undefined) { config.ignore = overrides.ignore; } if (overrides?.staging !== undefined) { config.staging = overrides.staging; } if (snapshot !== JSON.stringify(config)) { try { await this.configFile.write(config, { maxRetries: MAX_FIO_RETRIES, retryDelayMillis: FIO_RETRY_DELAY, }); } catch (err) { console.error(err); } } return config; } /** * Transform any staged sprites and add them to the source, * and compute updated sprite info. */ async update( /** Optionally override config options */ options) { const config = await this.loadConfig(options); // Process any staging folders for (const staging of config.staging ?? []) { // Do them sequentially since later patterns could overlap earlier ones await this.resolveStaged(staging); } // Update the sprite info cache const info = await this.updateSpriteInfo(config.ignore); return info; } /** * @param options If specified, creates/overwrites the config file with these options. */ static async from(spritesRoot, options) { // Ensure the spritesRoot exists assert(await existsSafe(spritesRoot, retryOptions), `Sprites root does not exist: ${spritesRoot}`); const source = new SpriteSource(spritesRoot); // Ensure a config file await source.loadConfig(options); return source; } } __decorate([ sequential, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], SpriteSource.prototype, "loadConfig", null); __decorate([ sequential, __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], SpriteSource.prototype, "update", null); //# sourceMappingURL=SpriteSource.js.map