@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.
Files changed (67) hide show
  1. package/LICENSE.md +29 -0
  2. package/README.md +84 -0
  3. package/dist/SpriteCache.d.ts +67 -0
  4. package/dist/SpriteCache.d.ts.map +1 -0
  5. package/dist/SpriteCache.js +139 -0
  6. package/dist/SpriteCache.js.map +1 -0
  7. package/dist/SpriteCache.schemas.d.ts +74 -0
  8. package/dist/SpriteCache.schemas.d.ts.map +1 -0
  9. package/dist/SpriteCache.schemas.js +51 -0
  10. package/dist/SpriteCache.schemas.js.map +1 -0
  11. package/dist/SpriteDest.actions.d.ts +18 -0
  12. package/dist/SpriteDest.actions.d.ts.map +1 -0
  13. package/dist/SpriteDest.actions.js +163 -0
  14. package/dist/SpriteDest.actions.js.map +1 -0
  15. package/dist/SpriteDest.d.ts +35 -0
  16. package/dist/SpriteDest.d.ts.map +1 -0
  17. package/dist/SpriteDest.js +359 -0
  18. package/dist/SpriteDest.js.map +1 -0
  19. package/dist/SpriteDest.schemas.d.ts +45 -0
  20. package/dist/SpriteDest.schemas.d.ts.map +1 -0
  21. package/dist/SpriteDest.schemas.js +33 -0
  22. package/dist/SpriteDest.schemas.js.map +1 -0
  23. package/dist/SpriteDir.d.ts +39 -0
  24. package/dist/SpriteDir.d.ts.map +1 -0
  25. package/dist/SpriteDir.js +240 -0
  26. package/dist/SpriteDir.js.map +1 -0
  27. package/dist/SpriteFrame.d.ts +36 -0
  28. package/dist/SpriteFrame.d.ts.map +1 -0
  29. package/dist/SpriteFrame.js +225 -0
  30. package/dist/SpriteFrame.js.map +1 -0
  31. package/dist/SpriteSource.d.ts +60 -0
  32. package/dist/SpriteSource.d.ts.map +1 -0
  33. package/dist/SpriteSource.js +153 -0
  34. package/dist/SpriteSource.js.map +1 -0
  35. package/dist/SpriteSource.schemas.d.ts +56 -0
  36. package/dist/SpriteSource.schemas.d.ts.map +1 -0
  37. package/dist/SpriteSource.schemas.js +54 -0
  38. package/dist/SpriteSource.schemas.js.map +1 -0
  39. package/dist/checksum.d.ts +4 -0
  40. package/dist/checksum.d.ts.map +1 -0
  41. package/dist/checksum.js +28 -0
  42. package/dist/checksum.js.map +1 -0
  43. package/dist/cli.d.mts +3 -0
  44. package/dist/cli.d.mts.map +1 -0
  45. package/dist/cli.mjs +113 -0
  46. package/dist/cli.mjs.map +1 -0
  47. package/dist/constants.d.ts +8 -0
  48. package/dist/constants.d.ts.map +1 -0
  49. package/dist/constants.js +10 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/index.d.ts +9 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +8 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/safeFs.d.ts +5 -0
  56. package/dist/safeFs.d.ts.map +1 -0
  57. package/dist/safeFs.js +34 -0
  58. package/dist/safeFs.js.map +1 -0
  59. package/dist/types.d.ts +25 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +2 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/utility.d.ts +25 -0
  64. package/dist/utility.d.ts.map +1 -0
  65. package/dist/utility.js +101 -0
  66. package/dist/utility.js.map +1 -0
  67. 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
+ ![Stitch Logo](https://img.bscotch.net/fit-in/256x256/logos/stitch.png "The Stitch Logo, copyright and trademark Butterscotch Shenanigans, all rights reserved.")
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