@ispriter/core 2.0.1 → 2.0.3

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/dist/index.d.ts CHANGED
@@ -175,6 +175,7 @@ interface SpriterRunInput {
175
175
  css: Map<string, string>;
176
176
  images: Map<string, Buffer>;
177
177
  cssBaseDir: string;
178
+ dryRun?: boolean;
178
179
  }
179
180
  /** 打包后的精灵图条目 */
180
181
  interface PackedSprite {
@@ -201,6 +202,8 @@ interface SpriterResult {
201
202
  height: number;
202
203
  }>;
203
204
  skippedImages: string[];
205
+ /** Only present when dryRun=true */
206
+ dryRunReport?: string;
204
207
  }
205
208
 
206
209
  interface PackOutput<T> {
@@ -270,6 +273,7 @@ declare class Spriter {
270
273
  /** Fix #13: 按 maxSingleSize 拆分精灵图 */
271
274
  private splitByMaxSize;
272
275
  private buildManifest;
276
+ private buildDryRunReport;
273
277
  }
274
278
 
275
279
  interface PluginRunOptions {
package/dist/index.js CHANGED
@@ -37,10 +37,12 @@ var SpriterConfigSchema = z.object({
37
37
  unit: z.enum(["px", "rem"]).default("px"),
38
38
  remBase: z.number().positive().default(16)
39
39
  }),
40
- groups: z.array(z.object({
41
- name: z.string(),
42
- images: z.union([z.string(), z.array(z.string())])
43
- })).optional()
40
+ groups: z.array(
41
+ z.object({
42
+ name: z.string(),
43
+ images: z.union([z.string(), z.array(z.string())])
44
+ })
45
+ ).optional()
44
46
  });
