@remvst/asset-catalog 1.4.0 → 1.6.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.
@@ -0,0 +1 @@
1
+ npx lint-staged
@@ -0,0 +1 @@
1
+ npm test
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="WEB_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.tmp" />
6
+ <excludeFolder url="file://$MODULE_DIR$/temp" />
7
+ <excludeFolder url="file://$MODULE_DIR$/tmp" />
8
+ </content>
9
+ <orderEntry type="inheritedJdk" />
10
+ <orderEntry type="sourceFolder" forTests="false" />
11
+ </component>
12
+ </module>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Ask2AgentMigrationStateService">
4
+ <option name="migrationStatus" value="COMPLETED" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/asset-catalog.iml" filepath="$PROJECT_DIR$/.idea/asset-catalog.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
package/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
@@ -0,0 +1,5 @@
1
+ node_modules
2
+ lib
3
+ testOut
4
+ *.min.js
5
+ package-lock.json
package/.prettierrc ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "semi": true,
3
+ "trailingComma": "all",
4
+ "singleQuote": true,
5
+ "printWidth": 100,
6
+ "tabWidth": 4
7
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const fs_1 = require("fs");
8
+ const utils_1 = require("./utils");
9
+ const path_1 = require("path");
10
+ const yargs_1 = __importDefault(require("yargs/yargs"));
11
+ const helpers_1 = require("yargs/helpers");
12
+ const tree_1 = require("./tree");
13
+ function importName(assetDir, file) {
14
+ let importName = file;
15
+ importName = (0, path_1.resolve)(file);
16
+ const prefix = (0, path_1.resolve)(assetDir);
17
+ const prefixIndex = importName.indexOf(prefix);
18
+ if (prefixIndex >= 0) {
19
+ importName = importName.slice(prefixIndex + prefix.length);
20
+ }
21
+ importName = (0, utils_1.sanitize)(importName);
22
+ return importName;
23
+ }
24
+ function generatedTemplateInterface(tree, indent = '') {
25
+ let generated = '{\n';
26
+ for (const [subname, item] of tree.entries()) {
27
+ if (item instanceof Map) {
28
+ const generatedSub = generatedTemplateInterface(item, indent + ' ');
29
+ generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub}`;
30
+ }
31
+ else {
32
+ const name = (0, path_1.basename)(subname);
33
+ generated += indent + ` ${(0, utils_1.lowerCamelize)(name)}: T,\n`;
34
+ }
35
+ }
36
+ generated += indent + '}\n';
37
+ return generated;
38
+ }
39
+ async function generatedCreateCatalogFunction(assetDir, tree) {
40
+ async function rec(tree, indent = '') {
41
+ let generated = '{\n';
42
+ for (const [subname, item] of tree.entries()) {
43
+ if (item instanceof Map) {
44
+ const generatedSub = await rec(item, indent + ' ');
45
+ generated += indent + ` ${(0, utils_1.lowerCamelize)(subname)}: ${generatedSub},\n`;
46
+ }
47
+ else {
48
+ const stats = await fs_1.promises.stat(item);
49
+ const name = (0, path_1.basename)(subname);
50
+ generated +=
51
+ indent +
52
+ ` ${(0, utils_1.lowerCamelize)(name)}: createItem(expand(
53
+ ${importName(assetDir, item)},
54
+ ${stats.size},
55
+ '${item}',
56
+ )),\n`;
57
+ }
58
+ }
59
+ generated += indent + '}';
60
+ return generated;
61
+ }
62
+ let generated = '';
63
+ generated +=
64
+ 'export function createFileCatalog<T>(createItem: (opts: CreateItemOptions) => T): FileCatalog<T> {\n';
65
+ generated += ` return ${await rec(tree, ' ')};\n`;
66
+ generated += '}\n';
67
+ return generated;
68
+ }
69
+ function generateExpandFunction() {
70
+ let generated = '';
71
+ generated +=
72
+ 'function expand(path: string, size: number, originalPath: string): CreateItemOptions {\n';
73
+ generated += ` return { path, size, originalPath };\n`;
74
+ generated += '}\n';
75
+ return generated;
76
+ }
77
+ function generateResolveFunction() {
78
+ let generated = '';
79
+ generated +=
80
+ 'export function resolveFromCatalog<T>(catalog: FileCatalog<T>, path: string[]): T {\n';
81
+ generated += ' let current: any = catalog;\n';
82
+ generated += ` for (const component of path) {\n`;
83
+ generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
84
+ generated += ` current = current[component];\n`;
85
+ generated += ` }\n`;
86
+ generated += ` return current as T;\n`;
87
+ generated += '}\n';
88
+ return generated;
89
+ }
90
+ async function main() {
91
+ const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
92
+ outFile: {
93
+ type: 'string',
94
+ default: 'files.ts',
95
+ alias: 'o',
96
+ describe: 'Directory to generate the files into',
97
+ },
98
+ dir: {
99
+ type: 'string',
100
+ default: '.',
101
+ alias: 'd',
102
+ describe: 'Asset directory where all the PNGs are located',
103
+ },
104
+ }).argv;
105
+ const filesRoot = argv.dir;
106
+ const generatedTs = argv.outFile;
107
+ try {
108
+ await fs_1.promises.rm(generatedTs, { recursive: true });
109
+ }
110
+ catch (e) { }
111
+ const files = await (0, utils_1.allFiles)(filesRoot);
112
+ const imports = [];
113
+ const tree = await (0, tree_1.generateTree)(argv.dir, files);
114
+ for (const file of files) {
115
+ const importPath = (0, path_1.relative)((0, path_1.dirname)(argv.outFile), file).replace(/\\/g, '/');
116
+ imports.push(`import ${importName(argv.dir, file)} from './${importPath}';`);
117
+ }
118
+ let generatedFileContent = '';
119
+ generatedFileContent += imports.join('\n');
120
+ generatedFileContent += '\n\n';
121
+ generatedFileContent += `export interface CreateItemOptions {
122
+ path: string;
123
+ size: number;
124
+ originalPath: string;
125
+ }\n\n`;
126
+ generatedFileContent += 'export type FileCatalog<T> = ' + generatedTemplateInterface(tree);
127
+ generatedFileContent += '\n';
128
+ generatedFileContent += generateExpandFunction();
129
+ generatedFileContent += '\n';
130
+ generatedFileContent += generateResolveFunction();
131
+ generatedFileContent += '\n';
132
+ generatedFileContent += await generatedCreateCatalogFunction(argv.dir, tree);
133
+ await fs_1.promises.writeFile(generatedTs, generatedFileContent);
134
+ }
135
+ main();
@@ -56,7 +56,9 @@ async function generatedCreateCatalogFunction(assetDir, tree, spritesheet) {
56
56
  if (spriteData) {
57
57
  spriteDataArr = `expandSpriteData(SpriteSheetPng, ${spriteData.x}, ${spriteData.y}, ${spriteData.width}, ${spriteData.height})`;
58
58
  }
59
- generated += indent + ` ${(0, utils_1.lowerCamelize)(withoutExt)}: createItem(expand(
59
+ generated +=
60
+ indent +
61
+ ` ${(0, utils_1.lowerCamelize)(withoutExt)}: createItem(expand(
60
62
  ${importName(assetDir, item)},
61
63
  ${dimensions.width},
62
64
  ${dimensions.height},
@@ -69,24 +71,28 @@ async function generatedCreateCatalogFunction(assetDir, tree, spritesheet) {
69
71
  return generated;
70
72
  }
71
73
  let generated = '\n';
72
- generated += 'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
74
+ generated +=
75
+ 'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
73
76
  generated += ` return ${await rec(tree, ' ')};\n`;
74
77
  generated += '}\n';
75
78
  return generated;
76
79
  }
77
80
  function generateExpandFunction() {
78
81
  let generated = '\n';
79
- generated += 'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
82
+ generated +=
83
+ 'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
80
84
  generated += ` return { sheet, frame: { x, y, width, height } };\n`;
81
85
  generated += '}\n\n';
82
- generated += 'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
86
+ generated +=
87
+ 'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
83
88
  generated += ` return { path, width, height, size, spriteData };\n`;
84
89
  generated += '}\n';
85
90
  return generated;
86
91
  }
87
92
  function generateResolveFunction() {
88
93
  let generated = '\n';
89
- generated += 'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
94
+ generated +=
95
+ 'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
90
96
  generated += ' let current = catalog;\n';
91
97
  generated += ` for (const component of path) {\n`;
92
98
  generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
@@ -127,7 +133,7 @@ async function createSpritesheet(tree, outFile, excludes) {
127
133
  const image = await (0, canvas_1.loadImage)(item.item.path);
128
134
  ctx.drawImage(image, item.x + padding, item.y + padding);
129
135
  }
130
- const buffer = canvas.toBuffer("image/png");
136
+ const buffer = canvas.toBuffer('image/png');
131
137
  await fs_1.promises.writeFile(outFile, buffer);
132
138
  const resultMap = new Map();
133
139
  for (const item of packed.items) {
@@ -141,43 +147,41 @@ async function createSpritesheet(tree, outFile, excludes) {
141
147
  return resultMap;
142
148
  }
143
149
  async function main() {
144
- const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
145
- .options({
146
- 'outFile': {
150
+ const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
151
+ outFile: {
147
152
  type: 'string',
148
153
  default: 'textures.ts',
149
154
  alias: 'o',
150
155
  describe: 'Directory to generate the files into',
151
156
  },
152
- 'assetDir': {
157
+ assetDir: {
153
158
  type: 'string',
154
159
  default: '.',
155
160
  alias: 'a',
156
161
  describe: 'Asset directory where all the PNGs are located',
157
162
  },
158
- 'outSpritesheet': {
163
+ outSpritesheet: {
159
164
  type: 'string',
160
165
  required: false,
161
166
  alias: 's',
162
167
  describe: 'Path to the generated spritesheet',
163
168
  },
164
- 'spritesheetExclude': {
169
+ spritesheetExclude: {
165
170
  type: 'string',
166
171
  array: true,
167
172
  required: false,
168
173
  alias: 'x',
169
174
  describe: 'Exclude certain paths from the spritesheet',
170
175
  },
171
- })
172
- .argv;
176
+ }).argv;
173
177
  const texturesRoot = argv.assetDir;
174
178
  const generatedTs = argv.outFile;
175
179
  try {
176
- await fs_1.promises.rm(generatedTs, { 'recursive': true });
180
+ await fs_1.promises.rm(generatedTs, { recursive: true });
177
181
  }
178
182
  catch (e) { }
179
183
  const files = await (0, utils_1.allFiles)(texturesRoot);
180
- const pngs = files.filter(file => (0, path_1.extname)(file) === '.png');
184
+ const pngs = files.filter((file) => (0, path_1.extname)(file) === '.png');
181
185
  const imports = [];
182
186
  const tree = await (0, tree_1.generateTree)(argv.assetDir, pngs);
183
187
  for (const png of pngs) {
@@ -210,7 +214,8 @@ async function main() {
210
214
  size: number;
211
215
  spriteData: SpriteData | null;
212
216
  }\n\n`;
213
- generatedFileContent += 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
217
+ generatedFileContent +=
218
+ 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
214
219
  generatedFileContent += '\n\n';
215
220
  generatedFileContent += generateExpandFunction();
216
221
  generatedFileContent += generateResolveFunction();
@@ -11,41 +11,40 @@ const helpers_1 = require("yargs/helpers");
11
11
  const path_1 = require("path");
12
12
  const audiosprite_1 = __importDefault(require("audiosprite"));
13
13
  async function main() {
14
- const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
15
- .options({
16
- 'outFile': {
14
+ const argv = await (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv)).options({
15
+ outFile: {
17
16
  type: 'string',
18
17
  default: '.',
19
18
  alias: 'o',
20
19
  describe: 'Directory to generate the files into',
21
20
  },
22
- 'assetDir': {
21
+ assetDir: {
23
22
  type: 'string',
24
23
  default: '.',
25
24
  alias: 'a',
26
25
  describe: 'Asset directory where all the PNGs are located',
27
26
  },
28
- 'wav': {
27
+ wav: {
29
28
  type: 'boolean',
30
29
  default: false,
31
30
  describe: 'Include .wav files',
32
31
  },
33
- 'ogg': {
32
+ ogg: {
34
33
  type: 'boolean',
35
34
  default: true,
36
35
  describe: 'Include .ogg files',
37
36
  },
38
- 'mp3': {
37
+ mp3: {
39
38
  type: 'boolean',
40
39
  default: true,
41
40
  describe: 'Include .mp3 files',
42
41
  },
43
- 'outSpritesheet': {
42
+ outSpritesheet: {
44
43
  type: 'string',
45
44
  required: false,
46
45
  describe: 'Path to the exported spritesheet (without the extension)',
47
46
  },
48
- 'spritesheetExcludeCategory': {
47
+ spritesheetExcludeCategory: {
49
48
  type: 'string',
50
49
  array: true,
51
50
  required: false,
@@ -53,8 +52,7 @@ async function main() {
53
52
  default: [],
54
53
  describe: 'Exclude certain categories from the spritesheet',
55
54
  },
56
- })
57
- .argv;
55
+ }).argv;
58
56
  const extensions = [];
59
57
  if (argv.mp3)
60
58
  extensions.push('.mp3');
@@ -64,12 +62,13 @@ async function main() {
64
62
  extensions.push('.wav');
65
63
  const generatedTs = argv.outFile;
66
64
  try {
67
- await fs_1.promises.rm(generatedTs, { 'recursive': true });
65
+ await fs_1.promises.rm(generatedTs, { recursive: true });
68
66
  }
69
67
  catch (e) { }
70
68
  const files = await (0, utils_1.allFiles)(argv.assetDir);
71
- const sounds = files.filter(file => extensions.indexOf((0, path_1.extname)(file)) >= 0)
72
- .filter(file => (0, path_1.basename)(file, (0, path_1.extname)(file)) !== 'sprites');
69
+ const sounds = files
70
+ .filter((file) => extensions.indexOf((0, path_1.extname)(file)) >= 0)
71
+ .filter((file) => (0, path_1.basename)(file, (0, path_1.extname)(file)) !== 'sprites');
73
72
  const defs = new Map();
74
73
  for (const sound of sounds) {
75
74
  const category = (0, utils_1.categoryPath)(argv.assetDir, sound).join('_');
@@ -100,9 +99,9 @@ async function main() {
100
99
  for (const filenameWithoutExt of defs.get(category).keys()) {
101
100
  const fileSet = defs.get(category).get(filenameWithoutExt);
102
101
  const files = Array.from(fileSet);
103
- const pickedFile = files.find(file => (0, path_1.extname)(file) === '.wav') ||
104
- files.find(file => (0, path_1.extname)(file) === '.ogg') ||
105
- files.find(file => (0, path_1.extname)(file) === '.mp3');
102
+ const pickedFile = files.find((file) => (0, path_1.extname)(file) === '.wav') ||
103
+ files.find((file) => (0, path_1.extname)(file) === '.ogg') ||
104
+ files.find((file) => (0, path_1.extname)(file) === '.mp3');
106
105
  if (!pickedFile) {
107
106
  throw new Error('Unable to pick file for sprite in ' + category);
108
107
  }
@@ -113,7 +112,7 @@ async function main() {
113
112
  const options = {
114
113
  output: argv.outSpritesheet,
115
114
  format: 'howler',
116
- export: extensions.map(ext => ext.slice(1)).join(','),
115
+ export: extensions.map((ext) => ext.slice(1)).join(','),
117
116
  };
118
117
  (0, audiosprite_1.default)(Array.from(spriteSounds.values()), options, (err, res) => {
119
118
  if (err) {
@@ -140,7 +139,7 @@ async function main() {
140
139
  definitions.push(soundDefinition);
141
140
  if (argv.outSpritesheet) {
142
141
  const modifiedSpritesheetJson = { ...spritesheetJson };
143
- modifiedSpritesheetJson.urls = extensions.map(ext => argv.outSpritesheet + ext);
142
+ modifiedSpritesheetJson.urls = extensions.map((ext) => argv.outSpritesheet + ext);
144
143
  let spriteSheetFunc = 'export function sound_spritesheet() {\n';
145
144
  spriteSheetFunc += ' return {\n';
146
145
  spriteSheetFunc += ' urls: [\n';
package/lib/tree.js CHANGED
@@ -13,7 +13,7 @@ async function generateTree(dir, files) {
13
13
  }
14
14
  subtree = subtree.get(category);
15
15
  }
16
- subtree.set((0, path_1.basename)(file).replace('.file', ''), file);
16
+ subtree.set((0, path_1.basename)(file), file);
17
17
  }
18
18
  return tree;
19
19
  }
package/lib/utils.js CHANGED
@@ -26,11 +26,14 @@ function sanitize(string) {
26
26
  }
27
27
  exports.sanitize = sanitize;
28
28
  function camelize(str) {
29
- return str.split(/[^a-z0-9]/gi).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
29
+ return str
30
+ .split(/[^a-z0-9]/gi)
31
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
32
+ .join('');
30
33
  }
31
34
  exports.camelize = camelize;
32
35
  function lowerCamelize(str) {
33
- const camelized = module.exports.camelize(str);
36
+ const camelized = camelize(str);
34
37
  return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
35
38
  }
36
39
  exports.lowerCamelize = lowerCamelize;
@@ -39,7 +42,7 @@ function categoryPath(assetDir, filepath) {
39
42
  const trimmedDir = (0, path_1.relative)(assetDir, dir);
40
43
  return trimmedDir
41
44
  .split(/[\/\\]/g)
42
- .map(component => lowerCamelize(component))
43
- .filter(component => component.length > 0);
45
+ .map((component) => lowerCamelize(component))
46
+ .filter((component) => component.length > 0);
44
47
  }
45
48
  exports.categoryPath = categoryPath;
package/package.json CHANGED
@@ -1,33 +1,44 @@
1
1
  {
2
- "name": "@remvst/asset-catalog",
3
- "version": "1.4.0",
4
- "description": "",
5
- "bin": {
6
- "generate-image-catalog": "./lib/generate-image-catalog.js",
7
- "generate-sound-catalog": "./lib/generate-sound-catalog.js"
8
- },
9
- "scripts": {
10
- "build": "rm -rf lib && tsc && chmod +x lib/generate-image-catalog.js lib/generate-sound-catalog.js",
11
- "test:images": "ts-node src/generate-image-catalog.ts --assetDir=./testData --outFile=testOut/images.ts --outSpritesheet=testOut/spritesheet.png",
12
- "test:sounds": "ts-node src/generate-sound-catalog.ts --assetDir=./testData/sounds --outFile=testOut/sounds.ts --outSpritesheet=testData/sounds/sprites --spritesheetExcludeCategory=jump",
13
- "test": "npm run test:images && npm run test:sounds",
14
- "prepublishOnly": "npm i && npm run build"
15
- },
16
- "author": "Rémi Vansteelandt",
17
- "license": "UNLICENSED",
18
- "dependencies": {
19
- "audiosprite": "^0.7.2",
20
- "bin-pack": "^1.0.2",
21
- "canvas": "^2.11.2",
22
- "image-size": "^1.0.2",
23
- "yargs": "^17.7.2"
24
- },
25
- "devDependencies": {
26
- "@types/audiosprite": "^0.7.3",
27
- "@types/bin-pack": "^1.0.3",
28
- "@types/node": "^18.11.5",
29
- "@types/yargs": "^17.0.32",
30
- "ts-node": "^10.9.1",
31
- "typescript": "^5.2.2"
32
- }
2
+ "name": "@remvst/asset-catalog",
3
+ "version": "1.6.0",
4
+ "description": "",
5
+ "bin": {
6
+ "generate-image-catalog": "./lib/generate-image-catalog.js",
7
+ "generate-sound-catalog": "./lib/generate-sound-catalog.js",
8
+ "generate-file-catalog": "./lib/generate-file-catalog.js"
9
+ },
10
+ "scripts": {
11
+ "build": "rm -rf lib && tsc && chmod +x lib/generate-image-catalog.js lib/generate-sound-catalog.js",
12
+ "test:images": "ts-node src/generate-image-catalog.ts --assetDir=./testData --outFile=testOut/images.ts --outSpritesheet=testOut/spritesheet.png",
13
+ "test:sounds": "ts-node src/generate-sound-catalog.ts --assetDir=./testData/sounds --outFile=testOut/sounds.ts --outSpritesheet=testData/sounds/sprites --spritesheetExcludeCategory=jump",
14
+ "test:files": "ts-node src/generate-file-catalog.ts --dir=./testData --outFile=testOut/files.ts",
15
+ "prettier:check": "prettier --check .",
16
+ "prettier:write": "prettier --write .",
17
+ "test": "npm run prettier:check && npm run test:images && npm run test:sounds && npm run test:files",
18
+ "prepublishOnly": "npm i && npm run build",
19
+ "prepare": "husky"
20
+ },
21
+ "author": "Rémi Vansteelandt",
22
+ "license": "UNLICENSED",
23
+ "dependencies": {
24
+ "audiosprite": "^0.7.2",
25
+ "bin-pack": "^1.0.2",
26
+ "canvas": "^2.11.2",
27
+ "image-size": "^1.0.2",
28
+ "yargs": "^17.7.2"
29
+ },
30
+ "devDependencies": {
31
+ "@types/audiosprite": "^0.7.3",
32
+ "@types/bin-pack": "^1.0.3",
33
+ "@types/node": "^18.11.5",
34
+ "@types/yargs": "^17.0.32",
35
+ "husky": "^9.1.7",
36
+ "lint-staged": "^16.3.0",
37
+ "prettier": "^3.8.1",
38
+ "ts-node": "^10.9.1",
39
+ "typescript": "^5.2.2"
40
+ },
41
+ "lint-staged": {
42
+ "*.{js,ts,json,md}": "prettier --write"
43
+ }
33
44
  }
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { promises as fs } from 'fs';
4
+ import { allFiles, lowerCamelize, sanitize } from './utils';
5
+ import { basename, dirname, extname, relative, resolve } from 'path';
6
+ import yargs from 'yargs/yargs';
7
+ import { hideBin } from 'yargs/helpers';
8
+ import { generateTree, Tree } from './tree';
9
+
10
+ function importName(assetDir: string, file: string): string {
11
+ let importName = file;
12
+ importName = resolve(file);
13
+
14
+ const prefix = resolve(assetDir);
15
+ const prefixIndex = importName.indexOf(prefix);
16
+ if (prefixIndex >= 0) {
17
+ importName = importName.slice(prefixIndex + prefix.length);
18
+ }
19
+
20
+ importName = sanitize(importName);
21
+ return importName;
22
+ }
23
+
24
+ function generatedTemplateInterface(tree: Tree, indent: string = ''): string {
25
+ let generated = '{\n';
26
+ for (const [subname, item] of tree.entries()) {
27
+ if (item instanceof Map) {
28
+ const generatedSub = generatedTemplateInterface(item, indent + ' ');
29
+ generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub}`;
30
+ } else {
31
+ const name = basename(subname);
32
+ generated += indent + ` ${lowerCamelize(name)}: T,\n`;
33
+ }
34
+ }
35
+ generated += indent + '}\n';
36
+ return generated;
37
+ }
38
+
39
+ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree): Promise<string> {
40
+ async function rec(tree: Tree, indent: string = '') {
41
+ let generated = '{\n';
42
+ for (const [subname, item] of tree.entries()) {
43
+ if (item instanceof Map) {
44
+ const generatedSub = await rec(item, indent + ' ');
45
+ generated += indent + ` ${lowerCamelize(subname)}: ${generatedSub},\n`;
46
+ } else {
47
+ const stats = await fs.stat(item);
48
+ const name = basename(subname);
49
+
50
+ generated +=
51
+ indent +
52
+ ` ${lowerCamelize(name)}: createItem(expand(
53
+ ${importName(assetDir, item)},
54
+ ${stats.size},
55
+ '${item}',
56
+ )),\n`;
57
+ }
58
+ }
59
+ generated += indent + '}';
60
+ return generated;
61
+ }
62
+
63
+ let generated = '';
64
+ generated +=
65
+ 'export function createFileCatalog<T>(createItem: (opts: CreateItemOptions) => T): FileCatalog<T> {\n';
66
+ generated += ` return ${await rec(tree, ' ')};\n`;
67
+ generated += '}\n';
68
+ return generated;
69
+ }
70
+
71
+ function generateExpandFunction() {
72
+ let generated = '';
73
+ generated +=
74
+ 'function expand(path: string, size: number, originalPath: string): CreateItemOptions {\n';
75
+ generated += ` return { path, size, originalPath };\n`;
76
+ generated += '}\n';
77
+ return generated;
78
+ }
79
+
80
+ function generateResolveFunction() {
81
+ let generated = '';
82
+ generated +=
83
+ 'export function resolveFromCatalog<T>(catalog: FileCatalog<T>, path: string[]): T {\n';
84
+ generated += ' let current: any = catalog;\n';
85
+ generated += ` for (const component of path) {\n`;
86
+ generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
87
+ generated += ` current = current[component];\n`;
88
+ generated += ` }\n`;
89
+ generated += ` return current as T;\n`;
90
+ generated += '}\n';
91
+ return generated;
92
+ }
93
+
94
+ async function main() {
95
+ const argv = await yargs(hideBin(process.argv)).options({
96
+ outFile: {
97
+ type: 'string',
98
+ default: 'files.ts',
99
+ alias: 'o',
100
+ describe: 'Directory to generate the files into',
101
+ },
102
+ dir: {
103
+ type: 'string',
104
+ default: '.',
105
+ alias: 'd',
106
+ describe: 'Asset directory where all the PNGs are located',
107
+ },
108
+ }).argv;
109
+
110
+ const filesRoot = argv.dir;
111
+ const generatedTs = argv.outFile;
112
+ try {
113
+ await fs.rm(generatedTs, { recursive: true });
114
+ } catch (e) {}
115
+
116
+ const files = await allFiles(filesRoot);
117
+
118
+ const imports = [];
119
+ const tree = await generateTree(argv.dir, files);
120
+
121
+ for (const file of files) {
122
+ const importPath = relative(dirname(argv.outFile), file).replace(/\\/g, '/');
123
+ imports.push(`import ${importName(argv.dir, file)} from './${importPath}';`);
124
+ }
125
+
126
+ let generatedFileContent = '';
127
+ generatedFileContent += imports.join('\n');
128
+ generatedFileContent += '\n\n';
129
+ generatedFileContent += `export interface CreateItemOptions {
130
+ path: string;
131
+ size: number;
132
+ originalPath: string;
133
+ }\n\n`;
134
+ generatedFileContent += 'export type FileCatalog<T> = ' + generatedTemplateInterface(tree);
135
+ generatedFileContent += '\n';
136
+ generatedFileContent += generateExpandFunction();
137
+ generatedFileContent += '\n';
138
+ generatedFileContent += generateResolveFunction();
139
+ generatedFileContent += '\n';
140
+ generatedFileContent += await generatedCreateCatalogFunction(argv.dir, tree);
141
+
142
+ await fs.writeFile(generatedTs, generatedFileContent);
143
+ }
144
+
145
+ main();
@@ -10,7 +10,7 @@ import { Tree, generateTree } from './tree';
10
10
  import pack from 'bin-pack';
11
11
  import { createCanvas, loadImage } from 'canvas';
12
12
 
13
- type Rectangle = {x: number, y: number, width: number, height: number};
13
+ type Rectangle = { x: number; y: number; width: number; height: number };
14
14
  type SpritesheetResult = Map<string, Rectangle>;
15
15
 
16
16
  function importName(assetDir: string, png: string): string {
@@ -42,7 +42,11 @@ function generatedTemplateInterface(tree: Tree, name: string, indent: string = '
42
42
  return generated;
43
43
  }
44
44
 
45
- async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spritesheet: SpritesheetResult | null): Promise<string> {
45
+ async function generatedCreateCatalogFunction(
46
+ assetDir: string,
47
+ tree: Tree,
48
+ spritesheet: SpritesheetResult | null,
49
+ ): Promise<string> {
46
50
  async function rec(tree: Tree, indent: string = '') {
47
51
  let generated = '{\n';
48
52
  for (const [subname, item] of tree.entries()) {
@@ -55,11 +59,13 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
55
59
  const withoutExt = basename(subname, extname(subname));
56
60
  const spriteData = spritesheet?.get(resolve(item)) || null;
57
61
  let spriteDataArr: string | null = null;
58
- if (spriteData) {
62
+ if (spriteData) {
59
63
  spriteDataArr = `expandSpriteData(SpriteSheetPng, ${spriteData.x}, ${spriteData.y}, ${spriteData.width}, ${spriteData.height})`;
60
64
  }
61
65
 
62
- generated += indent + ` ${lowerCamelize(withoutExt)}: createItem(expand(
66
+ generated +=
67
+ indent +
68
+ ` ${lowerCamelize(withoutExt)}: createItem(expand(
63
69
  ${importName(assetDir, item)},
64
70
  ${dimensions.width},
65
71
  ${dimensions.height},
@@ -73,7 +79,8 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
73
79
  }
74
80
 
75
81
  let generated = '\n';
76
- generated += 'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
82
+ generated +=
83
+ 'export function createTextureCatalog<T>(createItem: (opts: CreateItemOptions) => T): TextureCatalog<T> {\n';
77
84
  generated += ` return ${await rec(tree, ' ')};\n`;
78
85
  generated += '}\n';
79
86
  return generated;
@@ -81,10 +88,12 @@ async function generatedCreateCatalogFunction(assetDir: string, tree: Tree, spri
81
88
 
82
89
  function generateExpandFunction() {
83
90
  let generated = '\n';
84
- generated += 'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
91
+ generated +=
92
+ 'function expandSpriteData(sheet: string, x: number, y: number, width: number, height: number): SpriteData {\n';
85
93
  generated += ` return { sheet, frame: { x, y, width, height } };\n`;
86
94
  generated += '}\n\n';
87
- generated += 'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
95
+ generated +=
96
+ 'function expand(path: string, width: number, height: number, size: number, spriteData: SpriteData | null = null): CreateItemOptions {\n';
88
97
  generated += ` return { path, width, height, size, spriteData };\n`;
89
98
  generated += '}\n';
90
99
  return generated;
@@ -92,7 +101,8 @@ function generateExpandFunction() {
92
101
 
93
102
  function generateResolveFunction() {
94
103
  let generated = '\n';
95
- generated += 'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
104
+ generated +=
105
+ 'export function resolveFromCatalog<T>(catalog: TextureCatalog<T>, path: string[]): T {\n';
96
106
  generated += ' let current = catalog;\n';
97
107
  generated += ` for (const component of path) {\n`;
98
108
  generated += ` if (!(component in current)) throw new Error('Unresolvable catalog path: ' + path.join('.'));\n`;
@@ -103,8 +113,12 @@ function generateResolveFunction() {
103
113
  return generated;
104
114
  }
105
115
 
106
- async function createSpritesheet(tree: Tree, outFile: string, excludes: string[]): Promise<SpritesheetResult> {
107
- const bins: (pack.Bin & {path: string})[] = [];
116
+ async function createSpritesheet(
117
+ tree: Tree,
118
+ outFile: string,
119
+ excludes: string[],
120
+ ): Promise<SpritesheetResult> {
121
+ const bins: (pack.Bin & { path: string })[] = [];
108
122
 
109
123
  const padding = 1;
110
124
 
@@ -141,10 +155,10 @@ async function createSpritesheet(tree: Tree, outFile: string, excludes: string[]
141
155
  ctx.drawImage(image, item.x + padding, item.y + padding);
142
156
  }
143
157
 
144
- const buffer = canvas.toBuffer("image/png");
158
+ const buffer = canvas.toBuffer('image/png');
145
159
  await fs.writeFile(outFile, buffer);
146
160
 
147
- const resultMap = new Map<string, {x: number, y: number, width: number, height: number}>();
161
+ const resultMap = new Map<string, { x: number; y: number; width: number; height: number }>();
148
162
  for (const item of packed.items) {
149
163
  resultMap.set(item.item.path, {
150
164
  x: item.x + padding,
@@ -157,44 +171,42 @@ async function createSpritesheet(tree: Tree, outFile: string, excludes: string[]
157
171
  }
158
172
 
159
173
  async function main() {
160
- const argv = await yargs(hideBin(process.argv))
161
- .options({
162
- 'outFile': {
163
- type: 'string',
164
- default: 'textures.ts',
165
- alias: 'o',
166
- describe: 'Directory to generate the files into',
167
- },
168
- 'assetDir': {
169
- type: 'string',
170
- default: '.',
171
- alias: 'a',
172
- describe: 'Asset directory where all the PNGs are located',
173
- },
174
- 'outSpritesheet': {
175
- type: 'string',
176
- required: false,
177
- alias: 's',
178
- describe: 'Path to the generated spritesheet',
179
- },
180
- 'spritesheetExclude': {
181
- type: 'string',
182
- array: true,
183
- required: false,
184
- alias: 'x',
185
- describe: 'Exclude certain paths from the spritesheet',
186
- },
187
- })
188
- .argv;
174
+ const argv = await yargs(hideBin(process.argv)).options({
175
+ outFile: {
176
+ type: 'string',
177
+ default: 'textures.ts',
178
+ alias: 'o',
179
+ describe: 'Directory to generate the files into',
180
+ },
181
+ assetDir: {
182
+ type: 'string',
183
+ default: '.',
184
+ alias: 'a',
185
+ describe: 'Asset directory where all the PNGs are located',
186
+ },
187
+ outSpritesheet: {
188
+ type: 'string',
189
+ required: false,
190
+ alias: 's',
191
+ describe: 'Path to the generated spritesheet',
192
+ },
193
+ spritesheetExclude: {
194
+ type: 'string',
195
+ array: true,
196
+ required: false,
197
+ alias: 'x',
198
+ describe: 'Exclude certain paths from the spritesheet',
199
+ },
200
+ }).argv;
189
201
 
190
202
  const texturesRoot = argv.assetDir;
191
203
  const generatedTs = argv.outFile;
192
204
  try {
193
- await fs.rm(generatedTs, { 'recursive': true });
205
+ await fs.rm(generatedTs, { recursive: true });
194
206
  } catch (e) {}
195
207
 
196
208
  const files = await allFiles(texturesRoot);
197
- const pngs = files.filter(file => extname(file) === '.png');
209
+ const pngs = files.filter((file) => extname(file) === '.png');
198
210
 
199
211
  const imports = [];
200
212
  const tree = await generateTree(argv.assetDir, pngs);
@@ -206,9 +218,16 @@ async function main() {
206
218
 
207
219
  let spritesheet: SpritesheetResult | null = null;
208
220
  if (argv.outSpritesheet) {
209
- spritesheet = await createSpritesheet(tree, argv.outSpritesheet, argv.spritesheetExclude || []);
221
+ spritesheet = await createSpritesheet(
222
+ tree,
223
+ argv.outSpritesheet,
224
+ argv.spritesheetExclude || [],
225
+ );
210
226
 
211
- const importPath = relative(dirname(argv.outFile), resolve(argv.outSpritesheet)).replace(/\\/g, '/');
227
+ const importPath = relative(dirname(argv.outFile), resolve(argv.outSpritesheet)).replace(
228
+ /\\/g,
229
+ '/',
230
+ );
212
231
  imports.push(`import SpriteSheetPng from './${importPath}';`);
213
232
  }
214
233
 
@@ -232,7 +251,8 @@ async function main() {
232
251
  size: number;
233
252
  spriteData: SpriteData | null;
234
253
  }\n\n`;
235
- generatedFileContent += 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
254
+ generatedFileContent +=
255
+ 'export type TextureCatalog<T> = ' + generatedTemplateInterface(tree, 'TextureCatalog');
236
256
  generatedFileContent += '\n\n';
237
257
  generatedFileContent += generateExpandFunction();
238
258
  generatedFileContent += generateResolveFunction();
@@ -8,50 +8,48 @@ import { dirname, extname, basename, relative, parse } from 'path';
8
8
  import audiosprite from 'audiosprite';
9
9
 
10
10
  async function main() {
11
- const argv = await yargs(hideBin(process.argv))
12
- .options({
13
- 'outFile': {
14
- type: 'string',
15
- default: '.',
16
- alias: 'o',
17
- describe: 'Directory to generate the files into',
18
- },
19
- 'assetDir': {
20
- type: 'string',
21
- default: '.',
22
- alias: 'a',
23
- describe: 'Asset directory where all the PNGs are located',
24
- },
25
- 'wav': {
26
- type: 'boolean',
27
- default: false,
28
- describe: 'Include .wav files',
29
- },
30
- 'ogg': {
31
- type: 'boolean',
32
- default: true,
33
- describe: 'Include .ogg files',
34
- },
35
- 'mp3': {
36
- type: 'boolean',
37
- default: true,
38
- describe: 'Include .mp3 files',
39
- },
40
- 'outSpritesheet': {
41
- type: 'string',
42
- required: false,
43
- describe: 'Path to the exported spritesheet (without the extension)',
44
- },
45
- 'spritesheetExcludeCategory': {
46
- type: 'string',
47
- array: true,
48
- required: false,
49
- alias: 'x',
50
- default: [] as string[],
51
- describe: 'Exclude certain categories from the spritesheet',
52
- },
53
- })
54
- .argv;
11
+ const argv = await yargs(hideBin(process.argv)).options({
12
+ outFile: {
13
+ type: 'string',
14
+ default: '.',
15
+ alias: 'o',
16
+ describe: 'Directory to generate the files into',
17
+ },
18
+ assetDir: {
19
+ type: 'string',
20
+ default: '.',
21
+ alias: 'a',
22
+ describe: 'Asset directory where all the PNGs are located',
23
+ },
24
+ wav: {
25
+ type: 'boolean',
26
+ default: false,
27
+ describe: 'Include .wav files',
28
+ },
29
+ ogg: {
30
+ type: 'boolean',
31
+ default: true,
32
+ describe: 'Include .ogg files',
33
+ },
34
+ mp3: {
35
+ type: 'boolean',
36
+ default: true,
37
+ describe: 'Include .mp3 files',
38
+ },
39
+ outSpritesheet: {
40
+ type: 'string',
41
+ required: false,
42
+ describe: 'Path to the exported spritesheet (without the extension)',
43
+ },
44
+ spritesheetExcludeCategory: {
45
+ type: 'string',
46
+ array: true,
47
+ required: false,
48
+ alias: 'x',
49
+ default: [] as string[],
50
+ describe: 'Exclude certain categories from the spritesheet',
51
+ },
52
+ }).argv;
55
53
 
56
54
  const extensions: string[] = [];
57
55
  if (argv.mp3) extensions.push('.mp3');
@@ -60,13 +58,13 @@ async function main() {
60
58
 
61
59
  const generatedTs = argv.outFile;
62
60
  try {
63
- await fs.rm(generatedTs, { 'recursive': true });
61
+ await fs.rm(generatedTs, { recursive: true });
64
62
  } catch (e) {}
65
63
 
66
-
67
64
  const files = await allFiles(argv.assetDir);
68
- const sounds = files.filter(file => extensions.indexOf(extname(file)) >= 0)
69
- .filter(file => basename(file, extname(file)) !== 'sprites');
65
+ const sounds = files
66
+ .filter((file) => extensions.indexOf(extname(file)) >= 0)
67
+ .filter((file) => basename(file, extname(file)) !== 'sprites');
70
68
 
71
69
  const defs = new Map<string, Map<string, Set<string>>>();
72
70
 
@@ -106,9 +104,10 @@ async function main() {
106
104
  const fileSet = defs.get(category)!.get(filenameWithoutExt)!;
107
105
 
108
106
  const files = Array.from(fileSet);
109
- const pickedFile = files.find(file => extname(file) === '.wav') ||
110
- files.find(file => extname(file) === '.ogg') ||
111
- files.find(file => extname(file) === '.mp3');
107
+ const pickedFile =
108
+ files.find((file) => extname(file) === '.wav') ||
109
+ files.find((file) => extname(file) === '.ogg') ||
110
+ files.find((file) => extname(file) === '.mp3');
112
111
  if (!pickedFile) {
113
112
  throw new Error('Unable to pick file for sprite in ' + category);
114
113
  }
@@ -120,7 +119,7 @@ async function main() {
120
119
  const options: audiosprite.Option = {
121
120
  output: argv.outSpritesheet,
122
121
  format: 'howler',
123
- export: extensions.map(ext => ext.slice(1)).join(','),
122
+ export: extensions.map((ext) => ext.slice(1)).join(','),
124
123
  };
125
124
 
126
125
  audiosprite(Array.from(spriteSounds.values()), options, (err, res) => {
@@ -150,18 +149,21 @@ async function main() {
150
149
 
151
150
  if (argv.outSpritesheet) {
152
151
  const modifiedSpritesheetJson = { ...spritesheetJson };
153
- modifiedSpritesheetJson.urls = extensions.map(ext => argv.outSpritesheet + ext);
152
+ modifiedSpritesheetJson.urls = extensions.map((ext) => argv.outSpritesheet + ext);
154
153
 
155
154
  let spriteSheetFunc = 'export function sound_spritesheet() {\n';
156
155
  spriteSheetFunc += ' return {\n';
157
156
  spriteSheetFunc += ' urls: [\n';
158
157
 
159
-
160
158
  const idealOrder = ['.ogg', '.mp3', '.wav'];
161
- const sortedExtensions = extensions.sort((a, b) => idealOrder.indexOf(a) - idealOrder.indexOf(b));
159
+ const sortedExtensions = extensions.sort(
160
+ (a, b) => idealOrder.indexOf(a) - idealOrder.indexOf(b),
161
+ );
162
162
  for (const extension of sortedExtensions) {
163
163
  const importName = 'sprites_' + extension.slice(1);
164
- imports.push(`import ${importName} from '${relative(dirname(argv.outFile), argv.outSpritesheet + extension).replace(/\\/g, '/')}';`);
164
+ imports.push(
165
+ `import ${importName} from '${relative(dirname(argv.outFile), argv.outSpritesheet + extension).replace(/\\/g, '/')}';`,
166
+ );
165
167
 
166
168
  spriteSheetFunc += ` ${importName},\n`;
167
169
  }
@@ -201,7 +203,9 @@ async function main() {
201
203
  let totalFileSize = 0;
202
204
  for (const soundFile of files) {
203
205
  const importName = sanitize(soundFile);
204
- imports.push(`import ${importName} from '${relative(dirname(argv.outFile), soundFile).replace(/\\/g, '/')}';`);
206
+ imports.push(
207
+ `import ${importName} from '${relative(dirname(argv.outFile), soundFile).replace(/\\/g, '/')}';`,
208
+ );
205
209
  func += ` ${importName},\n`;
206
210
  const stats = await fs.stat(soundFile);
207
211
  totalFileSize += stats.size;
package/src/tree.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { basename } from "path";
2
- import { categoryPath } from "./utils";
1
+ import { basename } from 'path';
2
+ import { categoryPath } from './utils';
3
3
 
4
4
  export type Tree = Map<string, TreeOrPath>;
5
5
  export type TreeOrPath = Tree | string;
@@ -16,7 +16,7 @@ export async function generateTree(dir: string, files: string[]): Promise<Tree>
16
16
  subtree = subtree.get(category);
17
17
  }
18
18
 
19
- subtree.set(basename(file).replace('.file', ''), file);
19
+ subtree.set(basename(file), file);
20
20
  }
21
21
 
22
22
  return tree;
package/src/utils.ts CHANGED
@@ -26,11 +26,14 @@ export function sanitize(string: string): string {
26
26
  }
27
27
 
28
28
  export function camelize(str: string) {
29
- return str.split(/[^a-z0-9]/gi).map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('');
29
+ return str
30
+ .split(/[^a-z0-9]/gi)
31
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
32
+ .join('');
30
33
  }
31
34
 
32
35
  export function lowerCamelize(str: string): string {
33
- const camelized = module.exports.camelize(str);
36
+ const camelized = camelize(str);
34
37
  return camelized.slice(0, 1).toLowerCase() + camelized.slice(1);
35
38
  }
36
39
 
@@ -39,6 +42,6 @@ export function categoryPath(assetDir: string, filepath: string): string[] {
39
42
  const trimmedDir = relative(assetDir, dir);
40
43
  return trimmedDir
41
44
  .split(/[\/\\]/g)
42
- .map(component => lowerCamelize(component))
43
- .filter(component => component.length > 0);
45
+ .map((component) => lowerCamelize(component))
46
+ .filter((component) => component.length > 0);
44
47
  }