@openfairygui/cli 0.2.0-alpha.10 → 0.2.0-alpha.11

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/cli.mjs CHANGED
@@ -1,10 +1,178 @@
1
1
  import { createRequire } from "node:module";
2
+ import { Command } from "commander";
2
3
  import { createNodeBackendRuntime } from "@openfairygui/backend/node";
3
- import * as fs$1 from "node:fs/promises";
4
- import fs from "node:fs/promises";
5
4
  import * as path$1 from "node:path";
6
5
  import path from "node:path";
7
- import { parseArgs } from "node:util";
6
+ import { createJiti } from "jiti";
7
+ import * as fs$1 from "node:fs/promises";
8
+ import fs from "node:fs/promises";
9
+ //#region src/commands/backend-capabilities.ts
10
+ function registerBackendCapabilitiesCommand(program) {
11
+ program.command("backend-capabilities").description("Open a backend session, print runtime capabilities, then close it").argument("<project-dir>", "Project root directory").action(async (projectDir) => {
12
+ const runtime = createNodeBackendRuntime();
13
+ const opened = await runtime.openSession({ projectPath: path.resolve(projectDir) });
14
+ if (!opened.ok) throw new Error(`backend-capabilities: ${opened.error.message}`);
15
+ const capabilities = runtime.getCapabilities();
16
+ if (!capabilities.ok) {
17
+ await runtime.closeSession({ sessionId: opened.data.sessionId });
18
+ throw new Error("backend-capabilities: failed to read capabilities");
19
+ }
20
+ console.log(`Session: ${opened.data.sessionId}`);
21
+ console.log(`Project: ${opened.data.canonicalProjectPath}`);
22
+ console.log(`Revision: ${opened.data.revision}`);
23
+ console.log(`Runtime owner: ${capabilities.data.runtimeOwner}`);
24
+ console.log(`Transaction owner: ${capabilities.data.transactionKernelOwner}`);
25
+ console.log(`App seam owner: ${capabilities.data.appSeamOwner}`);
26
+ const closed = await runtime.closeSession({ sessionId: opened.data.sessionId });
27
+ if (!closed.ok) throw new Error(`backend-capabilities: ${closed.error.message}`);
28
+ });
29
+ }
30
+ //#endregion
31
+ //#region ../functions/src/inspect.ts
32
+ function mapResource(resource) {
33
+ return {
34
+ name: resource.getName(),
35
+ id: resource.getId(),
36
+ path: resource.getPath?.() ?? "/",
37
+ exported: resource.getExported?.() ?? false
38
+ };
39
+ }
40
+ function mapComponentDetail(component, totals) {
41
+ const children = component.listChildren();
42
+ const controllers = component.listControllers();
43
+ const transitions = component.listTransitions();
44
+ totals.displayObjects += children.length;
45
+ totals.controllers += controllers.length;
46
+ totals.transitions += transitions.length;
47
+ for (const child of children) totals.gears += child.listGears().length;
48
+ return {
49
+ name: component.getName(),
50
+ id: component.getId(),
51
+ childCount: children.length,
52
+ controllerCount: controllers.length,
53
+ transitionCount: transitions.length
54
+ };
55
+ }
56
+ /**
57
+ * Generates a detailed report of the project contents.
58
+ *
59
+ * Unlike other transforms, `inspect()` does NOT modify the document —
60
+ * it returns a structured report.
61
+ *
62
+ * ```ts
63
+ * const report = inspect(doc);
64
+ * console.log(`${report.totals.packages} packages, ${report.totals.components} components`);
65
+ * ```
66
+ */
67
+ function inspect(doc) {
68
+ const root = doc.getRoot();
69
+ const totals = {
70
+ packages: 0,
71
+ images: 0,
72
+ sounds: 0,
73
+ fonts: 0,
74
+ movieClips: 0,
75
+ components: 0,
76
+ displayObjects: 0,
77
+ gears: 0,
78
+ controllers: 0,
79
+ transitions: 0
80
+ };
81
+ const packages = root.listPackages().map((pkg) => {
82
+ totals.packages++;
83
+ const resources = pkg.listResources();
84
+ const images = resources.filter((r) => r.propertyType === "ImageResource");
85
+ const sounds = resources.filter((r) => r.propertyType === "SoundResource");
86
+ const fonts = resources.filter((r) => r.propertyType === "FontResource");
87
+ const movieClips = resources.filter((r) => r.propertyType === "MovieClipResource");
88
+ const components = pkg.listComponents();
89
+ totals.images += images.length;
90
+ totals.sounds += sounds.length;
91
+ totals.fonts += fonts.length;
92
+ totals.movieClips += movieClips.length;
93
+ totals.components += components.length;
94
+ const componentDetails = components.map((component) => mapComponentDetail(component, totals));
95
+ return {
96
+ name: pkg.getName(),
97
+ id: pkg.getId(),
98
+ publishName: pkg.getPublishName() || pkg.getName(),
99
+ resources: {
100
+ images: {
101
+ count: images.length,
102
+ details: images.map(mapResource)
103
+ },
104
+ sounds: {
105
+ count: sounds.length,
106
+ details: sounds.map(mapResource)
107
+ },
108
+ fonts: {
109
+ count: fonts.length,
110
+ details: fonts.map(mapResource)
111
+ },
112
+ movieClips: {
113
+ count: movieClips.length,
114
+ details: movieClips.map(mapResource)
115
+ },
116
+ components: {
117
+ count: components.length,
118
+ details: components.map(mapResource)
119
+ }
120
+ },
121
+ componentDetails
122
+ };
123
+ });
124
+ return {
125
+ projectId: root.getProjectId(),
126
+ projectType: root.getProjectType(),
127
+ version: root.getVersion(),
128
+ packages,
129
+ totals
130
+ };
131
+ }
132
+ //#endregion
133
+ //#region ../functions/src/utils.ts
134
+ /**
135
+ * Wraps a transform function, assigning it a name for the transform stack.
136
+ */
137
+ function createTransform(name, fn) {
138
+ Object.defineProperty(fn, "name", { value: name });
139
+ return fn;
140
+ }
141
+ function parseTextureSetMode(value) {
142
+ const raw = value?.trim() ?? "";
143
+ if (!raw) return {
144
+ kind: "auto",
145
+ raw: ""
146
+ };
147
+ if (raw === "alone") return {
148
+ kind: "standalone",
149
+ raw,
150
+ sizeMode: "default"
151
+ };
152
+ if (raw === "alone_npot") return {
153
+ kind: "standalone",
154
+ raw,
155
+ sizeMode: "npot"
156
+ };
157
+ if (raw === "alone_mof") return {
158
+ kind: "standalone",
159
+ raw,
160
+ sizeMode: "multipleOf4"
161
+ };
162
+ if (/^\d+$/.test(raw)) {
163
+ const pageIndex = Number(raw);
164
+ if (pageIndex >= 0 && pageIndex <= 10) return {
165
+ kind: "page",
166
+ raw,
167
+ pageIndex
168
+ };
169
+ }
170
+ return {
171
+ kind: "auto",
172
+ raw
173
+ };
174
+ }
175
+ //#endregion
8
176
  //#region ../../node_modules/.pnpm/property-graph@4.1.0/node_modules/property-graph/dist/index.mjs
