@bscotch/sprite-source 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +29 -0
- package/README.md +84 -0
- package/dist/SpriteCache.d.ts +67 -0
- package/dist/SpriteCache.d.ts.map +1 -0
- package/dist/SpriteCache.js +139 -0
- package/dist/SpriteCache.js.map +1 -0
- package/dist/SpriteCache.schemas.d.ts +74 -0
- package/dist/SpriteCache.schemas.d.ts.map +1 -0
- package/dist/SpriteCache.schemas.js +51 -0
- package/dist/SpriteCache.schemas.js.map +1 -0
- package/dist/SpriteDest.actions.d.ts +18 -0
- package/dist/SpriteDest.actions.d.ts.map +1 -0
- package/dist/SpriteDest.actions.js +163 -0
- package/dist/SpriteDest.actions.js.map +1 -0
- package/dist/SpriteDest.d.ts +35 -0
- package/dist/SpriteDest.d.ts.map +1 -0
- package/dist/SpriteDest.js +359 -0
- package/dist/SpriteDest.js.map +1 -0
- package/dist/SpriteDest.schemas.d.ts +45 -0
- package/dist/SpriteDest.schemas.d.ts.map +1 -0
- package/dist/SpriteDest.schemas.js +33 -0
- package/dist/SpriteDest.schemas.js.map +1 -0
- package/dist/SpriteDir.d.ts +39 -0
- package/dist/SpriteDir.d.ts.map +1 -0
- package/dist/SpriteDir.js +240 -0
- package/dist/SpriteDir.js.map +1 -0
- package/dist/SpriteFrame.d.ts +36 -0
- package/dist/SpriteFrame.d.ts.map +1 -0
- package/dist/SpriteFrame.js +225 -0
- package/dist/SpriteFrame.js.map +1 -0
- package/dist/SpriteSource.d.ts +60 -0
- package/dist/SpriteSource.d.ts.map +1 -0
- package/dist/SpriteSource.js +153 -0
- package/dist/SpriteSource.js.map +1 -0
- package/dist/SpriteSource.schemas.d.ts +56 -0
- package/dist/SpriteSource.schemas.d.ts.map +1 -0
- package/dist/SpriteSource.schemas.js +54 -0
- package/dist/SpriteSource.schemas.js.map +1 -0
- package/dist/checksum.d.ts +4 -0
- package/dist/checksum.d.ts.map +1 -0
- package/dist/checksum.js +28 -0
- package/dist/checksum.js.map +1 -0
- package/dist/cli.d.mts +3 -0
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +113 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +10 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/safeFs.d.ts +5 -0
- package/dist/safeFs.d.ts.map +1 -0
- package/dist/safeFs.js +34 -0
- package/dist/safeFs.js.map +1 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utility.d.ts +25 -0
- package/dist/utility.d.ts.map +1 -0
- package/dist/utility.js +101 -0
- package/dist/utility.js.map +1 -0
- package/package.json +58 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# License for the Stitch Monorepo
|
|
2
|
+
|
|
3
|
+
License information for the content of this git repository. The various packages in this repo are developed by [Butterscotch Shenanigans](https://www.bscotch.net) ("Bscotch").
|
|
4
|
+
|
|
5
|
+
## License files
|
|
6
|
+
|
|
7
|
+
Whenever a `LICENSE` file is found in a folder, or a license is specified by a `package.json` or similar manifest file, that license takes precedence for all files in that folder and its subfolders.
|
|
8
|
+
|
|
9
|
+
This is the root-most `LICENSE` file, and therefore dictates the default licensing in this repository.
|
|
10
|
+
|
|
11
|
+
## Stitch name and logo
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
Stitch™ and its logo are trademarks of Bscotch. Bscotch reserves all rights to Stitch and its logo.
|
|
16
|
+
|
|
17
|
+
Stitch Logo ©2022 by Bscotch. All rights reserved.
|
|
18
|
+
|
|
19
|
+
## Other code and content
|
|
20
|
+
|
|
21
|
+
Everything found in this project, except for the Stitch name and logo as described above or unless otherwise specified by a more proximal LICENSE file, falls under the MIT license (https://opensource.org/license/mit/):
|
|
22
|
+
|
|
23
|
+
Copyright 2023 Butterscotch Shenanigans
|
|
24
|
+
|
|
25
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
26
|
+
|
|
27
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
28
|
+
|
|
29
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Sprite Source
|
|
2
|
+
|
|
3
|
+
This project provides utilities for creating art asset pipelines for GameMaker games. It is used in [Stitch for VSCode](https://marketplace.visualstudio.com/items?itemName=bscotch.bscotch-stitch-vscode) and developed against the pipeline and team requirements of [Butterscotch Shenanigans](https://www.bscotch.net/). It may or may not be applicable to other use cases.
|
|
4
|
+
|
|
5
|
+
**⚠️ This package will make irreversible changes to your files! Use it at your own risk, and use version control to keep everything safe.**
|
|
6
|
+
|
|
7
|
+
## Core Concepts
|
|
8
|
+
|
|
9
|
+
An "art pipeline" is the process by which an artist gets from their art from editing/creating tools (like Clip Studio Paint or Photoshop) to having an in-game sprite. For the approach taken by this package, an art pipeline consists of the following:
|
|
10
|
+
|
|
11
|
+
1. Art Generator. The tools used by artists to create art assets. In our case, Clip Studio Paint.
|
|
12
|
+
2. A "Sprite Source". A Sprite Source is a collection of _raw_ art exports created by the Art Generator, placed into "staging" areas. This is documented by a `sprites.source.json` configuration file. Staging areas are listed in this file, where each consists of:
|
|
13
|
+
- A directory where raw exports can be found.
|
|
14
|
+
- Configuration information for how raw files should be transformed. For example, whether images should be renamed, cropped, and/or bled.
|
|
15
|
+
3. A "Sprite Destination" (SpriteDest). This is assumed to be a GameMaker project, where final versions of art assets should end up as sprite frames (or Spine sprites). This is all governed by a `sprites/.stitch/sprites.import.json` config file in the GameMaker project. This config file contains an array of "sources", where each source includes:
|
|
16
|
+
- The path to a Sprite Source
|
|
17
|
+
- Optional paths to "Collaborator Sources". These are other Sprite Sources that are _not_ used for import, but if sprites are found in both the source and the collaborator sources, then imports will only happen if
|
|
18
|
+
- Options to ignore source files or auto-prefix them
|
|
19
|
+
|
|
20
|
+
## Configuration
|
|
21
|
+
|
|
22
|
+
Your pipelines are configured by a combination of config files:
|
|
23
|
+
|
|
24
|
+
- A config called `.stitch/sprites.import.json` will be added to your project's `sprites` folder. This config lists your Sprite Sources and adds some options for how to handle them, like prefixing incoming sprite names.
|
|
25
|
+
- A config called `.stitch/sprites.source.json` will be added to _each_ of your root Sprite Source folders. These folders are what the project's `sprites.import.json` references. These configs are used to describe your "stages", which are a collection of subconfigs describing _which_ image files within the root Sprite Source folder should be exported and how to transform them (e.g. adding bleeds, cropping, or renaming).
|
|
26
|
+
|
|
27
|
+
This config files include a field called `$schema`, pointing to a JSON Schema that describes their content. If you open these config files in editors that understand that information, like VSCode and many other IDEs and code editors, you'll be able to hover over things to get descriptions, get autocompletes, and see warnings if something looks invalid.
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
You can create and use pipelines using this package from Stitch for VSCode, the CLI, or programmatically.
|
|
32
|
+
|
|
33
|
+
### Requirements
|
|
34
|
+
|
|
35
|
+
To create an art pipeline, you'll need:
|
|
36
|
+
|
|
37
|
+
- A folder (of folders) of source images
|
|
38
|
+
- Spine sprites are allowed
|
|
39
|
+
- Only PNGs are supported
|
|
40
|
+
- Each terminal folder containing one or more PNGs is treated as a "Sprite", with each PNG treated as a subimage for that sprite
|
|
41
|
+
- The terminal folder name is used as the sprite name (⚠️ make sure each one is unique!)
|
|
42
|
+
- The subimage order in GameMaker will match the alphabetical sort order of the PNG names
|
|
43
|
+
- A GameMaker project using a fairly current version of GameMaker
|
|
44
|
+
- Git. (Not strictly required, but a _really_ good idea!)
|
|
45
|
+
|
|
46
|
+
### Stitch for VSCode
|
|
47
|
+
|
|
48
|
+
[Stitch for VSCode](https://marketplace.visualstudio.com/items?itemName=bscotch.bscotch-stitch-vscode) has this package built right into it, and has an option to turn on a "watch" mode so that as you make changes to your source images they'll immediately get imported into your GameMaker project.
|
|
49
|
+
|
|
50
|
+
To use it, get VSCode and install the Stitch extension, then open the Stitch panel and look for the "Sprite Sources" sub-panel (it's probably the bottom-most one). From there you can find buttons to add sprite source folders, add stages to them, open the config files for direct editing, toggle the import watcher, manually run an import, and see a list of sprites updated in the current session.
|
|
51
|
+
|
|
52
|
+
### CLI
|
|
53
|
+
|
|
54
|
+
To get some CLI commands, you can install this globally via `npm install -g @bscotch/sprite-source` (or `pnpm add -g @bscotch-sprite-source`). This will make the CLI command `spsrc` available in you your terminal (run `spsrc --help` to see your options).
|
|
55
|
+
|
|
56
|
+
As of writing, there are two CLI commands:
|
|
57
|
+
|
|
58
|
+
- `spsrc add-source path/to/image/collection path/to/project.yyp` will initialize the two configuration files you need to create a default pipeline
|
|
59
|
+
- `spsrc import path/to/project.yyp` will run the transform/import process on any changed source images, syncing your source images with your GameMaker sprites
|
|
60
|
+
|
|
61
|
+
### Programmatic
|
|
62
|
+
|
|
63
|
+
If you have a Typescript-aware code editor, you can write Node scripts in Typescript or JavaScript and get information from your editor about the programmatic API.
|
|
64
|
+
|
|
65
|
+
The main entrypoints are:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { SpriteSource, SpriteDest } from '@bscotch/sprite-source';
|
|
69
|
+
|
|
70
|
+
const source = await SpriteSource.from('path/to/raw/exports');
|
|
71
|
+
// Ensure that the cached data about the raw images is up to date.
|
|
72
|
+
await source.update();
|
|
73
|
+
|
|
74
|
+
// Import into a project
|
|
75
|
+
const dest = await SpriteDest.from('my/project.yyp');
|
|
76
|
+
await dest.import({
|
|
77
|
+
sources: [
|
|
78
|
+
{
|
|
79
|
+
source: source.configFile,
|
|
80
|
+
prefix: 'sp_',
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Pathy } from '@bscotch/pathy';
|
|
2
|
+
import { SpritesInfo } from './SpriteCache.schemas.js';
|
|
3
|
+
import { SpriteDir } from './SpriteDir.js';
|
|
4
|
+
import type { Log } from './types.js';
|
|
5
|
+
export declare class SpriteCache {
|
|
6
|
+
readonly maxDepth: number;
|
|
7
|
+
readonly issues: Error[];
|
|
8
|
+
readonly logs: Log[];
|
|
9
|
+
readonly spritesRoot: Pathy;
|
|
10
|
+
/**
|
|
11
|
+
* @param spritesRoot The path to the root directory containing
|
|
12
|
+
* sprites. For a SpriteSource, this is the root of a set
|
|
13
|
+
* of nested folders-of-images. For a SpriteDest (a project),
|
|
14
|
+
* this is the `{project}/sprites` folder.
|
|
15
|
+
* @param maxDepth The maximum depth to search for sprites. For a SpriteSource this is probably Infinity. For a SpriteDest, this should be 1.
|
|
16
|
+
*/
|
|
17
|
+
constructor(spritesRoot: string | Pathy, maxDepth?: number);
|
|
18
|
+
get stitchDir(): Pathy<unknown>;
|
|
19
|
+
get cacheFile(): Pathy<{
|
|
20
|
+
[x: string]: unknown;
|
|
21
|
+
version: number;
|
|
22
|
+
info: Record<string, {
|
|
23
|
+
[x: string]: unknown;
|
|
24
|
+
spine: false;
|
|
25
|
+
checksum: string;
|
|
26
|
+
frames: Record<string, {
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
checksum: string;
|
|
30
|
+
changed: number;
|
|
31
|
+
}>;
|
|
32
|
+
} | {
|
|
33
|
+
[x: string]: unknown;
|
|
34
|
+
spine: true;
|
|
35
|
+
checksum: string;
|
|
36
|
+
changed: number;
|
|
37
|
+
}>;
|
|
38
|
+
$schema?: string | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
protected getSpriteDirs(dirs: Pathy[]): Promise<SpriteDir[]>;
|
|
41
|
+
protected loadCache(): Promise<SpritesInfo>;
|
|
42
|
+
/**
|
|
43
|
+
* Update the sprite-info cache.
|
|
44
|
+
*/
|
|
45
|
+
protected updateSpriteInfo(ignore?: string[] | null): Promise<{
|
|
46
|
+
[x: string]: unknown;
|
|
47
|
+
version: number;
|
|
48
|
+
info: Record<string, {
|
|
49
|
+
[x: string]: unknown;
|
|
50
|
+
spine: false;
|
|
51
|
+
checksum: string;
|
|
52
|
+
frames: Record<string, {
|
|
53
|
+
width: number;
|
|
54
|
+
height: number;
|
|
55
|
+
checksum: string;
|
|
56
|
+
changed: number;
|
|
57
|
+
}>;
|
|
58
|
+
} | {
|
|
59
|
+
[x: string]: unknown;
|
|
60
|
+
spine: true;
|
|
61
|
+
checksum: string;
|
|
62
|
+
changed: number;
|
|
63
|
+
}>;
|
|
64
|
+
$schema?: string | undefined;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=SpriteCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpriteCache.d.ts","sourceRoot":"","sources":["../src/SpriteCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAS,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EACL,WAAW,EAGZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAGtC,qBAAa,WAAW;IAcpB,QAAQ,CAAC,QAAQ;IAbnB,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,CAAM;IAC9B,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,CAAM;IAC1B,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC;IAE5B;;;;;;OAMG;gBAED,WAAW,EAAE,MAAM,GAAG,KAAK,EAClB,QAAQ,SAAW;IAK9B,IAAI,SAAS,mBAEZ;IAED,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;OAIZ;cAEe,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;cAsBlD,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;IAiCjD;;OAEG;cACa,gBAAgB,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;CA0E1D"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { pathy } from '@bscotch/pathy';
|
|
2
|
+
import { computePngChecksums } from '@bscotch/pixel-checksum';
|
|
3
|
+
import { cacheVersion, spritesInfoSchema, } from './SpriteCache.schemas.js';
|
|
4
|
+
import { SpriteDir } from './SpriteDir.js';
|
|
5
|
+
import { computeStringChecksum } from './checksum.js';
|
|
6
|
+
import { retryOptions, spriteCacheFilename } from './constants.js';
|
|
7
|
+
import { SpriteSourceError, getDirs } from './utility.js';
|
|
8
|
+
export class SpriteCache {
|
|
9
|
+
maxDepth;
|
|
10
|
+
issues = [];
|
|
11
|
+
logs = [];
|
|
12
|
+
spritesRoot;
|
|
13
|
+
/**
|
|
14
|
+
* @param spritesRoot The path to the root directory containing
|
|
15
|
+
* sprites. For a SpriteSource, this is the root of a set
|
|
16
|
+
* of nested folders-of-images. For a SpriteDest (a project),
|
|
17
|
+
* this is the `{project}/sprites` folder.
|
|
18
|
+
* @param maxDepth The maximum depth to search for sprites. For a SpriteSource this is probably Infinity. For a SpriteDest, this should be 1.
|
|
19
|
+
*/
|
|
20
|
+
constructor(spritesRoot, maxDepth = Infinity) {
|
|
21
|
+
this.maxDepth = maxDepth;
|
|
22
|
+
this.spritesRoot = pathy(spritesRoot);
|
|
23
|
+
}
|
|
24
|
+
get stitchDir() {
|
|
25
|
+
return this.spritesRoot.join('.stitch');
|
|
26
|
+
}
|
|
27
|
+
get cacheFile() {
|
|
28
|
+
return this.stitchDir
|
|
29
|
+
.join(spriteCacheFilename)
|
|
30
|
+
.withValidator(spritesInfoSchema);
|
|
31
|
+
}
|
|
32
|
+
async getSpriteDirs(dirs) {
|
|
33
|
+
const waits = [];
|
|
34
|
+
const spriteDirs = [];
|
|
35
|
+
for (const dir of dirs) {
|
|
36
|
+
waits.push(SpriteDir.from(dir, this.logs, this.issues)
|
|
37
|
+
.then((sprite) => {
|
|
38
|
+
if (sprite) {
|
|
39
|
+
spriteDirs.push(sprite);
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.catch((err) => {
|
|
43
|
+
this.issues.push(new SpriteSourceError(`Error processing "${dir.relative}"`, err));
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
await Promise.all(waits);
|
|
47
|
+
return spriteDirs;
|
|
48
|
+
}
|
|
49
|
+
async loadCache() {
|
|
50
|
+
let cache;
|
|
51
|
+
try {
|
|
52
|
+
cache = await this.cacheFile.read({
|
|
53
|
+
fallback: {},
|
|
54
|
+
...retryOptions,
|
|
55
|
+
});
|
|
56
|
+
if (cache.version !== cacheVersion) {
|
|
57
|
+
cache = {
|
|
58
|
+
version: cacheVersion,
|
|
59
|
+
info: {},
|
|
60
|
+
};
|
|
61
|
+
this.issues.push(new SpriteSourceError(`Sprite cache version is out of date. Will rebuild.`));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
cache = {
|
|
66
|
+
version: cacheVersion,
|
|
67
|
+
info: {},
|
|
68
|
+
};
|
|
69
|
+
this.issues.push(new SpriteSourceError(`Could not load sprite cache. Will rebuild.`, err));
|
|
70
|
+
}
|
|
71
|
+
return cache;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Update the sprite-info cache.
|
|
75
|
+
*/
|
|
76
|
+
async updateSpriteInfo(ignore) {
|
|
77
|
+
const ignorePatterns = ignore?.map((pattern) => new RegExp(pattern));
|
|
78
|
+
// Load the current cache and sprite dirs
|
|
79
|
+
const [cache, allSpriteDirs] = await Promise.all([
|
|
80
|
+
this.loadCache(),
|
|
81
|
+
getDirs(this.spritesRoot.absolute, this.maxDepth).then((dirs) => this.getSpriteDirs(dirs)),
|
|
82
|
+
]);
|
|
83
|
+
// Filter out ignored spriteDirs
|
|
84
|
+
const spriteDirs = !ignore?.length
|
|
85
|
+
? allSpriteDirs
|
|
86
|
+
: allSpriteDirs.filter((dir) => !ignorePatterns?.some((pattern) => dir.path.relative.match(pattern)));
|
|
87
|
+
// For each sprite, update the cache with its size, frames (checksums, changedAt, etc)
|
|
88
|
+
const waits = [];
|
|
89
|
+
for (const sprite of spriteDirs) {
|
|
90
|
+
waits.push(sprite.updateCache(cache));
|
|
91
|
+
}
|
|
92
|
+
await Promise.all(waits);
|
|
93
|
+
// Add any missing checksums
|
|
94
|
+
const checksumsToCompute = [];
|
|
95
|
+
for (const [sprite, info] of Object.entries(cache.info)) {
|
|
96
|
+
if (info.spine)
|
|
97
|
+
continue;
|
|
98
|
+
for (const [frame, frameInfo] of Object.entries(info.frames)) {
|
|
99
|
+
if (frameInfo.checksum)
|
|
100
|
+
continue;
|
|
101
|
+
checksumsToCompute.push([
|
|
102
|
+
sprite,
|
|
103
|
+
frame,
|
|
104
|
+
this.spritesRoot.join(sprite, frame).absolute,
|
|
105
|
+
]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const checksums = computePngChecksums(checksumsToCompute.map(([_s, _f, framePath]) => framePath));
|
|
109
|
+
checksumsToCompute.forEach(([sprite, frame], idx) => {
|
|
110
|
+
const spriteCache = cache.info[sprite];
|
|
111
|
+
if (spriteCache.spine)
|
|
112
|
+
return; // Shouldn't happen
|
|
113
|
+
spriteCache.frames[frame].checksum = checksums[idx];
|
|
114
|
+
spriteCache.checksum = ''; // Will be recomputed below
|
|
115
|
+
});
|
|
116
|
+
// Ensure cumulative checksums for all non-spine sprites
|
|
117
|
+
for (const [, spriteCache] of Object.entries(cache.info)) {
|
|
118
|
+
if (spriteCache.spine || spriteCache.checksum)
|
|
119
|
+
continue;
|
|
120
|
+
const frameChecksums = Object.values(spriteCache.frames)
|
|
121
|
+
.map((f) => f.checksum)
|
|
122
|
+
.sort();
|
|
123
|
+
spriteCache.checksum = computeStringChecksum(frameChecksums.join('-'));
|
|
124
|
+
}
|
|
125
|
+
// Remove any sprite info that no longer exists
|
|
126
|
+
const existingSpriteDirs = new Set(spriteDirs.map((dir) => dir.path.relative));
|
|
127
|
+
for (const spriteDir of Object.keys(cache.info)) {
|
|
128
|
+
if (!existingSpriteDirs.has(spriteDir)) {
|
|
129
|
+
delete cache.info[spriteDir];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Save and return the updated cache
|
|
133
|
+
await this.cacheFile.write(cache, {
|
|
134
|
+
...retryOptions,
|
|
135
|
+
});
|
|
136
|
+
return cache;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=SpriteCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpriteCache.js","sourceRoot":"","sources":["../src/SpriteCache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const cacheVersion = 2;
|
|
3
|
+
export type ImageSummary = z.infer<typeof imageSummarySchema>;
|
|
4
|
+
declare const imageSummarySchema: z.ZodObject<{
|
|
5
|
+
width: z.ZodNumber;
|
|
6
|
+
height: z.ZodNumber;
|
|
7
|
+
checksum: z.ZodString;
|
|
8
|
+
changed: z.ZodNumber;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export type SpriteSummary = z.infer<typeof spriteSummarySchema>;
|
|
11
|
+
declare const spriteSummarySchema: z.ZodObject<{
|
|
12
|
+
spine: z.ZodLiteral<false>;
|
|
13
|
+
checksum: z.ZodString;
|
|
14
|
+
frames: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
15
|
+
width: z.ZodNumber;
|
|
16
|
+
height: z.ZodNumber;
|
|
17
|
+
checksum: z.ZodString;
|
|
18
|
+
changed: z.ZodNumber;
|
|
19
|
+
}, z.core.$strip>>;
|
|
20
|
+
}, z.core.$loose>;
|
|
21
|
+
export type SpineSummary = z.infer<typeof spineSummarySchema>;
|
|
22
|
+
declare const spineSummarySchema: z.ZodObject<{
|
|
23
|
+
spine: z.ZodLiteral<true>;
|
|
24
|
+
checksum: z.ZodString;
|
|
25
|
+
changed: z.ZodNumber;
|
|
26
|
+
}, z.core.$loose>;
|
|
27
|
+
export type SpritesInfo = z.infer<typeof spritesInfoSchema>;
|
|
28
|
+
export declare const spritesInfoSchema: z.ZodObject<{
|
|
29
|
+
$schema: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
30
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
info: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
32
|
+
spine: z.ZodLiteral<false>;
|
|
33
|
+
checksum: z.ZodString;
|
|
34
|
+
frames: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
35
|
+
width: z.ZodNumber;
|
|
36
|
+
height: z.ZodNumber;
|
|
37
|
+
checksum: z.ZodString;
|
|
38
|
+
changed: z.ZodNumber;
|
|
39
|
+
}, z.core.$strip>>;
|
|
40
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
41
|
+
spine: z.ZodLiteral<true>;
|
|
42
|
+
checksum: z.ZodString;
|
|
43
|
+
changed: z.ZodNumber;
|
|
44
|
+
}, z.core.$loose>]>>>;
|
|
45
|
+
}, z.core.$loose>;
|
|
46
|
+
export declare const spritesInfoInfo: {
|
|
47
|
+
schema: z.ZodObject<{
|
|
48
|
+
$schema: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
49
|
+
version: z.ZodDefault<z.ZodNumber>;
|
|
50
|
+
info: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
51
|
+
spine: z.ZodLiteral<false>;
|
|
52
|
+
checksum: z.ZodString;
|
|
53
|
+
frames: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
54
|
+
width: z.ZodNumber;
|
|
55
|
+
height: z.ZodNumber;
|
|
56
|
+
checksum: z.ZodString;
|
|
57
|
+
changed: z.ZodNumber;
|
|
58
|
+
}, z.core.$strip>>;
|
|
59
|
+
}, z.core.$loose>, z.ZodObject<{
|
|
60
|
+
spine: z.ZodLiteral<true>;
|
|
61
|
+
checksum: z.ZodString;
|
|
62
|
+
changed: z.ZodNumber;
|
|
63
|
+
}, z.core.$loose>]>>>;
|
|
64
|
+
}, z.core.$loose>;
|
|
65
|
+
name: string;
|
|
66
|
+
filename: string;
|
|
67
|
+
};
|
|
68
|
+
export declare function lastChanged(info: SpriteSummary | SpineSummary): number;
|
|
69
|
+
/**
|
|
70
|
+
* Returns `true` if `a` is newer than `b`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isNewer(a: SpriteSummary | SpineSummary, b: SpriteSummary | SpineSummary): boolean;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=SpriteCache.schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpriteCache.schemas.d.ts","sourceRoot":"","sources":["../src/SpriteCache.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,QAAA,MAAM,kBAAkB;;;;;iBAKtB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,QAAA,MAAM,mBAAmB;;;;;;;;;iBAQvB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAC9D,QAAA,MAAM,kBAAkB;;;;iBAYtB,CAAC;AAIH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC5D,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;iBAS5B,CAAC;AAEH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;CAI3B,CAAC;AAEF,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,UAI7D;AAED;;GAEG;AACH,wBAAgB,OAAO,CACrB,CAAC,EAAE,aAAa,GAAG,YAAY,EAC/B,CAAC,EAAE,aAAa,GAAG,YAAY,WAGhC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { jsonSchemaRemoteDir } from './constants.js';
|
|
3
|
+
export const cacheVersion = 2;
|
|
4
|
+
const imageSummarySchema = z.object({
|
|
5
|
+
width: z.number(),
|
|
6
|
+
height: z.number(),
|
|
7
|
+
checksum: z.string().describe('Pixel-based checksum of the image.'),
|
|
8
|
+
changed: z.number().describe('Unix timestamp of last modification date.'),
|
|
9
|
+
});
|
|
10
|
+
const spriteSummarySchema = z.looseObject({
|
|
11
|
+
spine: z.literal(false),
|
|
12
|
+
checksum: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('A checksum combining the pixel-based checksums of all of the frame checksums.'),
|
|
15
|
+
frames: z.record(z.string(), imageSummarySchema),
|
|
16
|
+
});
|
|
17
|
+
const spineSummarySchema = z.looseObject({
|
|
18
|
+
spine: z.literal(true),
|
|
19
|
+
checksum: z
|
|
20
|
+
.string()
|
|
21
|
+
.describe('A checksum combining the pixel-based checksum of the atlas file with the contets of the atlas and json files.'),
|
|
22
|
+
changed: z
|
|
23
|
+
.number()
|
|
24
|
+
.describe('Unix timestamp of the most recent modified date for all associate files (atlas, json, png).'),
|
|
25
|
+
});
|
|
26
|
+
const schemaFilename = 'stitch.sprite-cache.schema.json';
|
|
27
|
+
const remoteFilename = `${jsonSchemaRemoteDir}/${schemaFilename}`;
|
|
28
|
+
export const spritesInfoSchema = z.looseObject({
|
|
29
|
+
$schema: z.string().default(remoteFilename).optional(),
|
|
30
|
+
version: z.number().default(1),
|
|
31
|
+
info: z
|
|
32
|
+
.record(z.string(), z.discriminatedUnion('spine', [spriteSummarySchema, spineSummarySchema]))
|
|
33
|
+
.default({}),
|
|
34
|
+
});
|
|
35
|
+
export const spritesInfoInfo = {
|
|
36
|
+
schema: spritesInfoSchema,
|
|
37
|
+
name: 'Sprite Cache',
|
|
38
|
+
filename: schemaFilename,
|
|
39
|
+
};
|
|
40
|
+
export function lastChanged(info) {
|
|
41
|
+
return info.spine
|
|
42
|
+
? info.changed
|
|
43
|
+
: Math.max(...Object.values(info.frames).map((f) => f.changed), 0);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns `true` if `a` is newer than `b`.
|
|
47
|
+
*/
|
|
48
|
+
export function isNewer(a, b) {
|
|
49
|
+
return lastChanged(a) > lastChanged(b);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=SpriteCache.schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpriteCache.schemas.js","sourceRoot":"","sources":["../src/SpriteCache.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAG9B,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oCAAoC,CAAC;IACnE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;CAC1E,CAAC,CAAC;AAGH,MAAM,mBAAmB,GAAG,CAAC,CAAC,WAAW,CAAC;IACxC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACvB,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,CACP,+EAA+E,CAChF;IACH,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,kBAAkB,CAAC;CACjD,CAAC,CAAC;AAGH,MAAM,kBAAkB,GAAG,CAAC,CAAC,WAAW,CAAC;IACvC,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IACtB,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,CACP,+GAA+G,CAChH;IACH,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,QAAQ,CACP,6FAA6F,CAC9F;CACJ,CAAC,CAAC;AAEH,MAAM,cAAc,GAAG,iCAAiC,CAAC;AACzD,MAAM,cAAc,GAAG,GAAG,mBAAmB,IAAI,cAAc,EAAE,CAAC;AAElE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,WAAW,CAAC;IAC7C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;IACtD,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9B,IAAI,EAAE,CAAC;SACJ,MAAM,CACL,CAAC,CAAC,MAAM,EAAE,EACV,CAAC,CAAC,kBAAkB,CAAC,OAAO,EAAE,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC,CACzE;SACA,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,MAAM,EAAE,iBAAiB;IACzB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,cAAc;CACzB,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,IAAkC;IAC5D,OAAO,IAAI,CAAC,KAAK;QACf,CAAC,CAAC,IAAI,CAAC,OAAO;QACd,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CACrB,CAA+B,EAC/B,CAA+B;IAE/B,OAAO,WAAW,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Yyp, type YypResourceId } from '@bscotch/yy';
|
|
2
|
+
import type { SpriteDestAction } from './SpriteDest.schemas.js';
|
|
3
|
+
export interface ApplySpriteActionOptions {
|
|
4
|
+
projectYypPath: string;
|
|
5
|
+
action: SpriteDestAction;
|
|
6
|
+
yyp: Yyp;
|
|
7
|
+
}
|
|
8
|
+
export interface SpriteDestActionResult {
|
|
9
|
+
resource: YypResourceId;
|
|
10
|
+
folder: {
|
|
11
|
+
name: string;
|
|
12
|
+
folderPath: string;
|
|
13
|
+
};
|
|
14
|
+
/** The path to the sprite source root from which this action started */
|
|
15
|
+
sourceRoot: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function applySpriteAction({ projectYypPath, action, yyp, }: ApplySpriteActionOptions): Promise<SpriteDestActionResult>;
|
|
18
|
+
//# sourceMappingURL=SpriteDest.actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpriteDest.actions.d.ts","sourceRoot":"","sources":["../src/SpriteDest.actions.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,GAAG,EACR,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,gBAAgB,CAAC;IACzB,GAAG,EAAE,GAAG,CAAC;CACV;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,aAAa,CAAC;IACxB,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,iBAAiB,CAAC,EACtC,cAAc,EACd,MAAM,EACN,GAAG,GACJ,EAAE,wBAAwB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqL5D"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { pathy } from '@bscotch/pathy';
|
|
2
|
+
import { Yy, yySpriteSchema, } from '@bscotch/yy';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { FIO_RETRY_DELAY, MAX_FIO_RETRIES } from './constants.js';
|
|
5
|
+
import { readdirSafeWithFileTypes } from './safeFs.js';
|
|
6
|
+
import { getPngSize } from './utility.js';
|
|
7
|
+
export async function applySpriteAction({ projectYypPath, action, yyp, }) {
|
|
8
|
+
let trace = [];
|
|
9
|
+
try {
|
|
10
|
+
// Ensure the target path exists
|
|
11
|
+
const targetFolder = pathy(action.dest);
|
|
12
|
+
// If this is a `create` action, delete the existing sprite
|
|
13
|
+
// (no effect when there literally is no existing sprite, but
|
|
14
|
+
// there could be leftover files, or a sprite with the same name
|
|
15
|
+
// but different type, etc.)
|
|
16
|
+
if (action.kind === 'create') {
|
|
17
|
+
trace.push(`Recursively deleting ${targetFolder}`);
|
|
18
|
+
await targetFolder.delete({
|
|
19
|
+
recursive: true,
|
|
20
|
+
force: true,
|
|
21
|
+
maxRetries: MAX_FIO_RETRIES,
|
|
22
|
+
retryDelay: FIO_RETRY_DELAY,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
trace.push(`Ensuring ${targetFolder}`);
|
|
26
|
+
await targetFolder.ensureDirectory();
|
|
27
|
+
// Get the list of children in the source and destination
|
|
28
|
+
trace.push(`Reading ${targetFolder.absolute} and ${action.source}`);
|
|
29
|
+
const [initialDestFileNames, sourceFileNames] = await Promise.all([
|
|
30
|
+
readdirSafeWithFileTypes(targetFolder),
|
|
31
|
+
readdirSafeWithFileTypes(action.source),
|
|
32
|
+
]);
|
|
33
|
+
const initialDestFiles = initialDestFileNames
|
|
34
|
+
.filter((f) => f.isFile())
|
|
35
|
+
.map((f) => pathy(f.name, targetFolder));
|
|
36
|
+
const sourceFiles = sourceFileNames
|
|
37
|
+
.filter((f) => f.isFile())
|
|
38
|
+
.map((f) => pathy(f.name, action.source));
|
|
39
|
+
const sourcePngs = sourceFiles.filter((f) => f.basename.match(/\.png$/i));
|
|
40
|
+
// Get origin info etc
|
|
41
|
+
trace.push(`Getting origin info`);
|
|
42
|
+
const size = await getPngSize(sourcePngs[0]);
|
|
43
|
+
const width = size.width;
|
|
44
|
+
const height = size.height;
|
|
45
|
+
const xorigin = Math.floor(width / 2) - 1;
|
|
46
|
+
const yorigin = Math.floor(height / 2) - 1;
|
|
47
|
+
// Load the yy file, or populate a default one
|
|
48
|
+
const yyFile = initialDestFiles.find((f) => f.basename.toLowerCase() === `${action.name}.yy`.toLowerCase()) || pathy(`${action.name}.yy`, targetFolder);
|
|
49
|
+
if (!(await yyFile.exists())) {
|
|
50
|
+
trace.push(`Creating ${yyFile.absolute}`);
|
|
51
|
+
await Yy.write(yyFile.absolute, {
|
|
52
|
+
name: action.name,
|
|
53
|
+
type: action.spine ? 2 : 0,
|
|
54
|
+
width,
|
|
55
|
+
height,
|
|
56
|
+
sequence: { xorigin, yorigin },
|
|
57
|
+
}, 'sprites', yyp);
|
|
58
|
+
}
|
|
59
|
+
trace.push(`Reading yy file ${yyFile}`);
|
|
60
|
+
let yy = await Yy.read(yyFile.absolute, 'sprites');
|
|
61
|
+
// Populate the frames to get UUIDs
|
|
62
|
+
// Keep the old frameIds if it's a spine sprite (the alternative would be to ensure we rename the GameMaker-generated thumbnail)
|
|
63
|
+
const frames = action.spine ? yy.frames : [];
|
|
64
|
+
frames.length = action.spine ? 1 : sourcePngs.length;
|
|
65
|
+
yy = yySpriteSchema.parse({ ...yy, frames });
|
|
66
|
+
if (action.spine) {
|
|
67
|
+
trace.push('Is Spine action');
|
|
68
|
+
// Copy over and rename the skeleton files
|
|
69
|
+
const uuid = yy.frames[0].name;
|
|
70
|
+
const ioWaits = [];
|
|
71
|
+
const keepers = new Set([yyFile.basename.toLowerCase()]);
|
|
72
|
+
for (const fileType of ['json', 'atlas', 'png']) {
|
|
73
|
+
const sourceFile = sourceFiles.find((f) => f.hasExtension(fileType));
|
|
74
|
+
const destFile = pathy(`${fileType === 'png' ? sourceFile.name : uuid}.${fileType}`, targetFolder);
|
|
75
|
+
trace.push(`Copying ${sourceFile} to ${destFile}`);
|
|
76
|
+
ioWaits.push(sourceFile.copy(destFile).catch((reason) => {
|
|
77
|
+
trace.push(reason);
|
|
78
|
+
throw reason;
|
|
79
|
+
}));
|
|
80
|
+
keepers.add(destFile.basename.toLowerCase());
|
|
81
|
+
keepers.add(`${uuid}.${fileType}`.toLowerCase());
|
|
82
|
+
}
|
|
83
|
+
// Delete excess files
|
|
84
|
+
for (const file of initialDestFiles) {
|
|
85
|
+
if (!keepers.has(file.basename.toLowerCase())) {
|
|
86
|
+
trace.push(`Deleting ${file}`);
|
|
87
|
+
ioWaits.push(file
|
|
88
|
+
.delete({
|
|
89
|
+
force: true,
|
|
90
|
+
maxRetries: MAX_FIO_RETRIES,
|
|
91
|
+
retryDelay: FIO_RETRY_DELAY,
|
|
92
|
+
})
|
|
93
|
+
.catch((reason) => {
|
|
94
|
+
trace.push(reason);
|
|
95
|
+
throw reason;
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
trace.push('Awaiting io ops');
|
|
100
|
+
await Promise.all(ioWaits);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
trace.push('Is non-Spine action');
|
|
104
|
+
// Ensure the source pngs are sorted by basename
|
|
105
|
+
sourcePngs.sort((a, b) => a.basename.localeCompare(b.basename, 'en-US'));
|
|
106
|
+
// Ensure the width & height are still correct
|
|
107
|
+
yy.width = width;
|
|
108
|
+
yy.height = height;
|
|
109
|
+
// Copy over the pngs
|
|
110
|
+
const ioWaits = [];
|
|
111
|
+
const keepers = new Set([yyFile.basename.toLowerCase()]);
|
|
112
|
+
for (let i = 0; i < sourcePngs.length; i++) {
|
|
113
|
+
const uuid = yy.frames[i].name;
|
|
114
|
+
const png = sourcePngs[i];
|
|
115
|
+
const destFile = pathy(`${uuid}.png`, targetFolder);
|
|
116
|
+
trace.push(`Copying ${png} to ${destFile}`);
|
|
117
|
+
ioWaits.push(png.copy(destFile).catch((reason) => {
|
|
118
|
+
trace.push(reason);
|
|
119
|
+
throw reason;
|
|
120
|
+
}));
|
|
121
|
+
keepers.add(destFile.basename.toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
// Delete excess files
|
|
124
|
+
for (const file of initialDestFiles) {
|
|
125
|
+
if (!keepers.has(file.basename.toLowerCase())) {
|
|
126
|
+
trace.push(`Deleting ${file}`);
|
|
127
|
+
ioWaits.push(file
|
|
128
|
+
.delete({
|
|
129
|
+
force: true,
|
|
130
|
+
maxRetries: MAX_FIO_RETRIES,
|
|
131
|
+
retryDelay: FIO_RETRY_DELAY,
|
|
132
|
+
})
|
|
133
|
+
.catch((reason) => {
|
|
134
|
+
trace.push(reason);
|
|
135
|
+
throw reason;
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
trace.push('Awaiting io ops');
|
|
140
|
+
await Promise.all(ioWaits);
|
|
141
|
+
}
|
|
142
|
+
// Save the yy file
|
|
143
|
+
trace.push(`Saving yy file ${yyFile}`);
|
|
144
|
+
await Yy.write(yyFile.absolute, yy, 'sprites', yyp);
|
|
145
|
+
// Send back info that can be used to update the project file
|
|
146
|
+
return {
|
|
147
|
+
resource: {
|
|
148
|
+
name: yy.name,
|
|
149
|
+
path: yyFile.relativeFrom(path.dirname(projectYypPath)),
|
|
150
|
+
},
|
|
151
|
+
folder: {
|
|
152
|
+
name: yy.parent.name,
|
|
153
|
+
folderPath: yy.parent.path,
|
|
154
|
+
},
|
|
155
|
+
sourceRoot: action.sourceRoot,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
trace.push(err);
|
|
160
|
+
throw trace;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=SpriteDest.actions.js.map
|