45
47
  function normalizeConfig(input) {
46
48
  if (typeof input === "string") {
@@ -66,13 +68,11 @@ function parseConfig(raw) {
66
68
  validateNoTraversal(result.data.output.imageDist, "output.imageDist");
67
69
  return result.data;
68
70
  }
69
- function validateNoTraversal(pathStr, fieldName) {
70
- if (pathStr.includes("..")) {
71
- throw new IspriterError(
72
- `Path traversal detected in ${fieldName}: "${pathStr}"`,
73
- "CONFIG_INVALID",
74
- { field: fieldName }
75
- );
71
+ function validateNoTraversal(pathStr, _fieldName) {
72
+ if (pathStr.includes("\0")) {
73
+ throw new IspriterError(`Invalid path in ${_fieldName}: null byte detected`, "CONFIG_INVALID", {
74
+ field: _fieldName
75
+ });
76
76
  }
77
77
  }
78
78
 
@@ -103,7 +103,9 @@ function analyseBackground(value) {
103
103
  }
104
104
  if (result.imageUrl) {
105
105
  const afterUrl = value.replace(/url\([^)]*\)/, "").trim();
106
- const posTokens = afterUrl.split(/\s+/).filter((t) => t && !["no-repeat", "repeat", "repeat-x", "repeat-y", "repeat-space"].includes(t)).filter((t) => !t.startsWith("/") && !t.startsWith("#"));
106
+ const posTokens = afterUrl.split(/\s+/).filter(
107
+ (t) => t && !["no-repeat", "repeat", "repeat-x", "repeat-y", "repeat-space"].includes(t)
108
+ ).filter((t) => !t.startsWith("/") && !t.startsWith("#"));
107
109
  if (posTokens.length >= 1) {
108
110
  result.positionX = parsePositionValue(posTokens[0]);
109
111
  }
@@ -128,8 +130,10 @@ function shouldSkip(parsed) {
128
130
  }
129
131
  if (parsed.positionX === "100%" || parsed.positionY === "100%") return true;
130
132
  if (parsed.positionX === "center" || parsed.positionY === "center") return true;
131
- if (typeof parsed.positionX === "string" && parsed.positionX.endsWith("%") && parsed.positionX !== "100%") return true;
132
- if (typeof parsed.positionY === "string" && parsed.positionY.endsWith("%") && parsed.positionY !== "100%") return true;
133
+ if (typeof parsed.positionX === "string" && parsed.positionX.endsWith("%") && parsed.positionX !== "100%")
134
+ return true;
135
+ if (typeof parsed.positionY === "string" && parsed.positionY.endsWith("%") && parsed.positionY !== "100%")
136
+ return true;
133
137
  return false;
134
138
  }
135
139
  function cleanImageUrl(url) {
@@ -173,7 +177,9 @@ async function extractBackgrounds(cssContents) {
173
177
  processRules(root, filename, false, rules);
174
178
  root.walkAtRules(/keyframes/i, (atRule) => {
175
179
  if (atRule.nodes) {
176
- const kfRoot = postcss2.parse(atRule.toString().replace(/@[^{]+\{/, "").replace(/\}$/, ""));
180
+ const kfRoot = postcss2.parse(
181
+ atRule.toString().replace(/@[^{]+\{/, "").replace(/\}$/, "")
182
+ );
177
183
  processRules(kfRoot, filename, true, rules);
178
184
  }
179
185
  });
@@ -328,14 +334,19 @@ async function emitCSS(cssContents, packedSprites, config, retinaInfo) {
328
334
  );
329
335
  const minified = cleaner.minify(output);
330
336
  if (minified.errors.length > 0) {
331
- throw new IspriterError(`CSS compression failed: ${minified.errors.join(", ")}`, "OUTPUT_ERROR");
337
+ throw new IspriterError(
338
+ `CSS compression failed: ${minified.errors.join(", ")}`,
339
+ "OUTPUT_ERROR"
340
+ );
332
341
  }
333
342
  output = minified.styles;
334
343
  }
335
344
  results.set(filename, output);
336
345
  } catch (e) {
337
346
  if (e instanceof IspriterError) throw e;
338
- throw new IspriterError(`Failed to emit CSS for ${filename}`, "OUTPUT_ERROR", { file: filename });
347
+ throw new IspriterError(`Failed to emit CSS for ${filename}`, "OUTPUT_ERROR", {
348
+ file: filename
349
+ });
339
350
  }
340
351
  }
341
352
  if (config.output.combine && results.size > 1) {
@@ -368,13 +379,15 @@ async function generateSprites(packedSprites, config) {
368
379
  background: { r: 0, g: 0, b: 0, alpha: 0 }
369
380
  }
370
381
  });
371
- const composites = await Promise.all(items.map(async (item) => {
372
- let inputBuf = item.asset.buffer;
373
- if (item.width !== item.asset.naturalWidth || item.height !== item.asset.naturalHeight) {
374
- inputBuf = await sharp(item.asset.buffer).resize(item.width, item.height, { fit: "fill" }).toBuffer();
375
- }
376
- return { input: inputBuf, left: item.x, top: item.y };
377
- }));
382
+ const composites = await Promise.all(
383
+ items.map(async (item) => {
384
+ let inputBuf = item.asset.buffer;
385
+ if (item.width !== item.asset.naturalWidth || item.height !== item.asset.naturalHeight) {
386
+ inputBuf = await sharp(item.asset.buffer).resize(item.width, item.height, { fit: "fill" }).toBuffer();
387
+ }
388
+ return { input: inputBuf, left: item.x, top: item.y };
389
+ })
390
+ );
378
391
  let output = canvas.composite(composites);
379
392
  if (config.output.format === "webp") {
380
393
  output = output.webp({ quality: config.output.quality });
@@ -385,10 +398,14 @@ async function generateSprites(packedSprites, config) {
385
398
  results.set(sprite.spriteFile, buffer);
386
399
  } catch (e) {
387
400
  if (e instanceof IspriterError) throw e;
388
- throw new IspriterError(`Failed to generate sprite: ${sprite.spriteFile}: ${e.message}`, "OUTPUT_ERROR", {
389
- spriteFile: sprite.spriteFile,
390
- cause: e
391
- });
401
+ throw new IspriterError(
402
+ `Failed to generate sprite: ${sprite.spriteFile}: ${e.message}`,
403
+ "OUTPUT_ERROR",
404
+ {
405
+ spriteFile: sprite.spriteFile,
406
+ cause: e
407
+ }
408
+ );
392
409
  }
393
410
  }
394
411
  return results;
@@ -415,6 +432,16 @@ var Spriter = class {
415
432
  allPackedSprites.push(...packed);
416
433
  }
417
434
  const finalSprites = this.splitByMaxSize(allPackedSprites);
435
+ if (input.dryRun) {
436
+ const manifest2 = this.buildManifest(finalSprites);
437
+ return {
438
+ cssFiles: /* @__PURE__ */ new Map(),
439
+ spriteImages: /* @__PURE__ */ new Map(),
440
+ manifest: manifest2,
441
+ skippedImages: skipped,
442
+ dryRunReport: this.buildDryRunReport(finalSprites, skipped, assets.length)
443
+ };
444
+ }
418
445
  const spriteImages = await generateSprites(finalSprites, this.config);
419
446
  let retinaInfo;
420
447
  if (this.config.output.retina) {
@@ -583,6 +610,33 @@ var Spriter = class {
583
610
  }
584
611
  return manifest;
585
612
  }
613
+ buildDryRunReport(packedSprites, skipped, totalAssets) {
614
+ const lines = [];
615
+ lines.push("");
616
+ lines.push("\u{1F4CB} Dry-run report");
617
+ lines.push("\u2500".repeat(50));
618
+ lines.push(`Images found: ${totalAssets}`);
619
+ lines.push(`Images sprited: ${totalAssets - skipped.length}`);
620
+ lines.push(`Images skipped: ${skipped.length}`);
621
+ lines.push(`Sprite sheets: ${packedSprites.length}`);
622
+ lines.push("");
623
+ for (const sprite of packedSprites) {
624
+ lines.push(`\u{1F4C4} ${sprite.spriteFile} (${sprite.canvasWidth}\xD7${sprite.canvasHeight}px)`);
625
+ lines.push(` Contains ${sprite.items.length} image(s):`);
626
+ for (const item of sprite.items) {
627
+ lines.push(` \u2022 ${item.asset.url} \u2192 (${item.x}, ${item.y}) ${item.width}\xD7${item.height}`);
628
+ }
629
+ lines.push("");
630
+ }
631
+ if (skipped.length > 0) {
632
+ lines.push(`\u26A0\uFE0F Skipped (${skipped.length}):`);
633
+ for (const url of skipped) {
634
+ lines.push(` \u2022 ${url}`);
635
+ }
636
+ lines.push("");
637
+ }
638
+ return lines.join("\n");
639
+ }
586
640
  };
587
641
 
588
642
  // src/plugin-helper.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ispriter/core",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,18 +10,24 @@
10
10
  "types": "./dist/index.d.ts"
11
11
  }
12
12
  },