9
177
  var EventDispatcher = class {
10
178
  _listeners = {};
@@ -1688,6 +1856,7 @@ var MovieClipResource = class extends ExtensibleProperty {
1688
1856
  highResolutionItemIds: [],
1689
1857
  fileName: "",
1690
1858
  exported: false,
1859
+ textureSetMode: "",
1691
1860
  width: 0,
1692
1861
  height: 0,
1693
1862
  interval: 0,
@@ -1739,6 +1908,12 @@ var MovieClipResource = class extends ExtensibleProperty {
1739
1908
  setExported(v) {
1740
1909
  return this.set("exported", v);
1741
1910
  }
1911
+ getTextureSetMode() {
1912
+ return this.get("textureSetMode");
1913
+ }
1914
+ setTextureSetMode(v) {
1915
+ return this.set("textureSetMode", v);
1916
+ }
1742
1917
  getWidth() {
1743
1918
  return this.get("width");
1744
1919
  }
@@ -4451,6 +4626,8 @@ var GComponent = class extends GObject {
4451
4626
  instanceController: "",
4452
4627
  instancePage: "",
4453
4628
  instanceChecked: false,
4629
+ instanceSound: "",
4630
+ instanceSoundVolumeScale: 1,
4454
4631
  instancePromptText: "",
4455
4632
  instanceSelectionController: "",
4456
4633
  instanceVisibleItemCount: 0,
@@ -4723,6 +4900,18 @@ var GComponent = class extends GObject {
4723
4900
  setInstanceChecked(v) {
4724
4901
  return this.setComponentProp("instanceChecked", v);
4725
4902
  }
4903
+ getInstanceSound() {
4904
+ return firstString$1(this.getComponentProp("instanceSound"));
4905
+ }
4906
+ setInstanceSound(v) {
4907
+ return this.setComponentProp("instanceSound", v);
4908
+ }
4909
+ getInstanceSoundVolumeScale() {
4910
+ return this.getComponentProp("instanceSoundVolumeScale");
4911
+ }
4912
+ setInstanceSoundVolumeScale(v) {
4913
+ return this.setComponentProp("instanceSoundVolumeScale", v);
4914
+ }
4726
4915
  getInstancePromptText() {
4727
4916
  return firstString$1(this.getComponentProp("instancePromptText"));
4728
4917
  }
@@ -9081,6 +9270,7 @@ var Document = class Document {
9081
9270
  _graph = new Graph();
9082
9271
  _root = new Root(this._graph);
9083
9272
  _logger = Logger.DEFAULT_INSTANCE;
9273
+ _projectDir = "";
9084
9274
  static _GRAPH_DOCUMENTS = /* @__PURE__ */ new WeakMap();
9085
9275
  static fromGraph(graph) {
9086
9276
  return Document._GRAPH_DOCUMENTS.get(graph) || null;
@@ -9102,6 +9292,13 @@ var Document = class Document {
9102
9292
  this._logger = logger;
9103
9293
  return this;
9104
9294
  }
9295
+ getProjectDir() {
9296
+ return this._projectDir;
9297
+ }
9298
+ setProjectDir(projectDir) {
9299
+ this._projectDir = projectDir;
9300
+ return this;
9301
+ }
9105
9302
  async transform(...transforms) {
9106
9303
  const stack = transforms.map((fn) => fn.name);
9107
9304
  for (const transform of transforms) await transform(this, { stack });
@@ -9282,6 +9479,7 @@ const PACKAGE_FONT_RESOURCE_ATTRS = {
9282
9479
  renderMode: { canonical: "renderMode" },
9283
9480
  samplePointSize: { canonical: "samplePointSize" }
9284
9481
  };
9482
+ const PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS = { atlas: { canonical: "atlas" } };
9285
9483
  const PACKAGE_SKELETON_RESOURCE_ATTRS = {
9286
9484
  width: { canonical: "width" },
9287
9485
  height: { canonical: "height" },
@@ -9518,7 +9716,10 @@ const LIST_PANEL_ATTRS = {
9518
9716
  const BUTTON_EXTENSION_ATTRS = {
9519
9717
  mode: { canonical: "mode" },
9520
9718
  sound: { canonical: "sound" },
9521
- soundVolumeScale: { canonical: "soundVolumeScale" },
9719
+ soundVolumeScale: {
9720
+ canonical: "soundVolumeScale",
9721
+ aliases: ["volume"]
9722
+ },
9522
9723
  downEffect: { canonical: "downEffect" },
9523
9724
  downEffectValue: { canonical: "downEffectValue" },
9524
9725
  title: { canonical: "title" },
@@ -9644,6 +9845,7 @@ const PACKAGE_PUBLISH_NODE = defineNode(PACKAGE_PUBLISH_ATTRS, { atlas: PACKAGE_
9644
9845
  const PACKAGE_RESOURCE_NODE = defineNode(PACKAGE_RESOURCE_BASE_ATTRS);
9645
9846
  const PACKAGE_IMAGE_RESOURCE_NODE = defineNode(PACKAGE_IMAGE_RESOURCE_ATTRS);
9646
9847
  const PACKAGE_FONT_RESOURCE_NODE = defineNode(PACKAGE_FONT_RESOURCE_ATTRS);
9848
+ const PACKAGE_MOVIE_CLIP_RESOURCE_NODE = defineNode(PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS);
9647
9849
  const PACKAGE_SKELETON_RESOURCE_NODE = defineNode(PACKAGE_SKELETON_RESOURCE_ATTRS);
9648
9850
  const DISPLAY_OBJECT_NODE = defineNode(DISPLAY_OBJECT_IDENTITY_ATTRS);
9649
9851
  const BUTTON_EXTENSION_NODE = defineNode(BUTTON_EXTENSION_ATTRS);
@@ -9734,6 +9936,7 @@ const PROJECT_XML_PROTOCOL = {
9734
9936
  packageResource: PACKAGE_RESOURCE_NODE,
9735
9937
  packageImageResource: PACKAGE_IMAGE_RESOURCE_NODE,
9736
9938
  packageFontResource: PACKAGE_FONT_RESOURCE_NODE,
9939
+ packageMovieClipResource: PACKAGE_MOVIE_CLIP_RESOURCE_NODE,
9737
9940
  packageSkeletonResource: PACKAGE_SKELETON_RESOURCE_NODE,
9738
9941
  displayObject: DISPLAY_OBJECT_NODE,
9739
9942
  image: IMAGE_NODE,
@@ -10177,6 +10380,7 @@ var ProjectReader = class {
10177
10380
  const fs = this._fs;
10178
10381
  const doc = new Document();
10179
10382
  const basePath = getProjectBasePath(fs, projectPath);
10383
+ doc.setProjectDir(basePath);
10180
10384
  const ctx = new ReaderContext(doc, basePath);
10181
10385
  const projDesc = getXmlNode(parseXML(await fs.readFile(projectPath)).projectDescription);
10182
10386
  if (projDesc) {
@@ -10493,6 +10697,8 @@ var ProjectReader = class {
10493
10697
  res.setBranch(branchName);
10494
10698
  res.setFileName(name);
10495
10699
  res.setExported(exported);
10700
+ const textureSetMode = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas);
10701
+ if (textureSetMode !== void 0) res.setTextureSetMode(textureSetMode);
10496
10702
  pkg.addResource(res);
10497
10703
  ctx.registerResource(pkg.getId(), id, res);
10498
10704
  return res;
@@ -11752,6 +11958,10 @@ var ProjectReader = class {
11752
11958
  if (page !== void 0) componentObj.setInstancePage?.(page);
11753
11959
  const checked = extSpecs.checked ? readXmlAttr(extAttrs, extSpecs.checked) : void 0;
11754
11960
  if (checked !== void 0) componentObj.setInstanceChecked?.(parseBool(checked));
11961
+ const sound = extSpecs.sound ? readXmlAttr(extAttrs, extSpecs.sound) : void 0;
11962
+ if (sound !== void 0) componentObj.setInstanceSound?.(sound);
11963
+ const soundVolumeScale = extSpecs.soundVolumeScale ? readXmlAttr(extAttrs, extSpecs.soundVolumeScale) : void 0;
11964
+ if (soundVolumeScale !== void 0) componentObj.setInstanceSoundVolumeScale?.(parseFloat2(soundVolumeScale, 1));
11755
11965
  const prompt = extSpecs.prompt ? readXmlAttr(extAttrs, extSpecs.prompt) : void 0;
11756
11966
  if (prompt !== void 0) componentObj.setInstancePromptText?.(prompt);
11757
11967
  const selectionController = extSpecs.selectionController ? readXmlAttr(extAttrs, extSpecs.selectionController) : void 0;
@@ -12418,6 +12628,10 @@ var ProjectWriter = class {
12418
12628
  const samplePointSize = fontRes.getSamplePointSize?.() ?? 0;
12419
12629
  if (samplePointSize !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageFontResource.attrs.samplePointSize, String(samplePointSize));
12420
12630
  }
12631
+ if (res.propertyType === "MovieClipResource") {
12632
+ const textureSetMode = res.getTextureSetMode?.() ?? "";
12633
+ if (textureSetMode) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas, textureSetMode);
12634
+ }
12421
12635
  if (res.propertyType === "SpineResource" || res.propertyType === "DragonBonesResource") {
12422
12636
  const skeletonRes = res;
12423
12637
  writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageSkeletonResource.attrs.width, String(skeletonRes.getWidth?.() ?? 0));
@@ -13099,6 +13313,8 @@ var ProjectWriter = class {
13099
13313
  if (typedObj.getInstanceController?.() && extSpecs.controller) writeXmlAttr(extAttrs, extSpecs.controller, typedObj.getInstanceController?.());
13100
13314
  if (typedObj.getInstancePage?.() && extSpecs.page) writeXmlAttr(extAttrs, extSpecs.page, typedObj.getInstancePage?.());
13101
13315
  if (typedObj.getInstanceChecked?.() && extSpecs.checked) writeXmlAttr(extAttrs, extSpecs.checked, "1");
13316
+ if (typedObj.getInstanceSound?.() && extSpecs.sound) writeXmlAttr(extAttrs, extSpecs.sound, typedObj.getInstanceSound?.());
13317
+ if ((typedObj.getInstanceSoundVolumeScale?.() ?? 1) !== 1 && extSpecs.soundVolumeScale) writeXmlAttr(extAttrs, extSpecs.soundVolumeScale, String(typedObj.getInstanceSoundVolumeScale?.() ?? 1));
13102
13318
  if (typedObj.getInstancePromptText?.() && extSpecs.prompt) writeXmlAttr(extAttrs, extSpecs.prompt, typedObj.getInstancePromptText?.());
13103
13319
  if (typedObj.getInstanceSelectionController?.() && extSpecs.selectionController) writeXmlAttr(extAttrs, extSpecs.selectionController, typedObj.getInstanceSelectionController?.());
13104
13320
  if ((typedObj.getInstanceVisibleItemCount?.() ?? 0) > 0 && extSpecs.visibleItemCount) writeXmlAttr(extAttrs, extSpecs.visibleItemCount, String(typedObj.getInstanceVisibleItemCount?.() ?? 0));
@@ -17569,8 +17785,8 @@ function decodeChildBlock6(resource, child, childBuf) {
17569
17785
  if (relatedControllerIndex >= 0) component.setInstanceController(resource.listControllers()[relatedControllerIndex]?.getName() ?? "");
17570
17786
  }
17571
17787
  component.setInstancePage(childBuf.readS() ?? "");
17572
- childBuf.readS();
17573
- if (childBuf.readBool() && remainingBytes(childBuf) >= 4) childBuf.getFloat32();
17788
+ component.setInstanceSound(childBuf.readS() ?? "");
17789
+ if (childBuf.readBool() && remainingBytes(childBuf) >= 4) component.setInstanceSoundVolumeScale(childBuf.getFloat32());
17574
17790
  if (remainingBytes(childBuf) >= 1) component.setInstanceChecked(childBuf.readBool());
17575
17791
  break;
17576
17792
  case "Label":
@@ -18144,8 +18360,9 @@ const BinItemType$1 = {
18144
18360
  Font: 5,
18145
18361
  Swf: 6,
18146
18362
  Misc: 7,
18147
- Spine: 8,
18148
- DragonBones: 9
18363
+ Unknown: 8,
18364
+ Spine: 9,
18365
+ DragonBones: 10
18149
18366
  };
18150
18367
  function normalizePackageResourcePath(path) {
18151
18368
  const normalized = path.replace(/\\/g, "/").trim();
@@ -19983,8 +20200,13 @@ function _writeExtensionInstanceData(buf, extType, child, comp, pkg, version) {
19983
20200
  buf.writeInt16(ctrlIdx >= 0 ? ctrlIdx : -1);
19984
20201
  } else buf.writeInt16(-1);
19985
20202
  buf.writeS(child.getInstancePage?.() ?? null);
19986
- buf.writeSEx(null, false, false);
19987
- buf.writeBool(false);
20203
+ const sound = child.getInstanceSound?.() ?? null;
20204
+ buf.writeSEx(remapLocalUiUrl(pkg, sound) ?? null, false, false);
20205
+ const soundVolume = child.getInstanceSoundVolumeScale?.();
20206
+ if (soundVolume !== void 0 && soundVolume !== null && soundVolume !== 1) {
20207
+ buf.writeBool(true);
20208
+ buf.writeFloat32(soundVolume);
20209
+ } else buf.writeBool(false);
19988
20210
  buf.writeBool(child.getInstanceChecked?.() ?? false);
19989
20211
  break;
19990
20212
  }
@@ -20165,8 +20387,9 @@ const BinItemType = {
20165
20387
  Atlas: 4,
20166
20388
  Font: 5,
20167
20389
  Misc: 7,
20168
- Spine: 8,
20169
- DragonBones: 9
20390
+ Unknown: 8,
20391
+ Spine: 9,
20392
+ DragonBones: 10
20170
20393
  };
20171
20394
  /**
20172
20395
  * Maps our PropertyType to the editor's type string used for sorting.
@@ -20825,228 +21048,60 @@ var PlatformIO = class {
20825
21048
  }
20826
21049
  };
20827
21050
  //#endregion
20828
- //#region ../core/src/io/node-io.ts
20829
- /**
20830
- * Node.js I/O implementation for reading and writing FairyGUI projects.
20831
- *
20832
- * Usage:
20833
- *
20834
- * ```ts
20835
- * import { NodeIO } from '@openfairygui/core/node';
20836
- *
20837
- * const io = new NodeIO();
20838
- * const doc = await io.readProject('./path/to/project.fairy');
20839
- * await io.writeProject(doc, './path/to/output.fairy');
20840
- * const doc2 = await io.readBinary('./path/to/package_fui.bytes');
20841
- * ```
20842
- *
20843
- * @category I/O
20844
- */
20845
- var NodeIO = class extends PlatformIO {
20846
- createFileSystem() {
20847
- return {
20848
- async readFile(filePath) {
20849
- return fs$1.readFile(filePath, "utf-8");
20850
- },
20851
- async readFileRaw(filePath) {
20852
- const buf = await fs$1.readFile(filePath);
20853
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
20854
- },
20855
- async writeFile(filePath, content) {
20856
- await fs$1.writeFile(filePath, content, "utf-8");
20857
- },
20858
- async writeFileRaw(filePath, data) {
20859
- await fs$1.writeFile(filePath, data);
20860
- },
20861
- async mkdir(dirPath) {
20862
- await fs$1.mkdir(dirPath, { recursive: true });
20863
- },
20864
- async readdir(dirPath) {
20865
- return (await fs$1.readdir(dirPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
20866
- },
20867
- async exists(filePath) {
20868
- try {
20869
- await fs$1.access(filePath);
20870
- return true;
20871
- } catch {
20872
- return false;
20873
- }
20874
- },
20875
- join(...paths) {
20876
- return path$1.join(...paths);
20877
- },
20878
- dirname(filePath) {
20879
- return path$1.dirname(filePath);
20880
- }
20881
- };
20882
- }
20883
- };
20884
- //#endregion
20885
- //#region ../functions/src/inspect.ts
20886
- function mapResource(resource) {
20887
- return {
20888
- name: resource.getName(),
20889
- id: resource.getId(),
20890
- path: resource.getPath?.() ?? "/",
20891
- exported: resource.getExported?.() ?? false
20892
- };
20893
- }
20894
- function mapComponentDetail(component, totals) {
20895
- const children = component.listChildren();
20896
- const controllers = component.listControllers();
20897
- const transitions = component.listTransitions();
20898
- totals.displayObjects += children.length;
20899
- totals.controllers += controllers.length;
20900
- totals.transitions += transitions.length;
20901
- for (const child of children) totals.gears += child.listGears().length;
20902
- return {
20903
- name: component.getName(),
20904
- id: component.getId(),
20905
- childCount: children.length,
20906
- controllerCount: controllers.length,
20907
- transitionCount: transitions.length
20908
- };
20909
- }
20910
- /**
20911
- * Generates a detailed report of the project contents.
20912
- *
20913
- * Unlike other transforms, `inspect()` does NOT modify the document —
20914
- * it returns a structured report.
20915
- *
20916
- * ```ts
20917
- * const report = inspect(doc);
20918
- * console.log(`${report.totals.packages} packages, ${report.totals.components} components`);
20919
- * ```
20920
- */
20921
- function inspect(doc) {
20922
- const root = doc.getRoot();
20923
- const totals = {
20924
- packages: 0,
20925
- images: 0,
20926
- sounds: 0,
20927
- fonts: 0,
20928
- movieClips: 0,
20929
- components: 0,
20930
- displayObjects: 0,
20931
- gears: 0,
20932
- controllers: 0,
20933
- transitions: 0
20934
- };
20935
- const packages = root.listPackages().map((pkg) => {
20936
- totals.packages++;
20937
- const resources = pkg.listResources();
20938
- const images = resources.filter((r) => r.propertyType === "ImageResource");
20939
- const sounds = resources.filter((r) => r.propertyType === "SoundResource");
20940
- const fonts = resources.filter((r) => r.propertyType === "FontResource");
20941
- const movieClips = resources.filter((r) => r.propertyType === "MovieClipResource");
20942
- const components = pkg.listComponents();
20943
- totals.images += images.length;
20944
- totals.sounds += sounds.length;
20945
- totals.fonts += fonts.length;
20946
- totals.movieClips += movieClips.length;
20947
- totals.components += components.length;
20948
- const componentDetails = components.map((component) => mapComponentDetail(component, totals));
20949
- return {
20950
- name: pkg.getName(),
20951
- id: pkg.getId(),
20952
- publishName: pkg.getPublishName() || pkg.getName(),
20953
- resources: {
20954
- images: {
20955
- count: images.length,
20956
- details: images.map(mapResource)
20957
- },
20958
- sounds: {
20959
- count: sounds.length,
20960
- details: sounds.map(mapResource)
20961
- },
20962
- fonts: {
20963
- count: fonts.length,
20964
- details: fonts.map(mapResource)
20965
- },
20966
- movieClips: {
20967
- count: movieClips.length,
20968
- details: movieClips.map(mapResource)
20969
- },
20970
- components: {
20971
- count: components.length,
20972
- details: components.map(mapResource)
20973
- }
20974
- },
20975
- componentDetails
20976
- };
20977
- });
20978
- return {
20979
- projectId: root.getProjectId(),
20980
- projectType: root.getProjectType(),
20981
- version: root.getVersion(),
20982
- packages,
20983
- totals
20984
- };
20985
- }
20986
- //#endregion
20987
- //#region ../functions/src/utils.ts
20988
- /**
20989
- * Wraps a transform function, assigning it a name for the transform stack.
20990
- */
20991
- function createTransform(name, fn) {
20992
- Object.defineProperty(fn, "name", { value: name });
20993
- return fn;
20994
- }
20995
- //#endregion
20996
- //#region ../functions/src/max-rects-compat.ts
20997
- const NO_ROTATION = 2;
20998
- const MAX_SCORE = 2147483647;
20999
- const MAX_RECTS_METHOD = {
21000
- BestShortSideFit: 0,
21001
- BestLongSideFit: 1,
21002
- BestAreaFit: 2,
21003
- BottomLeftRule: 3,
21004
- ContactPointRule: 4
21005
- };
21006
- const COMPAT_NODE_RECT_FLAGS = {
21007
- DUPLICATE_PADDING: 1,
21008
- NO_ROTATION
21009
- };
21010
- var MaxRectsCompat = class MaxRectsCompat {
21011
- static helperRect = createNodeRect();
21012
- binWidth = 0;
21013
- binHeight = 0;
21014
- allowRotations = false;
21015
- usedRectangles = [];
21016
- freeRectangles = [];
21017
- init(width, height, allowRotations = false) {
21018
- this.binWidth = width;
21019
- this.binHeight = height;
21020
- this.allowRotations = allowRotations;
21021
- this.usedRectangles.length = 0;
21022
- this.freeRectangles.length = 0;
21023
- this.freeRectangles.push({
21024
- ...createNodeRect(),
21025
- x: 0,
21026
- y: 0,
21027
- width,
21028
- height
21029
- });
21030
- }
21031
- insert(rect, method) {
21032
- const newNode = this.scoreRect(rect, method);
21033
- if (newNode.height === 0) return null;
21034
- const placed = cloneNodeRect(newNode);
21035
- this.placeRect(placed);
21036
- return placed;
21037
- }
21038
- pack(rects, method) {
21039
- const remaining = rects.map(cloneNodeRect);
21040
- while (remaining.length > 0) {
21041
- let bestIndex = -1;
21042
- const bestNode = createNodeRect();
21043
- bestNode.score1 = MAX_SCORE;
21044
- bestNode.score2 = MAX_SCORE;
21045
- for (let index = 0; index < remaining.length; index += 1) {
21046
- const candidate = this.scoreRect(remaining[index], method);
21047
- if (candidate.score1 < bestNode.score1 || candidate.score1 === bestNode.score1 && candidate.score2 < bestNode.score2) {
21048
- copyNodeRect(bestNode, candidate);
21049
- bestIndex = index;
21051
+ //#region ../functions/src/max-rects-compat.ts
21052
+ const NO_ROTATION = 2;
21053
+ const MAX_SCORE = 2147483647;
21054
+ const MAX_RECTS_METHOD = {
21055
+ BestShortSideFit: 0,
21056
+ BestLongSideFit: 1,
21057
+ BestAreaFit: 2,
21058
+ BottomLeftRule: 3,
21059
+ ContactPointRule: 4
21060
+ };
21061
+ const COMPAT_NODE_RECT_FLAGS = {
21062
+ DUPLICATE_PADDING: 1,
21063
+ NO_ROTATION
21064
+ };
21065
+ var MaxRectsCompat = class MaxRectsCompat {
21066
+ static helperRect = createNodeRect();
21067
+ binWidth = 0;
21068
+ binHeight = 0;
21069
+ allowRotations = false;
21070
+ usedRectangles = [];
21071
+ freeRectangles = [];
21072
+ init(width, height, allowRotations = false) {
21073
+ this.binWidth = width;
21074
+ this.binHeight = height;
21075
+ this.allowRotations = allowRotations;
21076
+ this.usedRectangles.length = 0;
21077
+ this.freeRectangles.length = 0;
21078
+ this.freeRectangles.push({
21079
+ ...createNodeRect(),
21080
+ x: 0,
21081
+ y: 0,
21082
+ width,
21083
+ height
21084
+ });
21085
+ }
21086
+ insert(rect, method) {
21087
+ const newNode = this.scoreRect(rect, method);
21088
+ if (newNode.height === 0) return null;
21089
+ const placed = cloneNodeRect(newNode);
21090
+ this.placeRect(placed);
21091
+ return placed;
21092
+ }
21093
+ pack(rects, method) {
21094
+ const remaining = rects.map(cloneNodeRect);
21095
+ while (remaining.length > 0) {
21096
+ let bestIndex = -1;
21097
+ const bestNode = createNodeRect();
21098
+ bestNode.score1 = MAX_SCORE;
21099
+ bestNode.score2 = MAX_SCORE;
21100
+ for (let index = 0; index < remaining.length; index += 1) {
21101
+ const candidate = this.scoreRect(remaining[index], method);
21102
+ if (candidate.score1 < bestNode.score1 || candidate.score1 === bestNode.score1 && candidate.score2 < bestNode.score2) {
21103
+ copyNodeRect(bestNode, candidate);
21104
+ bestIndex = index;
21050
21105
  }
21051
21106
  }
21052
21107
  if (bestIndex === -1) break;
@@ -21707,6 +21762,19 @@ const ATLAS_DEFAULTS = {
21707
21762
  function getPublishedItemId(resource) {
21708
21763
  return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
21709
21764
  }
21765
+ function getSelectedSkeletonDependencyImageIds(resources) {
21766
+ const imageIds = /* @__PURE__ */ new Set();
21767
+ const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource]));
21768
+ for (const resource of resources) {
21769
+ if (!isSkeletonResource$1(resource)) continue;
21770
+ for (const requiredId of resource.getRequireIds()) {
21771
+ if (!requiredId) continue;
21772
+ const required = resourcesById.get(requiredId);
21773
+ if (required && isImageResource$1(required)) imageIds.add(requiredId);
21774
+ }
21775
+ }
21776
+ return imageIds;
21777
+ }
21710
21778
  function resolveFontFileName(fontName) {
21711
21779
  return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
21712
21780
  }
@@ -21843,12 +21911,18 @@ function atlas(_options = {}) {
21843
21911
  const logger = doc.getLogger();
21844
21912
  const encoder = options.encoder;
21845
21913
  const doTrim = options.trimImage && !!encoder && !!options.basePath;
21914
+ const packageFilter = options.packages ? new Set(options.packages) : null;
21846
21915
  for (const pkg of root.listPackages()) {
21916
+ if (packageFilter && !packageFilter.has(pkg.getName())) continue;
21847
21917
  const selectedPublishIds = new Set((pkg.getExtras() ?? {}).publishedResourceIds ?? []);
21848
21918
  const allResources = selectedPublishIds.size > 0 ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
21919
+ const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
21849
21920
  const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
21850
21921
  const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
21851
- if (!allResources.some((resource) => isPackableResource(resource))) continue;
21922
+ if (!allResources.some((resource) => {
21923
+ if (isImageResource$1(resource) && skeletonDependencyImageIds.has(resource.getId())) return false;
21924
+ return isPackableResource(resource);
21925
+ })) continue;
21852
21926
  const inputs = [];
21853
21927
  const referencedIds = /* @__PURE__ */ new Set();
21854
21928
  const resourceMap = /* @__PURE__ */ new Map();
@@ -21917,6 +21991,7 @@ function atlas(_options = {}) {
21917
21991
  }
21918
21992
  for (const res of orderedAllResources) if (isImageResource$1(res)) {
21919
21993
  const resId = res.getId();
21994
+ if (skeletonDependencyImageIds.has(resId)) continue;
21920
21995
  if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21921
21996
  await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
21922
21997
  } else if (isMovieClipResource$1(res)) {
@@ -21929,137 +22004,59 @@ function atlas(_options = {}) {
21929
22004
  await _collectFontTexture(doc, res, pkg, options);
21930
22005
  }
21931
22006
  if (inputs.length === 0) continue;
21932
- const branchGroups = buildBranchAtlasGroups(doc, inputs, options);
21933
22007
  let totalPageCount = 0;
21934
22008
  let usedDirectOutput = false;
22009
+ const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
22010
+ const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
22011
+ const branchPageOffsets = /* @__PURE__ */ new Map();
21935
22012
  for (const group of branchGroups) {
21936
- const directOutput = resolveDirectImageOutput(group.inputs, options);
22013
+ const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0 ? resolveDirectImageOutput(group.inputs, options) : null;
21937
22014
  if (directOutput) {
21938
22015
  await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
21939
22016
  usedDirectOutput = true;
21940
22017
  totalPageCount += 1;
21941
22018
  continue;
21942
22019
  }
21943
- const hasDuplicatePadding = group.inputs.some((i) => {
21944
- return isImageResource$1(i.resource) && i.resource.getDuplicatePadding?.() === true;
22020
+ const pageStart = reserveAutoPageStart(branchPageOffsets, group.branchOrdinal, reservedPageIndexes);
22021
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
22022
+ branchName: group.branchName,
22023
+ branchOrdinal: group.branchOrdinal,
22024
+ pageStart,
22025
+ fileNameAt: (pageIndex) => resolveAtlasOutputFileName(pkg, pageIndex, group.branchName),
22026
+ options,
22027
+ encoder,
22028
+ logger
21945
22029
  });
21946
- const pages = new MaxRectsPackerCompat({
21947
- pot: options.powerOfTwo,
21948
- mof: !options.powerOfTwo,
21949
- padding: options.padding,
21950
- rotation: options.allowRotation,
21951
- minWidth: 16,
21952
- minHeight: 16,
21953
- maxWidth: options.maxSize,
21954
- maxHeight: options.maxSize,
21955
- square: options.square,
21956
- fast: options.fast,
21957
- edgePadding: false,
21958
- duplicatePadding: hasDuplicatePadding,
21959
- multiPage: options.multiPage,
21960
- preserveInputOrderOnTie: options.preserveInputOrderOnTie
21961
- }).pack(group.inputs.map((input, index) => inputToCompatRect(input, index)));
21962
- if (!pages || pages.length === 0) continue;
21963
- totalPageCount += pages.length;
21964
- for (let p = 0; p < pages.length; p++) {
21965
- const page = pages[p];
21966
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, p)}`);
21967
- atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, p));
21968
- atlasNode.setFile(resolveAtlasOutputFileName(pkg, p, group.branchName));
21969
- atlasNode.setWidth(page.width);
21970
- atlasNode.setHeight(page.height);
21971
- pkg.addAtlas(atlasNode);
21972
- for (const pr of page.outputRects) {
21973
- const input = group.inputs[pr.index];
21974
- if (!input) continue;
21975
- const packedSize = resolvePackedRectSize(input, pr.width, pr.height, pr.rotated);
21976
- const rotated = pr.rotated;
21977
- const sprite = doc.createSprite();
21978
- sprite.setItemId(input.id);
21979
- sprite.setRectX(pr.x);
21980
- sprite.setRectY(pr.y);
21981
- sprite.setRectWidth(packedSize.width);
21982
- sprite.setRectHeight(packedSize.height);
21983
- sprite.setRotated(rotated);
21984
- sprite.setOffsetX(input.offsetX);
21985
- sprite.setOffsetY(input.offsetY);
21986
- sprite.setOriginalWidth(input.originalWidth);
21987
- sprite.setOriginalHeight(input.originalHeight);
21988
- sprite.setAtlas(atlasNode);
21989
- atlasNode.addSprite(sprite);
21990
- }
21991
- for (const res of allResources) {
21992
- if (!isFontResource$1(res)) continue;
21993
- const alias = res.getExtras()?._fontSpriteAlias;
21994
- if (!alias) continue;
21995
- const imgSprite = page.outputRects.find((result) => group.inputs[result.index]?.id === alias.textureId);
21996
- if (!imgSprite) continue;
21997
- const imgInput = group.inputs[imgSprite.index];
21998
- const fontSprite = doc.createSprite();
21999
- fontSprite.setItemId(alias.fontId);
22000
- fontSprite.setRectX(imgSprite.x);
22001
- fontSprite.setRectY(imgSprite.y);
22002
- fontSprite.setRectWidth(imgSprite.width);
22003
- fontSprite.setRectHeight(imgSprite.height);
22004
- fontSprite.setRotated(imgSprite.rotated);
22005
- if (imgInput) {
22006
- fontSprite.setOffsetX(imgInput.offsetX);
22007
- fontSprite.setOffsetY(imgInput.offsetY);
22008
- fontSprite.setOriginalWidth(imgInput.originalWidth);
22009
- fontSprite.setOriginalHeight(imgInput.originalHeight);
22010
- }
22011
- fontSprite.setAtlas(atlasNode);
22012
- atlasNode.addSprite(fontSprite);
22013
- }
22014
- }
22015
- if (encoder && options.outputPath) {
22016
- if (options.mkdir) await options.mkdir(options.outputPath);
22017
- for (let p = 0; p < pages.length; p++) {
22018
- const page = pages[p];
22019
- const compositeInputs = [];
22020
- for (const pr of page.outputRects) {
22021
- const input = group.inputs[pr.index];
22022
- if (!input) continue;
22023
- if (pr.width <= 0 || pr.height <= 0 || input.width <= 0 || input.height <= 0) continue;
22024
- try {
22025
- let imgBuffer;
22026
- if (input.trimBuffer) {
22027
- imgBuffer = input.trimBuffer;
22028
- if (imgBuffer.length === 0) continue;
22029
- } else if (input.rasterizedBuffer) imgBuffer = input.rasterizedBuffer;
22030
- else {
22031
- if (!isImageResource$1(input.resource)) {
22032
- logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
22033
- continue;
22034
- }
22035
- imgBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
22036
- }
22037
- if (pr.rotated) imgBuffer = await encoder(imgBuffer).rotate(270).toBuffer();
22038
- compositeInputs.push({
22039
- input: imgBuffer,
22040
- left: pr.x,
22041
- top: pr.y
22042
- });
22043
- } catch {
22044
- logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
22045
- }
22046
- }
22047
- const atlasFileName = resolveAtlasOutputFileName(pkg, p, group.branchName);
22048
- const outputFile = `${options.outputPath}/${atlasFileName}`;
22049
- await encoder({ create: {
22050
- width: page.width,
22051
- height: page.height,
22052
- channels: 4,
22053
- background: {
22054
- r: 0,
22055
- g: 0,
22056
- b: 0,
22057
- alpha: 0
22058
- }
22059
- } }).composite(compositeInputs).png().toFile(outputFile);
22060
- logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
22061
- }
22062
- }
22030
+ totalPageCount += emittedPageCount;
22031
+ branchPageOffsets.set(group.branchOrdinal, pageStart + emittedPageCount);
22032
+ }
22033
+ for (const group of fixedPageGroups) {
22034
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
22035
+ branchName: group.branchName,
22036
+ branchOrdinal: group.branchOrdinal,
22037
+ pageStart: group.pageIndex,
22038
+ forceSinglePage: true,
22039
+ fileNameAt: () => resolveAtlasOutputFileName(pkg, group.pageIndex, group.branchName),
22040
+ options,
22041
+ encoder,
22042
+ logger
22043
+ });
22044
+ totalPageCount += emittedPageCount;
22045
+ }
22046
+ const standalonePageOffsets = new Map(branchPageOffsets);
22047
+ for (const group of fixedPageGroups) {
22048
+ const nextPageIndex = group.pageIndex + 1;
22049
+ if (nextPageIndex > (standalonePageOffsets.get(group.branchOrdinal) ?? 0)) standalonePageOffsets.set(group.branchOrdinal, nextPageIndex);
22050
+ }
22051
+ for (const group of standaloneGroups) {
22052
+ const emittedPageCount = await emitStandaloneAtlasGroup(doc, pkg, group, {
22053
+ atlasIndexStart: standalonePageOffsets.get(group.branchOrdinal) ?? 0,
22054
+ options,
22055
+ encoder,
22056
+ logger
22057
+ });
22058
+ totalPageCount += emittedPageCount;
22059
+ standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
22063
22060
  }
22064
22061
  if (usedDirectOutput) logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
22065
22062
  logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
@@ -22096,6 +22093,171 @@ function buildBranchAtlasGroups(doc, inputs, options) {
22096
22093
  inputs: groups.get(branchName) ?? []
22097
22094
  }));
22098
22095
  }
22096
+ function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageIndexes) {
22097
+ let pageIndex = branchPageOffsets.get(branchOrdinal) ?? 0;
22098
+ while (branchOrdinal === 0 && reservedPageIndexes.has(pageIndex)) pageIndex += 1;
22099
+ return pageIndex;
22100
+ }
22101
+ async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
22102
+ if (inputs.length === 0) return 0;
22103
+ const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
22104
+ if (pages.length === 0) return 0;
22105
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
22106
+ const page = pages[pageOffset];
22107
+ const pageIndex = context.pageStart + pageOffset;
22108
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(context.branchOrdinal, pageIndex)}`);
22109
+ atlasNode.setIndex(resolveAtlasIndex(context.branchOrdinal, pageIndex));
22110
+ atlasNode.setFile(context.fileNameAt(pageIndex));
22111
+ atlasNode.setWidth(page.width);
22112
+ atlasNode.setHeight(page.height);
22113
+ pkg.addAtlas(atlasNode);
22114
+ attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
22115
+ await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
22116
+ }
22117
+ return pages.length;
22118
+ }
22119
+ async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
22120
+ if (group.inputs.length === 0) return 0;
22121
+ const pages = packAtlasPages(group.inputs, context.options, true, group.sizeMode === "npot" ? {
22122
+ powerOfTwo: false,
22123
+ multipleOfFour: false,
22124
+ square: false
22125
+ } : group.sizeMode === "multipleOf4" ? {
22126
+ powerOfTwo: false,
22127
+ multipleOfFour: true,
22128
+ square: false
22129
+ } : void 0);
22130
+ if (pages.length === 0) return 0;
22131
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
22132
+ const page = pages[pageOffset];
22133
+ const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
22134
+ const atlasFileName = pages.length <= 1 ? baseFileName : insertFileNameSuffix(baseFileName, `_${pageOffset}`);
22135
+ const atlasIndex = context.atlasIndexStart + pageOffset;
22136
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, atlasIndex)}`);
22137
+ atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, atlasIndex));
22138
+ atlasNode.setFile(atlasFileName);
22139
+ const standaloneSize = resolveStandaloneAtlasSize(page.width, page.height, group.sizeMode, context.options);
22140
+ atlasNode.setWidth(standaloneSize.width);
22141
+ atlasNode.setHeight(standaloneSize.height);
22142
+ pkg.addAtlas(atlasNode);
22143
+ attachSpritesToAtlas(doc, [], group.inputs, page.outputRects, atlasNode);
22144
+ await writeAtlasPageImage(pkg, group.inputs, {
22145
+ ...page,
22146
+ width: standaloneSize.width,
22147
+ height: standaloneSize.height
22148
+ }, atlasFileName, context.encoder, context.options, context.logger);
22149
+ }
22150
+ return pages.length;
22151
+ }
22152
+ function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
22153
+ const hasDuplicatePadding = inputs.some((input) => {
22154
+ return isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
22155
+ });
22156
+ return new MaxRectsPackerCompat({
22157
+ pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
22158
+ mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
22159
+ padding: options.padding,
22160
+ rotation: options.allowRotation,
22161
+ minWidth: 16,
22162
+ minHeight: 16,
22163
+ maxWidth: options.maxSize,
22164
+ maxHeight: options.maxSize,
22165
+ square: sizeOverrides?.square ?? options.square,
22166
+ fast: options.fast,
22167
+ edgePadding: false,
22168
+ duplicatePadding: hasDuplicatePadding,
22169
+ multiPage: forceSinglePage ? false : options.multiPage,
22170
+ preserveInputOrderOnTie: options.preserveInputOrderOnTie
22171
+ }).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
22172
+ }
22173
+ function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
22174
+ for (const packedRect of outputRects) {
22175
+ const input = inputs[packedRect.index];
22176
+ if (!input) continue;
22177
+ const packedSize = resolvePackedRectSize(input, packedRect.width, packedRect.height, packedRect.rotated);
22178
+ const sprite = doc.createSprite();
22179
+ sprite.setItemId(input.id);
22180
+ sprite.setRectX(packedRect.x);
22181
+ sprite.setRectY(packedRect.y);
22182
+ sprite.setRectWidth(packedSize.width);
22183
+ sprite.setRectHeight(packedSize.height);
22184
+ sprite.setRotated(packedRect.rotated);
22185
+ sprite.setOffsetX(input.offsetX);
22186
+ sprite.setOffsetY(input.offsetY);
22187
+ sprite.setOriginalWidth(input.originalWidth);
22188
+ sprite.setOriginalHeight(input.originalHeight);
22189
+ sprite.setAtlas(atlasNode);
22190
+ atlasNode.addSprite(sprite);
22191
+ }
22192
+ for (const resource of allResources) {
22193
+ if (!isFontResource$1(resource)) continue;
22194
+ const alias = resource.getExtras()?._fontSpriteAlias;
22195
+ if (!alias) continue;
22196
+ const imageSprite = outputRects.find((result) => inputs[result.index]?.id === alias.textureId);
22197
+ if (!imageSprite) continue;
22198
+ const imageInput = inputs[imageSprite.index];
22199
+ const fontSprite = doc.createSprite();
22200
+ fontSprite.setItemId(alias.fontId);
22201
+ fontSprite.setRectX(imageSprite.x);
22202
+ fontSprite.setRectY(imageSprite.y);
22203
+ fontSprite.setRectWidth(imageSprite.width);
22204
+ fontSprite.setRectHeight(imageSprite.height);
22205
+ fontSprite.setRotated(imageSprite.rotated);
22206
+ if (imageInput) {
22207
+ fontSprite.setOffsetX(imageInput.offsetX);
22208
+ fontSprite.setOffsetY(imageInput.offsetY);
22209
+ fontSprite.setOriginalWidth(imageInput.originalWidth);
22210
+ fontSprite.setOriginalHeight(imageInput.originalHeight);
22211
+ }
22212
+ fontSprite.setAtlas(atlasNode);
22213
+ atlasNode.addSprite(fontSprite);
22214
+ }
22215
+ }
22216
+ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, options, logger) {
22217
+ if (!encoder || !options.outputPath) return;
22218
+ if (options.mkdir) await options.mkdir(options.outputPath);
22219
+ const compositeInputs = [];
22220
+ for (const packedRect of page.outputRects) {
22221
+ const input = inputs[packedRect.index];
22222
+ if (!input) continue;
22223
+ if (packedRect.width <= 0 || packedRect.height <= 0 || input.width <= 0 || input.height <= 0) continue;
22224
+ try {
22225
+ let imageBuffer;
22226
+ if (input.trimBuffer) {
22227
+ imageBuffer = input.trimBuffer;
22228
+ if (imageBuffer.length === 0) continue;
22229
+ } else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
22230
+ else {
22231
+ if (!isImageResource$1(input.resource)) {
22232
+ logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
22233
+ continue;
22234
+ }
22235
+ imageBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
22236
+ }
22237
+ if (packedRect.rotated) imageBuffer = await encoder(imageBuffer).rotate(270).toBuffer();
22238
+ compositeInputs.push({
22239
+ input: imageBuffer,
22240
+ left: packedRect.x,
22241
+ top: packedRect.y
22242
+ });
22243
+ } catch {
22244
+ logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
22245
+ }
22246
+ }
22247
+ const outputFile = `${options.outputPath}/${atlasFileName}`;
22248
+ await encoder({ create: {
22249
+ width: page.width,
22250
+ height: page.height,
22251
+ channels: 4,
22252
+ background: {
22253
+ r: 0,
22254
+ g: 0,
22255
+ b: 0,
22256
+ alpha: 0
22257
+ }
22258
+ } }).composite(compositeInputs).toFile(outputFile);
22259
+ logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
22260
+ }
22099
22261
  function inputToCompatRect(input, index) {
22100
22262
  const duplicatePadding = isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
22101
22263
  return {
@@ -22210,14 +22372,47 @@ function resolveAtlasOutputFileName(pkg, pageIndex, branchName) {
22210
22372
  const suffix = branchName ? `_${branchName}` : "";
22211
22373
  return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
22212
22374
  }
22375
+ function resolveStandaloneAtlasOutputFileName(pkg, resource, branchName) {
22376
+ const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
22377
+ const suffix = branchName ? `_${branchName}` : "";
22378
+ if (isImageResource$1(resource)) return `${baseName}${suffix}${extname$1(resolveImageFileName$1(resource)) || ".png"}`;
22379
+ return `${baseName}${suffix}.png`;
22380
+ }
22381
+ function resolveStandaloneAtlasSize(width, height, sizeMode, options) {
22382
+ if (sizeMode === "npot") return {
22383
+ width,
22384
+ height
22385
+ };
22386
+ if (sizeMode === "multipleOf4") return {
22387
+ width: roundUpToMultiple(width, 4),
22388
+ height: roundUpToMultiple(height, 4)
22389
+ };
22390
+ return resolveDirectOutputAtlasSize(width, height, options);
22391
+ }
22213
22392
  function resolveImageFileName$1(resource) {
22214
22393
  const extras = resource.getExtras();
22215
22394
  return resource.getFileName() || extras._fileName || resource.getName();
22216
22395
  }
22396
+ function extname$1(fileName) {
22397
+ const normalized = fileName.replace(/\\/g, "/");
22398
+ const lastSlash = normalized.lastIndexOf("/");
22399
+ const lastDot = normalized.lastIndexOf(".");
22400
+ if (lastDot <= lastSlash) return "";
22401
+ return normalized.slice(lastDot);
22402
+ }
22403
+ function insertFileNameSuffix(fileName, suffix) {
22404
+ const extension = extname$1(fileName);
22405
+ if (!extension) return `${fileName}${suffix}`;
22406
+ return `${fileName.slice(0, -extension.length)}${suffix}${extension}`;
22407
+ }
22217
22408
  function nextPow2(value) {
22218
22409
  if (value <= 1) return 1;
22219
22410
  return 2 ** Math.ceil(Math.log2(value));
22220
22411
  }
22412
+ function roundUpToMultiple(value, base) {
22413
+ if (value <= 0) return 0;
22414
+ return Math.ceil(value / base) * base;
22415
+ }
22221
22416
  function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
22222
22417
  const ordered = [...resources];
22223
22418
  ordered.sort((left, right) => {
@@ -22233,6 +22428,63 @@ function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
22233
22428
  });
22234
22429
  return ordered;
22235
22430
  }
22431
+ function getResourceTextureSetMode(resource) {
22432
+ if (isImageResource$1(resource)) return parseTextureSetMode(resource.getTextureSetMode?.());
22433
+ return parseTextureSetMode(resource.getTextureSetMode?.());
22434
+ }
22435
+ function groupStandaloneInputs(doc, inputs, options) {
22436
+ const autoInputs = [];
22437
+ const fixedInputsByPage = /* @__PURE__ */ new Map();
22438
+ const standaloneGroups = /* @__PURE__ */ new Map();
22439
+ const reservedPageIndexes = /* @__PURE__ */ new Set();
22440
+ const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
22441
+ const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
22442
+ for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
22443
+ const branchOrdinalByName = /* @__PURE__ */ new Map();
22444
+ branchOrdinalByName.set("", 0);
22445
+ if (options.separatedAtlasForBranch) {
22446
+ let ordinal = 1;
22447
+ for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, ordinal++);
22448
+ } else for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, 0);
22449
+ for (const input of inputs) {
22450
+ const branchName = getInputBranchName(input);
22451
+ const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
22452
+ const mode = getResourceTextureSetMode(input.resource);
22453
+ if (mode.kind === "standalone") {
22454
+ const key = `${branchName}\u0000${getPublishedItemId(input.resource)}`;
22455
+ const existing = standaloneGroups.get(key);
22456
+ if (existing) existing.inputs.push(input);
22457
+ else standaloneGroups.set(key, {
22458
+ resource: input.resource,
22459
+ branchName,
22460
+ branchOrdinal,
22461
+ sizeMode: mode.sizeMode,
22462
+ inputs: [input]
22463
+ });
22464
+ continue;
22465
+ }
22466
+ if (mode.kind === "page") {
22467
+ reservedPageIndexes.add(mode.pageIndex);
22468
+ const key = `${branchName}\u0000${mode.pageIndex}`;
22469
+ const existing = fixedInputsByPage.get(key);
22470
+ if (existing) existing.inputs.push(input);
22471
+ else fixedInputsByPage.set(key, {
22472
+ pageIndex: mode.pageIndex,
22473
+ branchName,
22474
+ branchOrdinal,
22475
+ inputs: [input]
22476
+ });
22477
+ continue;
22478
+ }
22479
+ autoInputs.push(input);
22480
+ }
22481
+ return {
22482
+ autoInputs,
22483
+ fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || left.pageIndex - right.pageIndex),
22484
+ standaloneGroups: [...standaloneGroups.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource))),
22485
+ reservedPageIndexes
22486
+ };
22487
+ }
22236
22488
  /**
22237
22489
  * Trim transparent edges from an image using sharp.
22238
22490
  * Returns the trimmed buffer, dimensions, and offsets.
@@ -22793,6 +23045,62 @@ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
22793
23045
  }
22794
23046
  `;
22795
23047
  //#endregion
23048
+ //#region ../functions/src/plugins/loader.ts
23049
+ const importNative = new Function("id", "return import(id)");
23050
+ async function loadPlugins(doc, pluginsDir) {
23051
+ if (!pluginsDir) return [];
23052
+ const fs = await importNative("node:fs/promises");
23053
+ const path = await importNative("node:path");
23054
+ let entries;
23055
+ try {
23056
+ entries = await fs.readdir(pluginsDir, { withFileTypes: true });
23057
+ } catch {
23058
+ return [];
23059
+ }
23060
+ const plugins = [];
23061
+ for (const entry of entries) {
23062
+ if (!entry.isDirectory()) continue;
23063
+ const pluginDir = path.join(pluginsDir, entry.name);
23064
+ try {
23065
+ const manifest = await readPluginManifest(fs, path, pluginDir);
23066
+ if (!manifest) continue;
23067
+ const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
23068
+ plugins.push({
23069
+ name: manifest.name,
23070
+ plugin
23071
+ });
23072
+ } catch (error) {
23073
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
23074
+ }
23075
+ }
23076
+ return plugins;
23077
+ }
23078
+ function formatPluginError(error) {
23079
+ return error instanceof Error ? error.message : String(error);
23080
+ }
23081
+ async function readPluginManifest(fs, path, pluginDir) {
23082
+ const manifestPath = path.join(pluginDir, "package.json");
23083
+ const content = await fs.readFile(manifestPath, "utf-8");
23084
+ const manifest = JSON.parse(content);
23085
+ if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
23086
+ if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
23087
+ return manifest;
23088
+ }
23089
+ function resolvePluginMain(path, pluginDir, manifest) {
23090
+ const mainPath = path.resolve(pluginDir, manifest.main);
23091
+ const relative = path.relative(pluginDir, mainPath);
23092
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
23093
+ return mainPath;
23094
+ }
23095
+ async function loadPlugin(mainPath) {
23096
+ const mod = await createJiti(import.meta.url).import(mainPath);
23097
+ const defaultExport = mod.default;
23098
+ return isObject(defaultExport) ? defaultExport : mod;
23099
+ }
23100
+ function isObject(value) {
23101
+ return value !== null && typeof value === "object";
23102
+ }
23103
+ //#endregion
22796
23104
  //#region ../functions/src/codegen.ts
22797
23105
  const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
22798
23106
  const DEFAULT_CLASS_NAME_PREFIX = "UI_";
@@ -22828,6 +23136,18 @@ async function publishCodeGeneration(doc, options) {
22828
23136
  const logger = doc.getLogger();
22829
23137
  const settings = resolveCodeGenerationSettings(doc);
22830
23138
  if (!settings.allowGenCode) return;
23139
+ const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === "function") ?? [];
23140
+ if (plugins.length > 0) {
23141
+ let handled = false;
23142
+ for (const plugin of plugins) try {
23143
+ await plugin.plugin.genCode(doc, settings, options);
23144
+ handled = true;
23145
+ logger.info(`publish: Generated code using plugin "${plugin.name}"`);
23146
+ } catch (error) {
23147
+ logger.warn(`publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`);
23148
+ }
23149
+ if (handled) return;
23150
+ }
22831
23151
  for (const pkg of options.packages) {
22832
23152
  if (!pkg.getGenCode()) continue;
22833
23153
  const plan = resolvePackageCodegenPlan(pkg, settings, options);
@@ -22926,9 +23246,9 @@ async function cleanupGeneratedFiles(directory, fs, extension = ".cs") {
22926
23246
  }
22927
23247
  }
22928
23248
  function buildCodegenClasses(doc, pkg, plan) {
22929
- const exportedComponents = pkg.listComponents().filter((component) => component.getExported()).sort((left, right) => left.getId().localeCompare(right.getId()));
23249
+ const codegenComponents = pkg.listComponents().sort((left, right) => left.getId().localeCompare(right.getId()));
22930
23250
  const generatedById = /* @__PURE__ */ new Map();
22931
- for (const component of exportedComponents) {
23251
+ for (const component of codegenComponents) {
22932
23252
  const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || "Component"}`;
22933
23253
  generatedById.set(component.getId(), {
22934
23254
  classId: component.getId(),
@@ -22941,7 +23261,13 @@ function buildCodegenClasses(doc, pkg, plan) {
22941
23261
  members: []
22942
23262
  });
22943
23263
  }
22944
- for (const component of exportedComponents) {
23264
+ for (const component of codegenComponents) {
23265
+ const classInfo = generatedById.get(component.getId());
23266
+ if (!classInfo) continue;
23267
+ classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
23268
+ }
23269
+ for (const [componentId, classInfo] of generatedById) if (classInfo.members.every((member) => member.ignored)) generatedById.delete(componentId);
23270
+ for (const component of codegenComponents) {
22945
23271
  const classInfo = generatedById.get(component.getId());
22946
23272
  if (!classInfo) continue;
22947
23273
  classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
@@ -22955,7 +23281,12 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
22955
23281
  let childIndex = 0;
22956
23282
  let transitionIndex = 0;
22957
23283
  for (const controller of component.listControllers()) members.push(createMember(ownerType, "controller", "Controller", controller.getName(), controllerIndex++, plan));
22958
- for (const child of component.listChildren()) members.push(createMember(ownerType, "child", resolveChildType(doc, pkg, child, generatedById), child.getName(), childIndex++, plan));
23284
+ for (const child of component.listChildren()) {
23285
+ if (!isRuntimeChild(child)) continue;
23286
+ const index = childIndex++;
23287
+ const resolvedChild = resolveChildType(doc, pkg, child, generatedById);
23288
+ members.push(createMember(ownerType, "child", resolvedChild.type, child.getName(), index, plan, resolvedChild.referencedComponent));
23289
+ }
22959
23290
  for (const transition of component.listTransitions()) members.push(createMember(ownerType, "transition", "Transition", transition.getName(), transitionIndex++, plan));
22960
23291
  const usedNames = /* @__PURE__ */ new Map();
22961
23292
  for (const member of members) {
@@ -22967,7 +23298,10 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
22967
23298
  }
22968
23299
  return members;
22969
23300
  }
22970
- function createMember(ownerType, kind, type, originalName, index, plan) {
23301
+ function isRuntimeChild(child) {
23302
+ return child.propertyType !== "GGroup" || child.getAdvanced?.() === true;
23303
+ }
23304
+ function createMember(ownerType, kind, type, originalName, index, plan, referencedComponent) {
22971
23305
  const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
22972
23306
  return {
22973
23307
  index,
@@ -22975,30 +23309,41 @@ function createMember(ownerType, kind, type, originalName, index, plan) {
22975
23309
  name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
22976
23310
  originalName,
22977
23311
  type,
22978
- ignored
23312
+ ignored,
23313
+ referencedComponent
22979
23314
  };
22980
23315
  }
22981
23316
  function resolveChildType(doc, pkg, child, generatedById) {
22982
23317
  const src = child.getSrc?.();
22983
23318
  if (src) {
22984
- const localResource = resolveChildSourceComponent(doc, pkg, src);
22985
- if (localResource) return generatedById.get(localResource.getId())?.encodedClassName ?? resolveComponentBaseType(localResource);
23319
+ let referencedComponent = null;
23320
+ if (src.startsWith("ui://")) {
23321
+ const rest = src.slice(5);
23322
+ const pkgId = rest.slice(0, 8);
23323
+ const resourceId = rest.slice(8);
23324
+ const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
23325
+ const targetResource = targetPackage?.getResourceById(resourceId);
23326
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
23327
+ component: targetResource,
23328
+ package: targetPackage
23329
+ };
23330
+ } else {
23331
+ const packageId = child.getPackageId?.();
23332
+ const targetPackage = packageId ? doc.getRoot().listPackages().find((candidate) => candidate.getId() === packageId) : pkg;
23333
+ const targetResource = targetPackage?.getResourceById(src);
23334
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
23335
+ component: targetResource,
23336
+ package: targetPackage
23337
+ };
23338
+ }
23339
+ if (referencedComponent) return {
23340
+ type: (referencedComponent.package === pkg ? generatedById.get(referencedComponent.component.getId()) : void 0)?.encodedClassName ?? resolveComponentBaseType(referencedComponent.component),
23341
+ referencedComponent
23342
+ };
22986
23343
  }
22987
23344
  const instanceExtType = child.getInstanceExtType?.();
22988
- if (instanceExtType) return `G${instanceExtType}`;
22989
- return child.propertyType;
22990
- }
22991
- function resolveChildSourceComponent(doc, pkg, src) {
22992
- if (!src) return null;
22993
- if (src.startsWith("ui://")) {
22994
- const rest = src.slice(5);
22995
- const pkgId = rest.slice(0, 8);
22996
- const resourceId = rest.slice(8);
22997
- const targetResource = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId)?.getResourceById(resourceId);
22998
- return targetResource?.propertyType === "Component" ? targetResource : null;
22999
- }
23000
- const localResource = pkg.getResourceById(src);
23001
- return localResource?.propertyType === "Component" ? localResource : null;
23345
+ if (instanceExtType) return { type: `G${instanceExtType}` };
23346
+ return { type: child.propertyType };
23002
23347
  }
23003
23348
  function resolveComponentBaseType(component) {
23004
23349
  const extensionType = component.getExtensionType();
@@ -23069,21 +23414,21 @@ function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant)
23069
23414
  return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
23070
23415
  }
23071
23416
  function resolveCodePath(codePath, basePath, fs) {
23072
- if (isAbsolutePath(codePath)) return trimTrailingSlashes$1(codePath);
23417
+ if (isAbsolutePath(codePath)) return trimTrailingSlashes$2(codePath);
23073
23418
  const projectBasePath = resolveProjectBasePath(basePath);
23074
- return projectBasePath ? trimTrailingSlashes$1(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$1(codePath);
23419
+ return projectBasePath ? trimTrailingSlashes$2(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$2(codePath);
23075
23420
  }
23076
23421
  function resolveProjectBasePath(basePath) {
23077
23422
  if (!basePath) return "";
23078
- const normalized = trimTrailingSlashes$1(basePath);
23423
+ const normalized = trimTrailingSlashes$2(basePath);
23079
23424
  const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
23080
23425
  if (assetsMatch?.[1]) return assetsMatch[1];
23081
23426
  return dirname$2(normalized);
23082
23427
  }
23083
23428
  function dirname$2(filePath) {
23084
- return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23429
+ return trimTrailingSlashes$2(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23085
23430
  }
23086
- function trimTrailingSlashes$1(value) {
23431
+ function trimTrailingSlashes$2(value) {
23087
23432
  return value.replace(/[/\\]+$/, "");
23088
23433
  }
23089
23434
  function isAbsolutePath(value) {
@@ -23092,9 +23437,15 @@ function isAbsolutePath(value) {
23092
23437
  function isDefaultMemberName(ownerType, kind, name) {
23093
23438
  if (kind === "controller") return (ownerType === "GButton" || ownerType === "GComboBox") && name === "button";
23094
23439
  if (kind === "transition") return false;
23095
- if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") return name === "title" || name === "icon";
23096
- if (ownerType === "GProgressBar") return name === "bar" || name === "bar_v" || name === "title" || name === "ani";
23097
- if (ownerType === "GSlider") return name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani";
23440
+ if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") {
23441
+ if (name === "title" || name === "icon") return true;
23442
+ }
23443
+ if (ownerType === "GProgressBar") {
23444
+ if (name === "bar" || name === "bar_v" || name === "title" || name === "ani") return true;
23445
+ }
23446
+ if (ownerType === "GSlider") {
23447
+ if (name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani") return true;
23448
+ }
23098
23449
  return /^n\d+(?:_.*)?$/i.test(name);
23099
23450
  }
23100
23451
  function applyMemberNamePrefix(name, prefix) {
@@ -23383,11 +23734,11 @@ function inferPackageName(fileName) {
23383
23734
  if (/\.fui$/i.test(fileName)) return fileName.replace(/\.fui$/i, "");
23384
23735
  return fileName.replace(/\.bin$/i, "");
23385
23736
  }
23386
- function trimTrailingSlashes(value) {
23737
+ function trimTrailingSlashes$1(value) {
23387
23738
  return value.replace(/[/\\]+$/, "");
23388
23739
  }
23389
23740
  function normalizeComparablePath(value) {
23390
- const normalized = trimTrailingSlashes(value).replace(/\\/g, "/");
23741
+ const normalized = trimTrailingSlashes$1(value).replace(/\\/g, "/");
23391
23742
  const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
23392
23743
  const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
23393
23744
  const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
@@ -23407,14 +23758,14 @@ function normalizeComparablePath(value) {
23407
23758
  return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
23408
23759
  }
23409
23760
  function dirname$1(filePath) {
23410
- return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23761
+ return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23411
23762
  }
23412
23763
  function basename(filePath) {
23413
- return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
23764
+ return trimTrailingSlashes$1(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
23414
23765
  }
23415
23766
  function resolveOutputProjectPath(output, fs) {
23416
23767
  if (/\.fairy$/i.test(output)) return output;
23417
- const normalizedOutput = trimTrailingSlashes(output);
23768
+ const normalizedOutput = trimTrailingSlashes$1(output);
23418
23769
  const projectName = basename(normalizedOutput) || "Restored";
23419
23770
  return fs.join(normalizedOutput, `${projectName}.fairy`);
23420
23771
  }
@@ -23460,7 +23811,7 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
23460
23811
  await fs.mkdir(outputDir);
23461
23812
  }
23462
23813
  async function restore(options) {
23463
- const sourceDir = trimTrailingSlashes(options.inputDir);
23814
+ const sourceDir = trimTrailingSlashes$1(options.inputDir);
23464
23815
  const outputIsProjectFile = /\.fairy$/i.test(options.output);
23465
23816
  const outputProjectPath = resolveOutputProjectPath(options.output, options.fs);
23466
23817
  await prepareRestoreOutputDir(sourceDir, dirname$1(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
@@ -23602,16 +23953,23 @@ var RestoreWorkflow = class {
23602
23953
  async _ensureLooseImageResource(doc, pkg, owner, sourceDir, fileName) {
23603
23954
  const resources = pkg.listResources();
23604
23955
  const existing = this._findResourceByFile(resources, owner, "ImageResource", fileName);
23605
- if (existing) return existing;
23606
23956
  const sourcePath = await this._resolveLooseSourceFile(pkg, sourceDir, fileName);
23607
- if (!sourcePath) return null;
23957
+ if (!sourcePath) return existing ?? null;
23958
+ if (existing) {
23959
+ existing.setExtras?.({
23960
+ ...existing.getExtras?.() ?? {},
23961
+ _publishedFile: fileBaseName(sourcePath),
23962
+ _restoreAsLooseImage: true
23963
+ });
23964
+ return existing;
23965
+ }
23608
23966
  const resource = doc.createImageResource(stripExtension(fileName));
23609
23967
  resource.setId(generateId()).setPath(owner.getPath?.() ?? "/").setBranch(owner.getBranch?.() ?? "").setBranchItemIds(owner.getBranchItemIds?.() ?? []).setExported(false).setFileName(fileName);
23610
23968
  resource.setExtras?.({
23611
23969
  ...resource.getExtras?.() ?? {},
23612
23970
  _publishedFile: fileBaseName(sourcePath),
23613
23971
  _suppressPackageSize: true,
23614
- _syntheticLooseImage: true
23972
+ _restoreAsLooseImage: true
23615
23973
  });
23616
23974
  pkg.addResource(resource);
23617
23975
  return resource;
@@ -23785,13 +24143,13 @@ var RestoreWorkflow = class {
23785
24143
  }
23786
24144
  async _copyLooseResources(pkg, options, warnings) {
23787
24145
  for (const resource of pkg.listResources()) {
23788
- const syntheticLooseImage = resource.getExtras?.()?._syntheticLooseImage === true;
24146
+ const restoreAsLooseImage = resource.getExtras?.()?._restoreAsLooseImage === true;
23789
24147
  if (![
23790
24148
  "SoundResource",
23791
24149
  "MiscResource",
23792
24150
  "SpineResource",
23793
24151
  "DragonBonesResource"
23794
- ].includes(resource.propertyType) && !syntheticLooseImage) continue;
24152
+ ].includes(resource.propertyType) && !restoreAsLooseImage) continue;
23795
24153
  const fileName = resourceFileName(resource);
23796
24154
  if (!fileName) continue;
23797
24155
  const sourcePath = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, resourcePublishedFileName(resource), fileName));
@@ -23961,6 +24319,26 @@ var RestoreWorkflow = class {
23961
24319
  };
23962
24320
  //#endregion
23963
24321
  //#region ../functions/src/publish.ts
24322
+ async function runPublishPluginHook(plugins, hook, doc, options) {
24323
+ const logger = doc.getLogger();
24324
+ for (const plugin of plugins) {
24325
+ const fn = plugin.plugin[hook];
24326
+ if (typeof fn !== "function") continue;
24327
+ try {
24328
+ await fn(doc, options);
24329
+ } catch (error) {
24330
+ logger.warn(`publish: Plugin "${plugin.name}" ${hook} failed: ${formatPluginError(error)}`);
24331
+ }
24332
+ }
24333
+ }
24334
+ function resolvePublishPluginsDir(doc, options) {
24335
+ const fs = options.fs;
24336
+ const projectDir = doc.getProjectDir?.() ?? "";
24337
+ if (projectDir) return fs?.join ? fs.join(projectDir, "plugins") : `${projectDir.replace(/[/\\]+$/, "")}/plugins`;
24338
+ const projectBasePath = resolveProjectBasePath(options.basePath);
24339
+ if (!projectBasePath) return "";
24340
+ return fs?.join ? fs.join(projectBasePath, "plugins") : `${projectBasePath.replace(/[/\\]+$/, "")}/plugins`;
24341
+ }
23964
24342
  const UNITY_PROJECT_TYPE = ProjectType.Unity;
23965
24343
  const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
23966
24344
  function resolveDefaultPublishFileExtension(projectType, publishSettings) {
@@ -24010,6 +24388,19 @@ function resolvePublishOptions(doc, overrides = {}) {
24010
24388
  atlas: atlasOptions
24011
24389
  };
24012
24390
  }
24391
+ function trimTrailingSlashes(value) {
24392
+ return value.replace(/[/\\]+$/, "");
24393
+ }
24394
+ function isAbsolutePathLike(value) {
24395
+ return /^(?:[a-zA-Z]:[/\\]|[/\\]{1,2})/u.test(value);
24396
+ }
24397
+ function joinPathSegments(left, right) {
24398
+ const normalizedLeft = trimTrailingSlashes(left);
24399
+ const normalizedRight = right.replace(/^[/\\]+/, "");
24400
+ if (!normalizedLeft) return normalizedRight;
24401
+ if (!normalizedRight) return normalizedLeft;
24402
+ return `${normalizedLeft}${normalizedLeft.includes("\\") ? "\\" : "/"}${normalizedRight}`;
24403
+ }
24013
24404
  function dirname(filePath) {
24014
24405
  return filePath.replace(/[/\\]+$/, "").match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
24015
24406
  }
@@ -24125,13 +24516,14 @@ function extname(fileName) {
24125
24516
  if (lastDot <= lastSlash) return "";
24126
24517
  return normalized.slice(lastDot);
24127
24518
  }
24128
- function resolvePublishedMiscFileName(resource) {
24519
+ function resolvePublishedMiscFileName(resource, projectType) {
24129
24520
  const file = resource.getFile();
24521
+ if (projectType !== UNITY_PROJECT_TYPE) return file;
24130
24522
  if (file.toLowerCase().endsWith(".atlas")) return `${file}.txt`;
24131
24523
  return file;
24132
24524
  }
24133
- function resolvePublishedSkeletonFileName(resource) {
24134
- if (isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
24525
+ function resolvePublishedSkeletonFileName(resource, projectType) {
24526
+ if (projectType === UNITY_PROJECT_TYPE && isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
24135
24527
  return resource.getFile();
24136
24528
  }
24137
24529
  function setPublishedFileExtra(resource, fileName) {
@@ -24234,6 +24626,24 @@ function collectPackagePublishContext(pkg, options) {
24234
24626
  const referencedIds = /* @__PURE__ */ new Set();
24235
24627
  const pixelHitTestImageIds = /* @__PURE__ */ new Set();
24236
24628
  const spriteItemIds = /* @__PURE__ */ new Set();
24629
+ const collectExportedResourceIds = (sourceResources, sourcePublishedResourceIds) => {
24630
+ const exportedResourceIds = new Set(sourcePublishedResourceIds);
24631
+ const resourcesById = new Map(sourceResources.map((resource) => [resource.getId(), resource]));
24632
+ let changed = true;
24633
+ while (changed) {
24634
+ changed = false;
24635
+ for (const resourceId of [...exportedResourceIds]) {
24636
+ const resource = resourcesById.get(resourceId);
24637
+ if (!resource || !isSkeletonResource(resource)) continue;
24638
+ for (const requiredId of resource.getRequireIds()) {
24639
+ if (!requiredId || exportedResourceIds.has(requiredId)) continue;
24640
+ exportedResourceIds.add(requiredId);
24641
+ changed = true;
24642
+ }
24643
+ }
24644
+ }
24645
+ return exportedResourceIds;
24646
+ };
24237
24647
  for (const atlas of pkg.listAtlases()) for (const sprite of atlas.listSprites()) spriteItemIds.add(sprite.getItemId());
24238
24648
  for (const resource of resources) {
24239
24649
  if (!isComponentResource(resource)) continue;
@@ -24260,6 +24670,7 @@ function collectPackagePublishContext(pkg, options) {
24260
24670
  child.getSelectedIcon?.(),
24261
24671
  child.getDropdown?.(),
24262
24672
  child.getSound?.(),
24673
+ child.getInstanceSound?.(),
24263
24674
  child.getInstanceIcon?.(),
24264
24675
  child.getInstanceSelectedIcon?.(),
24265
24676
  child.getVtScrollBarRes?.(),
@@ -24317,19 +24728,7 @@ function collectPackagePublishContext(pkg, options) {
24317
24728
  }
24318
24729
  if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
24319
24730
  }
24320
- let changed = true;
24321
- while (changed) {
24322
- changed = false;
24323
- for (const resource of resources) {
24324
- if (!isSkeletonResource(resource)) continue;
24325
- if (!publishedResourceIds.has(resource.getId())) continue;
24326
- for (const requiredId of resource.getRequireIds()) {
24327
- if (!requiredId || publishedResourceIds.has(requiredId)) continue;
24328
- publishedResourceIds.add(requiredId);
24329
- changed = true;
24330
- }
24331
- }
24332
- }
24731
+ for (const resourceId of collectExportedResourceIds(resources, publishedResourceIds)) publishedResourceIds.add(resourceId);
24333
24732
  const highResolutionItemIds = collectHighResolutionItemIds(resources, publishedResourceIds, options.includeHighResolution);
24334
24733
  if (!options.includeBranches) {
24335
24734
  const mainByKey = /* @__PURE__ */ new Map();
@@ -24378,6 +24777,7 @@ function collectPackagePublishContext(pkg, options) {
24378
24777
  return {
24379
24778
  referencedIds,
24380
24779
  publishedResourceIds,
24780
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
24381
24781
  pixelHitTestImageIds,
24382
24782
  highResolutionItemIds,
24383
24783
  effectiveResourceIds,
@@ -24387,6 +24787,7 @@ function collectPackagePublishContext(pkg, options) {
24387
24787
  return {
24388
24788
  referencedIds,
24389
24789
  publishedResourceIds,
24790
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
24390
24791
  pixelHitTestImageIds,
24391
24792
  highResolutionItemIds,
24392
24793
  effectiveResourceIds: new Map([...publishedResourceIds].map((resourceId) => [resourceId, resourceId])),
@@ -24437,7 +24838,7 @@ async function applyPixelHitTests(pkg, imageIds, basePath, encoder) {
24437
24838
  }
24438
24839
  }
24439
24840
  async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options) {
24440
- const { publishedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
24841
+ const { publishedResourceIds, exportedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
24441
24842
  for (const resource of pkg.listResources()) {
24442
24843
  setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
24443
24844
  if (isHighResolutionResource(resource)) resource.setHighResolutionItemIds(highResolutionItemIds.get(resource.getId()) ?? []);
@@ -24447,21 +24848,26 @@ async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options)
24447
24848
  pkg.setExtras({
24448
24849
  ...extras,
24449
24850
  publishedResourceIds: [...publishedResourceIds].sort((a, b) => a.localeCompare(b)),
24851
+ exportedResourceIds: [...exportedResourceIds].sort((a, b) => a.localeCompare(b)),
24450
24852
  publishedIncludeBranches: includeBranches,
24451
24853
  publishedEffectiveResourceIds: Object.fromEntries(effectiveResourceIds)
24452
24854
  });
24453
24855
  for (const resource of pkg.listResources()) {
24454
24856
  if (isMiscResource(resource)) {
24455
- setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource));
24857
+ setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
24456
24858
  continue;
24457
24859
  }
24458
- if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource));
24860
+ if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
24459
24861
  }
24460
24862
  }
24461
24863
  function getAnnotatedPublishedResourceIds(pkg) {
24462
24864
  const extras = pkg.getExtras() ?? {};
24463
24865
  return new Set(extras.publishedResourceIds ?? []);
24464
24866
  }
24867
+ function getAnnotatedExportedResourceIds(pkg) {
24868
+ const extras = pkg.getExtras() ?? {};
24869
+ return new Set(extras.exportedResourceIds ?? []);
24870
+ }
24465
24871
  function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
24466
24872
  const imageIds = /* @__PURE__ */ new Set();
24467
24873
  const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource]));
@@ -24500,18 +24906,18 @@ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, lo
24500
24906
  }
24501
24907
  }
24502
24908
  async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw, logger) {
24503
- const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
24504
- const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds);
24505
- if (publishedResourceIds.size === 0) return;
24909
+ const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
24910
+ const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
24911
+ if (exportedResourceIds.size === 0) return;
24506
24912
  if (!basePath || !readFileRaw) {
24507
24913
  if (pkg.listResources().some((resource) => {
24508
- return (isMiscResource(resource) || isSkeletonResource(resource)) && publishedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
24914
+ return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
24509
24915
  })) logger.warn(`publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
24510
24916
  return;
24511
24917
  }
24512
24918
  for (const resource of pkg.listResources()) {
24513
24919
  const resourceId = resource.getId();
24514
- const isSkeletonExternal = publishedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
24920
+ const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
24515
24921
  const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
24516
24922
  if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
24517
24923
  let sourcePath;
@@ -24557,16 +24963,102 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
24557
24963
  */
24558
24964
  function publish(options) {
24559
24965
  return createTransform("publish", async (doc) => {
24966
+ const resolveConfiguredOutputPath = (value, projectBasePath) => {
24967
+ const trimmed = value?.trim();
24968
+ if (!trimmed) return void 0;
24969
+ if (isAbsolutePathLike(trimmed) || !projectBasePath) return trimTrailingSlashes(trimmed);
24970
+ return trimTrailingSlashes(options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed));
24971
+ };
24972
+ const resolveProjectPublishConfig = () => {
24973
+ const publishSettings = (doc.getRoot().getSettings?.() ?? {}).publish ?? {};
24974
+ const resolved = resolvePublishOptions(doc, {
24975
+ compressed: options.compressed,
24976
+ fileExtension: options.fileExtension,
24977
+ packages: options.packages,
24978
+ atlas: options.atlas
24979
+ });
24980
+ const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
24981
+ return {
24982
+ ...resolved,
24983
+ projectType: doc.getRoot().getProjectType(),
24984
+ includeBranches,
24985
+ activeBranch: includeBranches ? "" : options.branch ?? "",
24986
+ includeHighResolution: publishSettings.includeHighResolution ?? 0,
24987
+ separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
24988
+ globalOutputPath: publishSettings.path?.trim() ?? "",
24989
+ globalBranchOutputPath: publishSettings.branchPath?.trim() ?? ""
24990
+ };
24991
+ };
24992
+ const resolvePackagePublishPlan = (pkg, config, projectBasePath) => {
24993
+ let outputDir;
24994
+ if (options.output) outputDir = trimTrailingSlashes(options.output);
24995
+ else {
24996
+ const candidates = [];
24997
+ if (!config.includeBranches && config.activeBranch) candidates.push(pkg.getPublishBranchPath(), config.globalBranchOutputPath);
24998
+ candidates.push(pkg.getPublishPath(), config.globalOutputPath);
24999
+ for (const candidate of candidates) {
25000
+ const resolved = resolveConfiguredOutputPath(candidate, projectBasePath);
25001
+ if (!resolved) continue;
25002
+ outputDir = resolved;
25003
+ break;
25004
+ }
25005
+ }
25006
+ const publishName = pkg.getPublishName() || pkg.getName();
25007
+ return {
25008
+ pkg,
25009
+ outputDir,
25010
+ publishName,
25011
+ fileName: resolvePublishFileName(publishName, config.fileExtension),
25012
+ compressed: config.compressed,
25013
+ fileExtension: config.fileExtension,
25014
+ includeBranches: config.includeBranches,
25015
+ activeBranch: config.activeBranch,
25016
+ includeHighResolution: config.includeHighResolution,
25017
+ separatedAtlasForBranch: config.separatedAtlasForBranch,
25018
+ atlas: config.atlas
25019
+ };
25020
+ };
25021
+ const createNoopPublishFs = () => ({
25022
+ async writeFileRaw() {},
25023
+ async mkdir() {},
25024
+ join(...paths) {
25025
+ return paths.join("/");
25026
+ }
25027
+ });
25028
+ const publishPackage = async (plan, writerFs, packageIndex) => {
25029
+ const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
25030
+ await atlas({
25031
+ ...plan.atlas,
25032
+ ...options.atlas ?? {},
25033
+ separatedAtlasForBranch: plan.separatedAtlasForBranch,
25034
+ encoder: options.encoder,
25035
+ basePath: options.basePath,
25036
+ outputPath: options.fs ? plan.outputDir : void 0,
25037
+ mkdir: options.fs ? options.fs.mkdir : void 0,
25038
+ readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
25039
+ packages: [plan.pkg.getName()],
25040
+ ...atlasRuntimeOptions
25041
+ })(doc);
25042
+ if (!options.fs) return;
25043
+ if (!plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
25044
+ await options.fs.mkdir(plan.outputDir);
25045
+ const filePath = options.fs.join(plan.outputDir, plan.fileName);
25046
+ const bwOptions = {
25047
+ compressed: plan.compressed,
25048
+ packageIndex
25049
+ };
25050
+ await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
25051
+ await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
25052
+ await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
25053
+ logger.info(`publish: Written ${plan.fileName}`);
25054
+ };
24560
25055
  const root = doc.getRoot();
24561
25056
  const logger = doc.getLogger();
24562
- const publishSettings = (root.getSettings?.() ?? {}).publish ?? {};
24563
- const resolved = resolvePublishOptions(doc, {
24564
- compressed: options.compressed,
24565
- fileExtension: options.fileExtension,
24566
- packages: options.packages,
24567
- atlas: options.atlas
24568
- });
24569
- const ext = resolved.fileExtension;
25057
+ const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || "";
25058
+ const pluginsDir = resolvePublishPluginsDir(doc, options);
25059
+ const plugins = pluginsDir ? await loadPlugins(doc, pluginsDir) : [];
25060
+ await runPublishPluginHook(plugins, "onPublishStart", doc, options);
25061
+ const resolved = resolveProjectPublishConfig();
24570
25062
  let allPackages = root.listPackages();
24571
25063
  if (resolved.packages && resolved.packages.length > 0) {
24572
25064
  const names = new Set(resolved.packages);
@@ -24574,59 +25066,42 @@ function publish(options) {
24574
25066
  }
24575
25067
  if (allPackages.length === 0) {
24576
25068
  logger.warn("publish: No packages to publish.");
25069
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
24577
25070
  return;
24578
25071
  }
24579
- const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
24580
- const activeBranch = includeBranches ? "" : options.branch ?? "";
24581
- const includeHighResolution = publishSettings.includeHighResolution ?? 0;
24582
- const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(ext);
24583
25072
  const allDocPackages = root.listPackages();
24584
25073
  const pkgMap = /* @__PURE__ */ new Map();
24585
25074
  for (const p of allDocPackages) pkgMap.set(p.getId(), p);
24586
25075
  for (const pkg of allPackages) {
24587
- _computeDependencies(pkg, pkgMap);
25076
+ _computeDependencies(doc, pkg, pkgMap);
24588
25077
  await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
24589
- includeBranches,
24590
- activeBranch,
24591
- includeHighResolution
25078
+ projectType: resolved.projectType,
25079
+ includeBranches: resolved.includeBranches,
25080
+ activeBranch: resolved.activeBranch,
25081
+ includeHighResolution: resolved.includeHighResolution
24592
25082
  });
24593
25083
  }
24594
- await atlas({
24595
- ...resolved.atlas,
24596
- ...options.atlas ?? {},
24597
- separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
24598
- encoder: options.encoder,
24599
- basePath: options.basePath,
24600
- outputPath: options.fs ? options.output : void 0,
24601
- mkdir: options.fs ? options.fs.mkdir : void 0,
24602
- readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
24603
- ...atlasRuntimeOptions
24604
- })(doc);
25084
+ const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
24605
25085
  if (!options.fs) {
24606
25086
  logger.info(`publish: No fs provided — layout computed for ${allPackages.length} package(s), skipping file output.`);
25087
+ const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
25088
+ for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
25089
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
24607
25090
  return;
24608
25091
  }
24609
- await options.fs.mkdir(options.output);
25092
+ const unresolvedPlan = plans.find((plan) => !plan.outputDir);
25093
+ if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
24610
25094
  const writerFs = toBinaryWriterFileSystem(options.fs);
24611
- for (const pkg of allPackages) {
24612
- const pkgIndex = allDocPackages.indexOf(pkg);
24613
- const fileName = resolvePublishFileName(pkg.getPublishName() || pkg.getName(), ext);
24614
- const filePath = options.fs.join(options.output, fileName);
24615
- const bwOptions = {
24616
- compressed: resolved.compressed,
24617
- packageIndex: pkgIndex
24618
- };
24619
- await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
24620
- await exportPackageSounds(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
24621
- await exportPackageExternalResources(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
24622
- logger.info(`publish: Written ${fileName}`);
24623
- }
25095
+ for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
24624
25096
  await publishCodeGeneration(doc, {
24625
25097
  basePath: options.basePath,
24626
25098
  fs: options.fs,
24627
- packages: allPackages
25099
+ packages: allPackages,
25100
+ plugins
24628
25101
  });
24629
- logger.info(`publish: Published ${allPackages.length} package(s) to ${options.output}`);
25102
+ const publishedTargets = [...new Set(plans.map((plan) => plan.outputDir).filter((value) => Boolean(value)))];
25103
+ logger.info(publishedTargets.length > 0 ? `publish: Published ${allPackages.length} package(s) to ${publishedTargets.join(", ")}` : `publish: Published ${allPackages.length} package(s)`);
25104
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
24630
25105
  });
24631
25106
  }
24632
25107
  /**
@@ -24634,25 +25109,104 @@ function publish(options) {
24634
25109
  * The editor only adds dependencies for packages referenced via bitmap font URLs.
24635
25110
  * @internal
24636
25111
  */
24637
- function _computeDependencies(pkg, pkgMap) {
25112
+ function _computeDependencies(doc, pkg, pkgMap) {
24638
25113
  const referencedPkgIds = /* @__PURE__ */ new Set();
24639
- function scanFontUrl(font) {
24640
- if (!font) return;
24641
- const fontStr = Array.isArray(font) ? font[0] : String(font);
24642
- if (typeof fontStr !== "string" || !fontStr.startsWith("ui://")) return;
24643
- const rest = fontStr.slice(5);
24644
- if (rest.length >= 8) {
24645
- const depPkgId = rest.slice(0, 8);
24646
- if (depPkgId !== pkg.getId()) referencedPkgIds.add(depPkgId);
25114
+ const pkgId = pkg.getId();
25115
+ const packageOrder = new Map(doc.getRoot().listPackages().map((entry, index) => [entry.getId(), index]));
25116
+ const addDependencyPackageId = (dependencyPkgId) => {
25117
+ const normalized = dependencyPkgId?.trim() ?? "";
25118
+ if (!normalized || normalized === pkgId) return;
25119
+ referencedPkgIds.add(normalized);
25120
+ };
25121
+ const extractPackageIdFromUiUrl = (value) => {
25122
+ if (!value.startsWith("ui://")) return null;
25123
+ const rest = value.slice(5);
25124
+ if (!rest) return null;
25125
+ const slashIndex = rest.indexOf("/");
25126
+ if (slashIndex >= 0) return rest.slice(0, slashIndex) || null;
25127
+ if (rest.length >= 8) return rest.slice(0, 8);
25128
+ return null;
25129
+ };
25130
+ const addDependencyPackageIdFromUiValue = (value) => {
25131
+ if (!value || typeof value !== "string") return;
25132
+ addDependencyPackageId(extractPackageIdFromUiUrl(value));
25133
+ };
25134
+ const addDependencyPackageIdsFromText = (value) => {
25135
+ if (!value || typeof value !== "string") return;
25136
+ const matches = value.matchAll(/ui:\/\/([0-9a-z]{8})/giu);
25137
+ for (const match of matches) addDependencyPackageId(match[1] ?? "");
25138
+ };
25139
+ const addDependencyPackageIdsFromUnknown = (value) => {
25140
+ if (Array.isArray(value)) {
25141
+ for (const entry of value) addDependencyPackageIdsFromUnknown(entry);
25142
+ return;
24647
25143
  }
24648
- }
25144
+ if (typeof value === "string") {
25145
+ addDependencyPackageIdFromUiValue(value);
25146
+ addDependencyPackageIdsFromText(value);
25147
+ }
25148
+ };
25149
+ const addDependencyFontRef = (value) => {
25150
+ if (Array.isArray(value)) {
25151
+ for (const entry of value) addDependencyPackageIdFromUiValue(entry);
25152
+ return;
25153
+ }
25154
+ addDependencyPackageIdFromUiValue(value ?? void 0);
25155
+ };
24649
25156
  for (const res of pkg.listResources()) {
24650
25157
  if (res.propertyType !== "Component") continue;
24651
- for (const child of res.listChildren?.() ?? []) scanFontUrl(child.getFont?.());
25158
+ const component = res;
25159
+ for (const child of component.listChildren?.() ?? []) {
25160
+ addDependencyPackageId(child.getPackageId?.());
25161
+ addDependencyFontRef(child.getFont?.());
25162
+ addDependencyPackageIdsFromText(child.getText?.());
25163
+ for (const ref of [
25164
+ child.getUrl?.(),
25165
+ child.getDefaultItem?.(),
25166
+ child.getIcon?.(),
25167
+ child.getSelectedIcon?.(),
25168
+ child.getDropdown?.(),
25169
+ child.getSound?.(),
25170
+ child.getInstanceSound?.(),
25171
+ child.getInstanceIcon?.(),
25172
+ child.getInstanceSelectedIcon?.(),
25173
+ child.getVtScrollBarRes?.(),
25174
+ child.getHzScrollBarRes?.(),
25175
+ child.getHeaderRes?.(),
25176
+ child.getFooterRes?.()
25177
+ ]) addDependencyPackageIdFromUiValue(ref);
25178
+ for (const item of child.getInstanceComboItems?.() ?? []) addDependencyPackageIdFromUiValue(item.icon ?? void 0);
25179
+ for (const item of child.getListItems?.() ?? []) {
25180
+ addDependencyPackageIdFromUiValue(item.icon ?? void 0);
25181
+ addDependencyPackageIdFromUiValue(item.url ?? void 0);
25182
+ }
25183
+ for (const gear of child.listGears?.() ?? []) {
25184
+ addDependencyPackageIdsFromUnknown(gear.getValues?.());
25185
+ addDependencyPackageIdsFromUnknown(gear.getDefaultValue?.());
25186
+ }
25187
+ }
25188
+ addDependencyFontRef(component.getFont?.());
25189
+ for (const ref of [
25190
+ component.getDropdown?.(),
25191
+ component.getHeaderRes?.(),
25192
+ component.getFooterRes?.(),
25193
+ component.getVtScrollBarRes?.(),
25194
+ component.getHzScrollBarRes?.(),
25195
+ component.getSound?.()
25196
+ ]) addDependencyPackageIdFromUiValue(ref);
25197
+ for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
25198
+ addDependencyPackageIdsFromUnknown(item.getStartValue?.());
25199
+ addDependencyPackageIdsFromUnknown(item.getEndValue?.());
25200
+ }
24652
25201
  }
24653
25202
  for (const dep of pkg.listDependencies()) pkg.removeDependency(dep);
24654
25203
  if (referencedPkgIds.size > 0) {
24655
- const sortedIds = [...referencedPkgIds].sort((a, b) => a.localeCompare(b));
25204
+ const sortedIds = [...referencedPkgIds].sort((a, b) => {
25205
+ const orderA = packageOrder.get(a) ?? Number.MAX_SAFE_INTEGER;
25206
+ const orderB = packageOrder.get(b) ?? Number.MAX_SAFE_INTEGER;
25207
+ if (orderA !== orderB) return orderA - orderB;
25208
+ return a.localeCompare(b);
25209
+ });
24656
25210
  for (const refId of sortedIds) {
24657
25211
  const depPkg = pkgMap.get(refId);
24658
25212
  if (depPkg) pkg.addDependency(depPkg);
@@ -24660,98 +25214,238 @@ function _computeDependencies(pkg, pkgMap) {
24660
25214
  }
24661
25215
  }
24662
25216
  //#endregion
24663
- //#region src/cli.ts
24664
- const HELP = `
24665
- ofgui FairyGUI Headless Authoring CLI
24666
-
24667
- Alias:
24668
- openfairygui
24669
-
24670
- Commands:
24671
- inspect <project-dir> Show project contents report
24672
- publish <project-dir> --output <dir> [options] Publish project to binary outputs and configured generated code
24673
- restore <release-dir> --output <dir> [options] Restore a FairyGUI project from published binaries
24674
- backend-capabilities <project-dir> Open a backend session, print runtime capabilities, then close it
24675
-
24676
- Publish options:
24677
- --output, -o <dir> Output directory (required)
24678
- --compressed Compress binary data (overrides project setting)
24679
- --packages <a,b,c> Only publish specific packages (comma-separated)
24680
- --branch <name> Active branch used by "主干合并活跃分支"; omit for main branch
24681
- --project-type <name|id> Override project type (for example: unity, layabox, cocoscreator, 0, 4, 3)
24682
-
24683
- Restore options:
24684
- --output, -o <dir> Output project directory (required)
24685
- --packages <a,b,c> Only restore specific packages (comma-separated)
24686
- --force Overwrite a non-empty output directory
24687
- --project-type <name|id> Override restored project type; default is unity
24688
-
24689
- Options:
24690
- --help, -h Show this help
24691
- --version, -v Show version
24692
-
24693
- Input can be a .fairy file or a project root directory (auto-discovers .fairy file).
24694
- File extension and binary format are read from project settings.
24695
- `;
24696
- const require = createRequire(import.meta.url);
24697
- function getInjectedPackageVersion() {
24698
- const version = import.meta.env?.PACKAGE_VERSION;
24699
- return typeof version === "string" && version.length > 0 ? version : null;
24700
- }
24701
- function readPackageVersion() {
24702
- const injectedVersion = getInjectedPackageVersion();
24703
- if (injectedVersion) return injectedVersion;
24704
- try {
24705
- const pkg = require("../package.json");
24706
- if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
24707
- } catch {}
24708
- return "0.0.0-dev";
24709
- }
24710
- const PACKAGE_VERSION = readPackageVersion();
25217
+ //#region ../core/src/io/node-io.ts
25218
+ /**
25219
+ * Node.js I/O implementation for reading and writing FairyGUI projects.
25220
+ *
25221
+ * Usage:
25222
+ *
25223
+ * ```ts
25224
+ * import { NodeIO } from '@openfairygui/core/node';
25225
+ *
25226
+ * const io = new NodeIO();
25227
+ * const doc = await io.readProject('./path/to/project.fairy');
25228
+ * await io.writeProject(doc, './path/to/output.fairy');
25229
+ * const doc2 = await io.readBinary('./path/to/package_fui.bytes');
25230
+ * ```
25231
+ *
25232
+ * @category I/O
25233
+ */
25234
+ var NodeIO = class extends PlatformIO {
25235
+ createFileSystem() {
25236
+ return {
25237
+ async readFile(filePath) {
25238
+ return fs$1.readFile(filePath, "utf-8");
25239
+ },
25240
+ async readFileRaw(filePath) {
25241
+ const buf = await fs$1.readFile(filePath);
25242
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25243
+ },
25244
+ async writeFile(filePath, content) {
25245
+ await fs$1.writeFile(filePath, content, "utf-8");
25246
+ },
25247
+ async writeFileRaw(filePath, data) {
25248
+ await fs$1.writeFile(filePath, data);
25249
+ },
25250
+ async mkdir(dirPath) {
25251
+ await fs$1.mkdir(dirPath, { recursive: true });
25252
+ },
25253
+ async readdir(dirPath) {
25254
+ const entries = await fs$1.readdir(dirPath, { withFileTypes: true });
25255
+ return (await Promise.all(entries.map(async (entry) => {
25256
+ if (entry.isDirectory()) return entry.name;
25257
+ if (!entry.isSymbolicLink()) return null;
25258
+ try {
25259
+ return (await fs$1.stat(path$1.join(dirPath, entry.name))).isDirectory() ? entry.name : null;
25260
+ } catch {
25261
+ return null;
25262
+ }
25263
+ }))).filter((entry) => entry !== null);
25264
+ },
25265
+ async exists(filePath) {
25266
+ try {
25267
+ await fs$1.access(filePath);
25268
+ return true;
25269
+ } catch {
25270
+ return false;
25271
+ }
25272
+ },
25273
+ join(...paths) {
25274
+ return path$1.join(...paths);
25275
+ },
25276
+ dirname(filePath) {
25277
+ return path$1.dirname(filePath);
25278
+ }
25279
+ };
25280
+ }
25281
+ };
25282
+ //#endregion
25283
+ //#region src/utils/project-input.ts
24711
25284
  /** Resolve input to a .fairy file path. Accepts a directory or a .fairy file. */
24712
25285
  async function resolveFairyPath(input) {
24713
25286
  const resolved = path.resolve(input);
24714
25287
  const stat = await fs.stat(resolved);
24715
25288
  if (stat.isFile() && resolved.endsWith(".fairy")) return resolved;
24716
25289
  if (stat.isDirectory()) {
24717
- const fairyFiles = (await fs.readdir(resolved)).filter((e) => e.endsWith(".fairy"));
25290
+ const fairyFiles = (await fs.readdir(resolved)).filter((entry) => entry.endsWith(".fairy"));
24718
25291
  if (fairyFiles.length === 1) return path.join(resolved, fairyFiles[0]);
24719
25292
  if (fairyFiles.length > 1) throw new Error(`Multiple .fairy files found in ${resolved}: ${fairyFiles.join(", ")}. Please specify one.`);
24720
25293
  throw new Error(`No .fairy file found in ${resolved}`);
24721
25294
  }
24722
25295
  throw new Error(`Input is not a .fairy file or directory: ${resolved}`);
24723
25296
  }
24724
- async function main() {
24725
- const args = process.argv.slice(2);
24726
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
24727
- console.log(HELP);
24728
- return;
24729
- }
24730
- if (args.includes("--version") || args.includes("-v")) {
24731
- console.log(PACKAGE_VERSION);
24732
- return;
24733
- }
24734
- const command = args[0];
24735
- const rest = args.slice(1);
24736
- switch (command) {
24737
- case "inspect":
24738
- await cmdInspect(rest);
24739
- break;
24740
- case "publish":
24741
- await cmdPublish(rest);
24742
- break;
24743
- case "restore":
24744
- await cmdRestore(rest);
24745
- break;
24746
- case "backend-capabilities":
24747
- await cmdBackendCapabilities(rest);
24748
- break;
24749
- default:
24750
- console.error(`Unknown command: ${command}\n`);
24751
- console.log(HELP);
24752
- process.exit(1);
25297
+ //#endregion
25298
+ //#region src/commands/inspect.ts
25299
+ function registerInspectCommand(program) {
25300
+ program.command("inspect").description("Show project contents report").argument("<project-dir>", "Project root directory or .fairy file").action(async (projectDir) => {
25301
+ const fairyPath = await resolveFairyPath(projectDir);
25302
+ console.log(`Project: ${fairyPath}\n`);
25303
+ printReport(inspect(await new NodeIO().readProject(fairyPath)));
25304
+ });
25305
+ }
25306
+ function printReport(report) {
25307
+ console.log(`ID: ${report.projectId}`);
25308
+ console.log(`Type: ${report.projectType}, Version: ${report.version}`);
25309
+ console.log(`\nPackages: ${report.totals.packages}`);
25310
+ console.log(` Images: ${report.totals.images}`);
25311
+ console.log(` Sounds: ${report.totals.sounds}`);
25312
+ console.log(` Fonts: ${report.totals.fonts}`);
25313
+ console.log(` MovieClips: ${report.totals.movieClips}`);
25314
+ console.log(` Components: ${report.totals.components}`);
25315
+ console.log(` DisplayObjs: ${report.totals.displayObjects}`);
25316
+ console.log(` Gears: ${report.totals.gears}`);
25317
+ console.log(` Controllers: ${report.totals.controllers}`);
25318
+ console.log(` Transitions: ${report.totals.transitions}`);
25319
+ console.log("\nPackage details:");
25320
+ for (const pkg of report.packages) {
25321
+ const res = pkg.resources;
25322
+ console.log(` ${pkg.name} (${pkg.id}): ${res.images.count} img, ${res.sounds.count} snd, ${res.fonts.count} font, ${res.components.count} comp`);
24753
25323
  }
24754
25324
  }
25325
+ //#endregion
25326
+ //#region src/utils/project-type.ts
25327
+ function parseProjectType(value) {
25328
+ if (!value) return void 0;
25329
+ const trimmed = value.trim();
25330
+ if (trimmed === "") return void 0;
25331
+ if (/^\d+$/u.test(trimmed)) return Number(trimmed);
25332
+ const normalized = trimmed.toLowerCase();
25333
+ const map = {
25334
+ unity: ProjectType.Unity,
25335
+ flash: ProjectType.Flash,
25336
+ starling: ProjectType.Starling,
25337
+ cocoscreator: ProjectType.CocosCreator,
25338
+ cocos: ProjectType.CocosCreator,
25339
+ layabox: ProjectType.LayaBox,
25340
+ laya: ProjectType.LayaBox,
25341
+ egret: ProjectType.Egret,
25342
+ haxe: ProjectType.Haxe,
25343
+ pixi: ProjectType.Pixi,
25344
+ libgdx: ProjectType.LibGDX,
25345
+ unreal: ProjectType.Unreal,
25346
+ cryengine: ProjectType.CryEngine,
25347
+ monogame: ProjectType.MonoGame,
25348
+ vision: ProjectType.Vision
25349
+ };
25350
+ const resolved = map[normalized];
25351
+ if (resolved === void 0) throw new Error(`Unknown project type: ${value}. Use a numeric id or one of: ${Object.keys(map).join(", ")}`);
25352
+ return resolved;
25353
+ }
25354
+ //#endregion
25355
+ //#region src/commands/publish.ts
25356
+ function registerPublishCommand(program) {
25357
+ program.command("publish").description("Publish project to binary outputs and configured generated code").argument("<project-dir>", "Project root directory or .fairy file").option("-o, --output <dir>", "Override project or package publish output directory").option("-c, --compressed", "Compress binary data (overrides project setting)").option("-p, --packages <a,b,c>", "Only publish specific packages (comma-separated)").option("-b, --branch <name>", "Active branch used by \"主干合并活跃分支\"; omit for main branch").option("-t, --project-type <name|id>", "Override project type (for example: unity, layabox, cocoscreator, 0, 4, 3)").action(async (projectDir, options) => {
25358
+ const fairyPath = await resolveFairyPath(projectDir);
25359
+ const projectRootDir = path.dirname(fairyPath);
25360
+ const outputDir = options.output ? path.resolve(options.output) : void 0;
25361
+ console.log(`Reading project: ${fairyPath}`);
25362
+ const doc = await new NodeIO().readProject(fairyPath);
25363
+ const projectType = parseProjectType(options.projectType);
25364
+ if (projectType !== void 0) doc.getRoot().setProjectType(projectType);
25365
+ const pkgFilter = options.packages?.split(",").map((value) => value.trim());
25366
+ const resolved = resolvePublishOptions(doc, {
25367
+ compressed: options.compressed,
25368
+ packages: pkgFilter
25369
+ });
25370
+ console.log(`Settings: ext=${resolved.fileExtension}, compressed=${resolved.compressed}`);
25371
+ if (options.branch) console.log(`Active branch: ${options.branch}`);
25372
+ const atlasConfig = {
25373
+ ...resolved.atlas,
25374
+ readFileRaw: async (filePath) => {
25375
+ const buf = await fs.readFile(filePath);
25376
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25377
+ }
25378
+ };
25379
+ let encoder;
25380
+ try {
25381
+ const sharp = await import("sharp");
25382
+ encoder = sharp.default ?? sharp;
25383
+ console.log("Sharp loaded — atlas PNGs will be generated.");
25384
+ } catch {
25385
+ console.log("Sharp not available — atlas PNGs will NOT be generated (layout only).");
25386
+ console.log(" Install sharp to enable: pnpm add sharp");
25387
+ }
25388
+ await doc.transform(publish({
25389
+ output: outputDir,
25390
+ compressed: resolved.compressed,
25391
+ fileExtension: resolved.fileExtension,
25392
+ packages: resolved.packages,
25393
+ fs: {
25394
+ async readFileRaw(filePath) {
25395
+ const buf = await fs.readFile(filePath);
25396
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25397
+ },
25398
+ async writeFileRaw(filePath, data) {
25399
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
25400
+ await fs.writeFile(filePath, data);
25401
+ },
25402
+ async mkdir(dirPath) {
25403
+ await fs.mkdir(dirPath, { recursive: true });
25404
+ },
25405
+ async readdir(dirPath) {
25406
+ return fs.readdir(dirPath);
25407
+ },
25408
+ async deleteFile(filePath) {
25409
+ await fs.rm(filePath, { force: true });
25410
+ },
25411
+ join(...paths) {
25412
+ return path.join(...paths);
25413
+ }
25414
+ },
25415
+ encoder,
25416
+ basePath: path.join(projectRootDir, "assets"),
25417
+ atlas: atlasConfig,
25418
+ branch: options.branch
25419
+ }));
25420
+ console.log(`\nDone!${outputDir ? ` Output override: ${outputDir}` : ""}`);
25421
+ });
25422
+ }
25423
+ //#endregion
25424
+ //#region src/commands/restore.ts
25425
+ function registerRestoreCommand(program) {
25426
+ program.command("restore").description("Restore a FairyGUI project from published binaries").argument("<release-dir>", "Published release directory").requiredOption("-o, --output <dir>", "Output project directory").option("-p, --packages <a,b,c>", "Only restore specific packages (comma-separated)").option("-f, --force", "Overwrite a non-empty output directory").option("-t, --project-type <name|id>", "Override restored project type; default is unity").action(async (releaseDir, options) => {
25427
+ const inputDir = path.resolve(releaseDir);
25428
+ const outputDir = path.resolve(options.output);
25429
+ const pkgFilter = options.packages ? options.packages.split(",").map((value) => value.trim()).filter(Boolean) : void 0;
25430
+ const projectType = parseProjectType(options.projectType);
25431
+ const { cropImage, extractImage } = await createRestoreImageProcessors();
25432
+ console.log(`Restoring published FairyGUI project: ${inputDir}`);
25433
+ const result = await restore({
25434
+ inputDir,
25435
+ output: outputDir,
25436
+ fs: createNodeRestoreFs(),
25437
+ packages: pkgFilter,
25438
+ force: options.force,
25439
+ projectType,
25440
+ cropImage,
25441
+ extractImage
25442
+ });
25443
+ const packages = result.document.getRoot().listPackages();
25444
+ console.log(`\nDone! Output: ${result.projectPath}`);
25445
+ console.log(`Packages: ${packages.map((pkg) => pkg.getName()).join(", ")}`);
25446
+ for (const warning of result.warnings) console.warn(`Warning: ${warning}`);
25447
+ });
25448
+ }
24755
25449
  function createNodeRestoreFs() {
24756
25450
  return {
24757
25451
  async readFile(filePath) {
@@ -24860,214 +25554,47 @@ async function createRestoreImageProcessors() {
24860
25554
  }
24861
25555
  };
24862
25556
  }
24863
- async function cmdInspect(args) {
24864
- if (args.length === 0) {
24865
- console.error("Usage: ofgui inspect <project-dir>");
24866
- process.exit(1);
24867
- }
24868
- const fairyPath = await resolveFairyPath(args[0]);
24869
- console.log(`Project: ${fairyPath}\n`);
24870
- printReport(inspect(await new NodeIO().readProject(fairyPath)));
25557
+ //#endregion
25558
+ //#region src/utils/package-version.ts
25559
+ const require = createRequire(import.meta.url);
25560
+ function getInjectedPackageVersion() {
25561
+ const version = import.meta.env?.PACKAGE_VERSION;
25562
+ return typeof version === "string" && version.length > 0 ? version : null;
24871
25563
  }
24872
- function printReport(report) {
24873
- console.log(`ID: ${report.projectId}`);
24874
- console.log(`Type: ${report.projectType}, Version: ${report.version}`);
24875
- console.log(`\nPackages: ${report.totals.packages}`);
24876
- console.log(` Images: ${report.totals.images}`);
24877
- console.log(` Sounds: ${report.totals.sounds}`);
24878
- console.log(` Fonts: ${report.totals.fonts}`);
24879
- console.log(` MovieClips: ${report.totals.movieClips}`);
24880
- console.log(` Components: ${report.totals.components}`);
24881
- console.log(` DisplayObjs: ${report.totals.displayObjects}`);
24882
- console.log(` Gears: ${report.totals.gears}`);
24883
- console.log(` Controllers: ${report.totals.controllers}`);
24884
- console.log(` Transitions: ${report.totals.transitions}`);
24885
- console.log("\nPackage details:");
24886
- for (const pkg of report.packages) {
24887
- const res = pkg.resources;
24888
- console.log(` ${pkg.name} (${pkg.id}): ${res.images.count} img, ${res.sounds.count} snd, ${res.fonts.count} font, ${res.components.count} comp`);
24889
- }
25564
+ function readPackageVersion() {
25565
+ const injectedVersion = getInjectedPackageVersion();
25566
+ if (injectedVersion) return injectedVersion;
25567
+ try {
25568
+ const pkg = require("../../package.json");
25569
+ if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
25570
+ } catch {}
25571
+ return "0.0.0-dev";
24890
25572
  }
24891
- function parseProjectType(value) {
24892
- if (!value) return void 0;
24893
- const trimmed = value.trim();
24894
- if (trimmed === "") return void 0;
24895
- if (/^\d+$/u.test(trimmed)) return Number(trimmed);
24896
- const normalized = trimmed.toLowerCase();
24897
- const map = {
24898
- unity: ProjectType.Unity,
24899
- flash: ProjectType.Flash,
24900
- starling: ProjectType.Starling,
24901
- cocoscreator: ProjectType.CocosCreator,
24902
- cocos: ProjectType.CocosCreator,
24903
- layabox: ProjectType.LayaBox,
24904
- laya: ProjectType.LayaBox,
24905
- egret: ProjectType.Egret,
24906
- haxe: ProjectType.Haxe,
24907
- pixi: ProjectType.Pixi,
24908
- libgdx: ProjectType.LibGDX,
24909
- unreal: ProjectType.Unreal,
24910
- cryengine: ProjectType.CryEngine,
24911
- monogame: ProjectType.MonoGame,
24912
- vision: ProjectType.Vision
24913
- };
24914
- const resolved = map[normalized];
24915
- if (resolved === void 0) throw new Error(`Unknown project type: ${value}. Use a numeric id or one of: ${Object.keys(map).join(", ")}`);
24916
- return resolved;
25573
+ //#endregion
25574
+ //#region src/cli.ts
25575
+ const PACKAGE_VERSION = readPackageVersion();
25576
+ function createProgram() {
25577
+ const program = new Command("ofgui");
25578
+ program.description("FairyGUI Headless Authoring CLI").version(PACKAGE_VERSION).showHelpAfterError();
25579
+ registerInspectCommand(program);
25580
+ registerPublishCommand(program);
25581
+ registerRestoreCommand(program);
25582
+ registerBackendCapabilitiesCommand(program);
25583
+ program.addHelpText("after", [
25584
+ "",
25585
+ "Alias:",
25586
+ " openfairygui",
25587
+ "",
25588
+ "Input can be a .fairy file or a project root directory (auto-discovers .fairy file).",
25589
+ "File extension and binary format are read from project settings."
25590
+ ].join("\n"));
25591
+ return program;
24917
25592
  }
24918
- async function cmdRestore(args) {
24919
- const { values, positionals } = parseArgs({
24920
- args,
24921
- options: {
24922
- output: {
24923
- type: "string",
24924
- short: "o"
24925
- },
24926
- packages: { type: "string" },
24927
- force: { type: "boolean" },
24928
- "project-type": { type: "string" }
24929
- },
24930
- allowPositionals: true
24931
- });
24932
- if (positionals.length === 0 || !values.output) {
24933
- console.error("Usage: ofgui restore <release-dir> --output <dir> [--packages a,b,c] [--force]");
24934
- process.exit(1);
24935
- }
24936
- const releaseDir = path.resolve(positionals[0]);
24937
- const outputDir = path.resolve(values.output);
24938
- const pkgFilter = values.packages ? values.packages.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
24939
- const projectType = parseProjectType(values["project-type"]);
24940
- const { cropImage, extractImage } = await createRestoreImageProcessors();
24941
- console.log(`Restoring published FairyGUI project: ${releaseDir}`);
24942
- const result = await restore({
24943
- inputDir: releaseDir,
24944
- output: outputDir,
24945
- fs: createNodeRestoreFs(),
24946
- packages: pkgFilter,
24947
- force: values.force,
24948
- projectType,
24949
- cropImage,
24950
- extractImage
24951
- });
24952
- const packages = result.document.getRoot().listPackages();
24953
- console.log(`\nDone! Output: ${result.projectPath}`);
24954
- console.log(`Packages: ${packages.map((pkg) => pkg.getName()).join(", ")}`);
24955
- for (const warning of result.warnings) console.warn(`Warning: ${warning}`);
24956
- }
24957
- async function cmdPublish(args) {
24958
- const { values, positionals } = parseArgs({
24959
- args,
24960
- options: {
24961
- output: {
24962
- type: "string",
24963
- short: "o"
24964
- },
24965
- compressed: { type: "boolean" },
24966
- packages: { type: "string" },
24967
- branch: { type: "string" },
24968
- "project-type": { type: "string" }
24969
- },
24970
- allowPositionals: true
24971
- });
24972
- if (positionals.length === 0 || !values.output) {
24973
- console.error("Usage: ofgui publish <project-dir> --output <dir> [--compressed] [--packages a,b,c] [--branch name]");
24974
- process.exit(1);
24975
- }
24976
- const fairyPath = await resolveFairyPath(positionals[0]);
24977
- const projectDir = path.dirname(fairyPath);
24978
- const outputDir = path.resolve(values.output);
24979
- console.log(`Reading project: ${fairyPath}`);
24980
- const doc = await new NodeIO().readProject(fairyPath);
24981
- const projectType = parseProjectType(values["project-type"]);
24982
- if (projectType !== void 0) doc.getRoot().setProjectType(projectType);
24983
- const pkgFilter = values.packages ? values.packages.split(",").map((s) => s.trim()) : void 0;
24984
- const resolved = resolvePublishOptions(doc, {
24985
- compressed: values.compressed,
24986
- packages: pkgFilter
24987
- });
24988
- console.log(`Settings: ext=${resolved.fileExtension}, compressed=${resolved.compressed}`);
24989
- if (values.branch) console.log(`Active branch: ${values.branch}`);
24990
- const atlasConfig = {
24991
- ...resolved.atlas,
24992
- readFileRaw: async (filePath) => {
24993
- const buf = await fs.readFile(filePath);
24994
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
24995
- }
24996
- };
24997
- let encoder;
24998
- try {
24999
- const sharp = await import("sharp");
25000
- encoder = sharp.default ?? sharp;
25001
- console.log("Sharp loaded — atlas PNGs will be generated.");
25002
- } catch {
25003
- console.log("Sharp not available — atlas PNGs will NOT be generated (layout only).");
25004
- console.log(" Install sharp to enable: pnpm add sharp");
25005
- }
25006
- await doc.transform(publish({
25007
- output: outputDir,
25008
- compressed: resolved.compressed,
25009
- fileExtension: resolved.fileExtension,
25010
- packages: resolved.packages,
25011
- fs: {
25012
- async readFileRaw(filePath) {
25013
- const buf = await fs.readFile(filePath);
25014
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25015
- },
25016
- async writeFileRaw(filePath, data) {
25017
- await fs.mkdir(path.dirname(filePath), { recursive: true });
25018
- await fs.writeFile(filePath, data);
25019
- },
25020
- async mkdir(dirPath) {
25021
- await fs.mkdir(dirPath, { recursive: true });
25022
- },
25023
- async readdir(dirPath) {
25024
- return fs.readdir(dirPath);
25025
- },
25026
- async deleteFile(filePath) {
25027
- await fs.rm(filePath, { force: true });
25028
- },
25029
- join(...paths) {
25030
- return path.join(...paths);
25031
- }
25032
- },
25033
- encoder,
25034
- basePath: path.join(projectDir, "assets"),
25035
- atlas: atlasConfig,
25036
- branch: values.branch
25037
- }));
25038
- console.log(`\nDone! Output: ${outputDir}`);
25039
- }
25040
- async function cmdBackendCapabilities(args) {
25041
- if (args.length === 0) {
25042
- console.error("Usage: ofgui backend-capabilities <project-dir>");
25043
- process.exit(1);
25044
- }
25045
- const runtime = createNodeBackendRuntime();
25046
- const opened = await runtime.openSession({ projectPath: path.resolve(args[0]) });
25047
- if (!opened.ok) {
25048
- console.error(`backend-capabilities: ${opened.error.message}`);
25049
- process.exit(1);
25050
- }
25051
- const capabilities = runtime.getCapabilities();
25052
- if (!capabilities.ok) {
25053
- console.error("backend-capabilities: failed to read capabilities");
25054
- await runtime.closeSession({ sessionId: opened.data.sessionId });
25055
- process.exit(1);
25056
- }
25057
- console.log(`Session: ${opened.data.sessionId}`);
25058
- console.log(`Project: ${opened.data.canonicalProjectPath}`);
25059
- console.log(`Revision: ${opened.data.revision}`);
25060
- console.log(`Runtime owner: ${capabilities.data.runtimeOwner}`);
25061
- console.log(`Transaction owner: ${capabilities.data.transactionKernelOwner}`);
25062
- console.log(`App seam owner: ${capabilities.data.appSeamOwner}`);
25063
- const closed = await runtime.closeSession({ sessionId: opened.data.sessionId });
25064
- if (!closed.ok) {
25065
- console.error(`backend-capabilities: ${closed.error.message}`);
25066
- process.exit(1);
25067
- }
25593
+ async function main() {
25594
+ await createProgram().parseAsync(process.argv);
25068
25595
  }
25069
25596
  main().catch((err) => {
25070
- console.error(err);
25597
+ console.error(err instanceof Error ? err.message : String(err));
25071
25598
  process.exit(1);
25072
25599
  });
25073
25600
  //#endregion