13
+ "scripts": {
14
+ "build": "tsup",
15
+ "dev": "tsup --watch",
16
+ "test": "vitest run"
17
+ },
13
18
  "dependencies": {
19
+ "@ispriter/shared": "workspace:*",
14
20
  "clean-css": "^5.3.0",
15
21
  "postcss": "^8.5.0",
16
22
  "postcss-import": "^16.1.1",
17
23
  "sharp": "^0.33.0",
18
- "zod": "^3.24.0",
19
- "@ispriter/shared": "2.0.1"
24
+ "zod": "^3.24.0"
20
25
  },
21
26
  "devDependencies": {
22
27
  "@types/clean-css": "^4.2.0",
23
28
  "tsup": "^8.4.0",
24
- "vitest": "^3.1.0"
29
+ "vitest": "^3.1.0",
30
+ "zod-to-json-schema": "^3.25.2"
25
31
  },
26
32
  "files": [
27
33
  "dist",
@@ -33,10 +39,5 @@
33
39
  },
34
40
  "publishConfig": {
35
41
  "access": "public"
36
- },
37
- "scripts": {
38
- "build": "tsup",
39
- "dev": "tsup --watch",
40
- "test": "vitest run"
41
42
  }
42
- }
43
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2014 iazrael.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a
6
- copy of this software and associated documentation files (the "Software"),
7
- to deal in the Software without restriction, including without limitation
8
- the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
- and/or sell copies of the Software, and to permit persons to whom the
10
- Software is furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
- DEALINGS IN THE SOFTWARE.