@openfairygui/cli 0.2.0-alpha.1 → 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,9 +1,178 @@
1
+ import { createRequire } from "node:module";
2
+ import { Command } from "commander";
1
3
  import { createNodeBackendRuntime } from "@openfairygui/backend/node";
2
- import * as fs$1 from "node:fs/promises";
3
- import fs from "node:fs/promises";
4
4
  import * as path$1 from "node:path";
5
5
  import path from "node:path";
6
- 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
7
176
  //#region ../../node_modules/.pnpm/property-graph@4.1.0/node_modules/property-graph/dist/index.mjs
8
177
  var EventDispatcher = class {
9
178
  _listeners = {};
@@ -1262,6 +1431,7 @@ var ImageResource = class extends ExtensibleProperty {
1262
1431
  path: "",
1263
1432
  branch: "",
1264
1433
  branchItemIds: [],
1434
+ highResolutionItemIds: [],
1265
1435
  width: 0,
1266
1436
  height: 0,
1267
1437
  exported: false,
@@ -1308,6 +1478,12 @@ var ImageResource = class extends ExtensibleProperty {
1308
1478
  setBranchItemIds(ids) {
1309
1479
  return this.set("branchItemIds", [...ids]);
1310
1480
  }
1481
+ getHighResolutionItemIds() {
1482
+ return [...this.get("highResolutionItemIds")];
1483
+ }
1484
+ setHighResolutionItemIds(ids) {
1485
+ return this.set("highResolutionItemIds", [...ids]);
1486
+ }
1311
1487
  getWidth() {
1312
1488
  return this.get("width");
1313
1489
  }
@@ -1677,8 +1853,10 @@ var MovieClipResource = class extends ExtensibleProperty {
1677
1853
  path: "",
1678
1854
  branch: "",
1679
1855
  branchItemIds: [],
1856
+ highResolutionItemIds: [],
1680
1857
  fileName: "",
1681
1858
  exported: false,
1859
+ textureSetMode: "",
1682
1860
  width: 0,
1683
1861
  height: 0,
1684
1862
  interval: 0,
@@ -1712,6 +1890,12 @@ var MovieClipResource = class extends ExtensibleProperty {
1712
1890
  setBranchItemIds(ids) {
1713
1891
  return this.set("branchItemIds", [...ids]);
1714
1892
  }
1893
+ getHighResolutionItemIds() {
1894
+ return [...this.get("highResolutionItemIds")];
1895
+ }
1896
+ setHighResolutionItemIds(ids) {
1897
+ return this.set("highResolutionItemIds", [...ids]);
1898
+ }
1715
1899
  getFileName() {
1716
1900
  return this.get("fileName");
1717
1901
  }
@@ -1724,6 +1908,12 @@ var MovieClipResource = class extends ExtensibleProperty {
1724
1908
  setExported(v) {
1725
1909
  return this.set("exported", v);
1726
1910
  }
1911
+ getTextureSetMode() {
1912
+ return this.get("textureSetMode");
1913
+ }
1914
+ setTextureSetMode(v) {
1915
+ return this.set("textureSetMode", v);
1916
+ }
1727
1917
  getWidth() {
1728
1918
  return this.get("width");
1729
1919
  }
@@ -2775,6 +2965,7 @@ var GObject = class extends ExtensibleProperty {
2775
2965
  sourceHeight: 0,
2776
2966
  initWidth: 0,
2777
2967
  initHeight: 0,
2968
+ customData: "",
2778
2969
  relations: [],
2779
2970
  gears: new RefList()
2780
2971
  });
@@ -2815,6 +3006,12 @@ var GObject = class extends ExtensibleProperty {
2815
3006
  setInitHeight(v) {
2816
3007
  return this.setObjectProp("initHeight", v);
2817
3008
  }
3009
+ getCustomData() {
3010
+ return this.getObjectProp("customData");
3011
+ }
3012
+ setCustomData(v) {
3013
+ return this.setObjectProp("customData", v);
3014
+ }
2818
3015
  /****** Relations ******/
2819
3016
  getRelations() {
2820
3017
  return this.getObjectProp("relations");
@@ -4429,6 +4626,8 @@ var GComponent = class extends GObject {
4429
4626
  instanceController: "",
4430
4627
  instancePage: "",
4431
4628
  instanceChecked: false,
4629
+ instanceSound: "",
4630
+ instanceSoundVolumeScale: 1,
4432
4631
  instancePromptText: "",
4433
4632
  instanceSelectionController: "",
4434
4633
  instanceVisibleItemCount: 0,
@@ -4701,6 +4900,18 @@ var GComponent = class extends GObject {
4701
4900
  setInstanceChecked(v) {
4702
4901
  return this.setComponentProp("instanceChecked", v);
4703
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
+ }
4704
4915
  getInstancePromptText() {
4705
4916
  return firstString$1(this.getComponentProp("instancePromptText"));
4706
4917
  }
@@ -9059,6 +9270,7 @@ var Document = class Document {
9059
9270
  _graph = new Graph();
9060
9271
  _root = new Root(this._graph);
9061
9272
  _logger = Logger.DEFAULT_INSTANCE;
9273
+ _projectDir = "";
9062
9274
  static _GRAPH_DOCUMENTS = /* @__PURE__ */ new WeakMap();
9063
9275
  static fromGraph(graph) {
9064
9276
  return Document._GRAPH_DOCUMENTS.get(graph) || null;
@@ -9080,6 +9292,13 @@ var Document = class Document {
9080
9292
  this._logger = logger;
9081
9293
  return this;
9082
9294
  }
9295
+ getProjectDir() {
9296
+ return this._projectDir;
9297
+ }
9298
+ setProjectDir(projectDir) {
9299
+ this._projectDir = projectDir;
9300
+ return this;
9301
+ }
9083
9302
  async transform(...transforms) {
9084
9303
  const stack = transforms.map((fn) => fn.name);
9085
9304
  for (const transform of transforms) await transform(this, { stack });
@@ -9260,6 +9479,7 @@ const PACKAGE_FONT_RESOURCE_ATTRS = {
9260
9479
  renderMode: { canonical: "renderMode" },
9261
9480
  samplePointSize: { canonical: "samplePointSize" }
9262
9481
  };
9482
+ const PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS = { atlas: { canonical: "atlas" } };
9263
9483
  const PACKAGE_SKELETON_RESOURCE_ATTRS = {
9264
9484
  width: { canonical: "width" },
9265
9485
  height: { canonical: "height" },
@@ -9496,7 +9716,10 @@ const LIST_PANEL_ATTRS = {
9496
9716
  const BUTTON_EXTENSION_ATTRS = {
9497
9717
  mode: { canonical: "mode" },
9498
9718
  sound: { canonical: "sound" },
9499
- soundVolumeScale: { canonical: "soundVolumeScale" },
9719
+ soundVolumeScale: {
9720
+ canonical: "soundVolumeScale",
9721
+ aliases: ["volume"]
9722
+ },
9500
9723
  downEffect: { canonical: "downEffect" },
9501
9724
  downEffectValue: { canonical: "downEffectValue" },
9502
9725
  title: { canonical: "title" },
@@ -9622,6 +9845,7 @@ const PACKAGE_PUBLISH_NODE = defineNode(PACKAGE_PUBLISH_ATTRS, { atlas: PACKAGE_
9622
9845
  const PACKAGE_RESOURCE_NODE = defineNode(PACKAGE_RESOURCE_BASE_ATTRS);
9623
9846
  const PACKAGE_IMAGE_RESOURCE_NODE = defineNode(PACKAGE_IMAGE_RESOURCE_ATTRS);
9624
9847
  const PACKAGE_FONT_RESOURCE_NODE = defineNode(PACKAGE_FONT_RESOURCE_ATTRS);
9848
+ const PACKAGE_MOVIE_CLIP_RESOURCE_NODE = defineNode(PACKAGE_MOVIE_CLIP_RESOURCE_ATTRS);
9625
9849
  const PACKAGE_SKELETON_RESOURCE_NODE = defineNode(PACKAGE_SKELETON_RESOURCE_ATTRS);
9626
9850
  const DISPLAY_OBJECT_NODE = defineNode(DISPLAY_OBJECT_IDENTITY_ATTRS);
9627
9851
  const BUTTON_EXTENSION_NODE = defineNode(BUTTON_EXTENSION_ATTRS);
@@ -9682,13 +9906,13 @@ const IMAGE_NODE = defineNode(mergeAttrs(IMAGE_PANEL_ATTRS, XY_SIZE_ATTRS, LOCKE
9682
9906
  const GRAPH_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, LOCKED_ATTRS, RESTRICT_SIZE_ATTRS, PIVOT_ATTRS, ANCHOR_ATTRS, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, GRAPH_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9683
9907
  const MOVIE_CLIP_NODE = defineNode(mergeAttrs(MOVIE_CLIP_PANEL_ATTRS, XY_SIZE_ATTRS, PIVOT_ATTRS, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, GRAYED_ATTRS, RESOURCE_LINK_ATTRS, FILTER_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9684
9908
  const COMPONENT_INSTANCE_NODE = defineNode(mergeAttrs(COMPONENT_INSTANCE_PANEL_ATTRS, XY_SIZE_ATTRS, LOCKED_ATTRS, RESTRICT_SIZE_ATTRS, ASPECT_ATTRS, PIVOT_ATTRS, ANCHOR_ATTRS, SCALE_ATTRS, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, GRAYED_ATTRS, INSTANCE_MISC_PANEL_ATTRS, RESOURCE_LINK_ATTRS, FILTER_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN, WITH_INSTANCE_EXTENSION_CHILDREN));
9685
- const LOADER_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, PIVOT_ATTRS, SCALE_ATTRS, GROUP_REF_ATTRS, GRAYED_ATTRS, LOADER_PANEL_ATTRS, FILTER_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9686
- const LOADER3D_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, LOADER3D_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9687
- const TEXT_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, RESTRICT_SIZE_ATTRS, { customData: { canonical: "customData" } }, GROUP_REF_ATTRS, TEXT_PANEL_ATTRS, TEXT_INPUT_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9688
- const TEXT_INPUT_NODE = defineNode(mergeAttrs(TEXT_INPUT_PANEL_ATTRS));
9689
- const RICH_TEXT_NODE = defineNode(mergeAttrs(RICH_TEXT_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9909
+ const LOADER_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, PIVOT_ATTRS, SCALE_ATTRS, GROUP_REF_ATTRS, VISIBLE_ATTRS, GRAYED_ATTRS, LOADER_PANEL_ATTRS, FILTER_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9910
+ const LOADER3D_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, VISIBLE_ATTRS, LOADER3D_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9911
+ const TEXT_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, RESTRICT_SIZE_ATTRS, { customData: { canonical: "customData" } }, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, GRAYED_ATTRS, TEXT_PANEL_ATTRS, TEXT_INPUT_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9912
+ const TEXT_INPUT_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, RESTRICT_SIZE_ATTRS, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, GRAYED_ATTRS, TEXT_PANEL_ATTRS, TEXT_INPUT_PANEL_ATTRS));
9913
+ const RICH_TEXT_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, GROUP_REF_ATTRS, ROTATION_ALPHA_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, GRAYED_ATTRS, TEXT_PANEL_ATTRS, RICH_TEXT_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN));
9690
9914
  const GROUP_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, LOCKED_ATTRS, GROUP_REF_ATTRS, VISIBLE_ATTRS, GROUP_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GROUP_GEAR_CHILDREN));
9691
- const LIST_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, GROUP_REF_ATTRS, TOUCHABLE_ATTRS, LIST_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN, WITH_LIST_ITEM_CHILDREN));
9915
+ const LIST_NODE = defineNode(mergeAttrs(XY_SIZE_ATTRS, GROUP_REF_ATTRS, VISIBLE_ATTRS, TOUCHABLE_ATTRS, LIST_PANEL_ATTRS), mergeChildren(WITH_RELATION_CHILDREN, WITH_GEAR_CHILDREN, WITH_LIST_ITEM_CHILDREN));
9692
9916
  const DISPLAY_LIST_CONTAINER$2 = defineContainer({
9693
9917
  image: IMAGE_NODE,
9694
9918
  graph: GRAPH_NODE,
@@ -9712,6 +9936,7 @@ const PROJECT_XML_PROTOCOL = {
9712
9936
  packageResource: PACKAGE_RESOURCE_NODE,
9713
9937
  packageImageResource: PACKAGE_IMAGE_RESOURCE_NODE,
9714
9938
  packageFontResource: PACKAGE_FONT_RESOURCE_NODE,
9939
+ packageMovieClipResource: PACKAGE_MOVIE_CLIP_RESOURCE_NODE,
9715
9940
  packageSkeletonResource: PACKAGE_SKELETON_RESOURCE_NODE,
9716
9941
  displayObject: DISPLAY_OBJECT_NODE,
9717
9942
  image: IMAGE_NODE,
@@ -10142,6 +10367,10 @@ function parseComboBoxItemXmlNode(item) {
10142
10367
  icon: readXmlAttr(item, specs.icon) ?? null
10143
10368
  };
10144
10369
  }
10370
+ function getProjectBasePath(fs, projectPath) {
10371
+ const basePath = fs.dirname(projectPath);
10372
+ return basePath === "." ? "" : basePath;
10373
+ }
10145
10374
  var ProjectReader = class {
10146
10375
  _fs;
10147
10376
  constructor(fs) {
@@ -10150,7 +10379,8 @@ var ProjectReader = class {
10150
10379
  async read(projectPath) {
10151
10380
  const fs = this._fs;
10152
10381
  const doc = new Document();
10153
- const basePath = projectPath.replace(/[/\\][^/\\]*\.fairy$/i, "");
10382
+ const basePath = getProjectBasePath(fs, projectPath);
10383
+ doc.setProjectDir(basePath);
10154
10384
  const ctx = new ReaderContext(doc, basePath);
10155
10385
  const projDesc = getXmlNode(parseXML(await fs.readFile(projectPath)).projectDescription);
10156
10386
  if (projDesc) {
@@ -10467,6 +10697,8 @@ var ProjectReader = class {
10467
10697
  res.setBranch(branchName);
10468
10698
  res.setFileName(name);
10469
10699
  res.setExported(exported);
10700
+ const textureSetMode = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas);
10701
+ if (textureSetMode !== void 0) res.setTextureSetMode(textureSetMode);
10470
10702
  pkg.addResource(res);
10471
10703
  ctx.registerResource(pkg.getId(), id, res);
10472
10704
  return res;
@@ -10871,6 +11103,16 @@ var ProjectReader = class {
10871
11103
  }
10872
11104
  const textGroup = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.group);
10873
11105
  if (textGroup) g.setGroup(textGroup);
11106
+ const textRotation = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.rotation);
11107
+ if (textRotation !== void 0) g.setRotation(parseFloat2(textRotation));
11108
+ const textAlpha = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.alpha);
11109
+ if (textAlpha !== void 0) g.setAlpha(parseFloat2(textAlpha, 1));
11110
+ const textVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.visible);
11111
+ if (textVisible !== void 0) g.setVisible(parseBool(textVisible));
11112
+ const textTouchable = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.touchable);
11113
+ if (textTouchable !== void 0) g.setTouchable(parseBool(textTouchable));
11114
+ const textGrayed = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.grayed);
11115
+ if (textGrayed !== void 0) g.setGrayed(parseBool(textGrayed));
10874
11116
  const textCustomData = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.customData);
10875
11117
  if (textCustomData !== void 0) g.setCustomData(textCustomData);
10876
11118
  const textValue = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.text);
@@ -10981,6 +11223,16 @@ var ProjectReader = class {
10981
11223
  }
10982
11224
  const richTextGroup = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.group);
10983
11225
  if (richTextGroup) g.setGroup(richTextGroup);
11226
+ const richTextRotation = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.rotation);
11227
+ if (richTextRotation !== void 0) g.setRotation(parseFloat2(richTextRotation));
11228
+ const richTextAlpha = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.alpha);
11229
+ if (richTextAlpha !== void 0) g.setAlpha(parseFloat2(richTextAlpha, 1));
11230
+ const richTextVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.visible);
11231
+ if (richTextVisible !== void 0) g.setVisible(parseBool(richTextVisible));
11232
+ const richTextTouchable = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.touchable);
11233
+ if (richTextTouchable !== void 0) g.setTouchable(parseBool(richTextTouchable));
11234
+ const richTextGrayed = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.grayed);
11235
+ if (richTextGrayed !== void 0) g.setGrayed(parseBool(richTextGrayed));
10984
11236
  const richText = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.text);
10985
11237
  if (richText !== void 0) g.setText(String(richText));
10986
11238
  const richTextFontSize = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.fontSize);
@@ -11069,6 +11321,16 @@ var ProjectReader = class {
11069
11321
  }
11070
11322
  const inputGroup = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.group);
11071
11323
  if (inputGroup) g.setGroup(inputGroup);
11324
+ const inputRotation = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.textInput.attrs.rotation);
11325
+ if (inputRotation !== void 0) g.setRotation(parseFloat2(inputRotation));
11326
+ const inputAlpha = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.textInput.attrs.alpha);
11327
+ if (inputAlpha !== void 0) g.setAlpha(parseFloat2(inputAlpha, 1));
11328
+ const inputVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.textInput.attrs.visible);
11329
+ if (inputVisible !== void 0) g.setVisible(parseBool(inputVisible));
11330
+ const inputTouchable = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.textInput.attrs.touchable);
11331
+ if (inputTouchable !== void 0) g.setTouchable(parseBool(inputTouchable));
11332
+ const inputGrayed = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.textInput.attrs.grayed);
11333
+ if (inputGrayed !== void 0) g.setGrayed(parseBool(inputGrayed));
11072
11334
  const inputText = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.text);
11073
11335
  if (inputText !== void 0) g.setText(String(inputText));
11074
11336
  const inputFontSize = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.fontSize);
@@ -11275,6 +11537,8 @@ var ProjectReader = class {
11275
11537
  }
11276
11538
  const loaderGrayed = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.grayed);
11277
11539
  if (loaderGrayed !== void 0) g.setGrayed(parseBool(loaderGrayed));
11540
+ const loaderVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.visible);
11541
+ if (loaderVisible !== void 0) g.setVisible(parseBool(loaderVisible));
11278
11542
  const loaderUrl = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.url);
11279
11543
  if (loaderUrl) g.setUrl(loaderUrl);
11280
11544
  const loaderAlign = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.align);
@@ -11348,6 +11612,8 @@ var ProjectReader = class {
11348
11612
  const [w, h] = parseSizeString(loader3dSize);
11349
11613
  g.setSize(w, h);
11350
11614
  }
11615
+ const loader3dVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.visible);
11616
+ if (loader3dVisible !== void 0) g.setVisible(parseBool(loader3dVisible));
11351
11617
  const loader3dUrl = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.url);
11352
11618
  if (loader3dUrl) g.setUrl(loader3dUrl);
11353
11619
  const loader3dAlign = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.align);
@@ -11530,6 +11796,8 @@ var ProjectReader = class {
11530
11796
  }
11531
11797
  const listGroup = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.group);
11532
11798
  if (listGroup) g.setGroup(listGroup);
11799
+ const listVisible = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.visible);
11800
+ if (listVisible !== void 0) g.setVisible(parseBool(listVisible));
11533
11801
  const listTouchable = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.touchable);
11534
11802
  if (listTouchable !== void 0) g.setTouchable(parseBool(listTouchable));
11535
11803
  const defaultItem = readXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.defaultItem);
@@ -11690,6 +11958,10 @@ var ProjectReader = class {
11690
11958
  if (page !== void 0) componentObj.setInstancePage?.(page);
11691
11959
  const checked = extSpecs.checked ? readXmlAttr(extAttrs, extSpecs.checked) : void 0;
11692
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));
11693
11965
  const prompt = extSpecs.prompt ? readXmlAttr(extAttrs, extSpecs.prompt) : void 0;
11694
11966
  if (prompt !== void 0) componentObj.setInstancePromptText?.(prompt);
11695
11967
  const selectionController = extSpecs.selectionController ? readXmlAttr(extAttrs, extSpecs.selectionController) : void 0;
@@ -12356,6 +12628,10 @@ var ProjectWriter = class {
12356
12628
  const samplePointSize = fontRes.getSamplePointSize?.() ?? 0;
12357
12629
  if (samplePointSize !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageFontResource.attrs.samplePointSize, String(samplePointSize));
12358
12630
  }
12631
+ if (res.propertyType === "MovieClipResource") {
12632
+ const textureSetMode = res.getTextureSetMode?.() ?? "";
12633
+ if (textureSetMode) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageMovieClipResource.attrs.atlas, textureSetMode);
12634
+ }
12359
12635
  if (res.propertyType === "SpineResource" || res.propertyType === "DragonBonesResource") {
12360
12636
  const skeletonRes = res;
12361
12637
  writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.packageSkeletonResource.attrs.width, String(skeletonRes.getWidth?.() ?? 0));
@@ -12715,6 +12991,7 @@ var ProjectWriter = class {
12715
12991
  if (pivotX !== 0 || pivotY !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.pivot, `${pivotX},${pivotY}`);
12716
12992
  const [scaleX, scaleY] = [typedObj.getScaleX?.() ?? 1, typedObj.getScaleY?.() ?? 1];
12717
12993
  if (scaleX !== 1 || scaleY !== 1) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.scale, `${scaleX},${scaleY}`);
12994
+ if (typedObj.getVisible?.() === false) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.visible, "false");
12718
12995
  if (typedObj.getGrayed?.()) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.grayed, "true");
12719
12996
  const url = typedObj.getUrl?.();
12720
12997
  if (url) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader.attrs.url, url);
@@ -12776,6 +13053,7 @@ var ProjectWriter = class {
12776
13053
  if (typedObj.getFilterData?.()) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.movieClip.attrs.filterData, typedObj.getFilterData?.());
12777
13054
  }
12778
13055
  if (type === "GTextField" || type === "GRichTextField" || type === "GTextInput") {
13056
+ const textNodeProtocol = type === "GRichTextField" ? PROJECT_XML_PROTOCOL.richText : type === "GTextInput" ? PROJECT_XML_PROTOCOL.textInput : PROJECT_XML_PROTOCOL.text;
12779
13057
  const [x, y] = [typedObj.getX?.() ?? 0, typedObj.getY?.() ?? 0];
12780
13058
  writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.xy, `${x},${y}`);
12781
13059
  const [w, h] = [typedObj.getWidth?.() ?? 0, typedObj.getHeight?.() ?? 0];
@@ -12802,6 +13080,11 @@ var ProjectWriter = class {
12802
13080
  if (underlaySoftness !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.richText.attrs.underlaySoftness, String(underlaySoftness));
12803
13081
  }
12804
13082
  if (typedObj.getGroup?.()) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.group, typedObj.getGroup?.());
13083
+ if ((typedObj.getRotation?.() ?? 0) !== 0) writeXmlAttr(attrs, textNodeProtocol.attrs.rotation, String(typedObj.getRotation?.() ?? 0));
13084
+ if ((typedObj.getAlpha?.() ?? 1) !== 1) writeXmlAttr(attrs, textNodeProtocol.attrs.alpha, formatDisplayAlpha(typedObj.getAlpha?.() ?? 1));
13085
+ if (typedObj.getVisible?.() === false) writeXmlAttr(attrs, textNodeProtocol.attrs.visible, "false");
13086
+ if (typedObj.getTouchable?.() === false) writeXmlAttr(attrs, textNodeProtocol.attrs.touchable, "false");
13087
+ if (typedObj.getGrayed?.()) writeXmlAttr(attrs, textNodeProtocol.attrs.grayed, "true");
12805
13088
  if (typedObj.getCustomData?.()) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.text.attrs.customData, typedObj.getCustomData?.());
12806
13089
  }
12807
13090
  if ((type === "GList" || type === "GTree") && typedObj.getGroup?.()) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.group, typedObj.getGroup?.());
@@ -12810,12 +13093,14 @@ var ProjectWriter = class {
12810
13093
  writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.xy, `${x},${y}`);
12811
13094
  const [w, h] = [typedObj.getWidth?.() ?? 0, typedObj.getHeight?.() ?? 0];
12812
13095
  if (w !== 0 || h !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.size, `${w},${h}`);
13096
+ if (typedObj.getVisible?.() === false) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.list.attrs.visible, "false");
12813
13097
  }
12814
13098
  if (type === "GLoader3D") {
12815
13099
  const [x, y] = [typedObj.getX?.() ?? 0, typedObj.getY?.() ?? 0];
12816
13100
  writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.xy, `${x},${y}`);
12817
13101
  const [w, h] = [typedObj.getWidth?.() ?? 0, typedObj.getHeight?.() ?? 0];
12818
13102
  if (w !== 0 || h !== 0) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.size, `${w},${h}`);
13103
+ if (typedObj.getVisible?.() === false) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.visible, "false");
12819
13104
  const url = typedObj.getUrl?.();
12820
13105
  if (url) writeXmlAttr(attrs, PROJECT_XML_PROTOCOL.loader3D.attrs.url, url);
12821
13106
  const align = typedObj.getAlign?.();
@@ -13028,6 +13313,8 @@ var ProjectWriter = class {
13028
13313
  if (typedObj.getInstanceController?.() && extSpecs.controller) writeXmlAttr(extAttrs, extSpecs.controller, typedObj.getInstanceController?.());
13029
13314
  if (typedObj.getInstancePage?.() && extSpecs.page) writeXmlAttr(extAttrs, extSpecs.page, typedObj.getInstancePage?.());
13030
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));
13031
13318
  if (typedObj.getInstancePromptText?.() && extSpecs.prompt) writeXmlAttr(extAttrs, extSpecs.prompt, typedObj.getInstancePromptText?.());
13032
13319
  if (typedObj.getInstanceSelectionController?.() && extSpecs.selectionController) writeXmlAttr(extAttrs, extSpecs.selectionController, typedObj.getInstanceSelectionController?.());
13033
13320
  if ((typedObj.getInstanceVisibleItemCount?.() ?? 0) > 0 && extSpecs.visibleItemCount) writeXmlAttr(extAttrs, extSpecs.visibleItemCount, String(typedObj.getInstanceVisibleItemCount?.() ?? 0));
@@ -17498,8 +17785,8 @@ function decodeChildBlock6(resource, child, childBuf) {
17498
17785
  if (relatedControllerIndex >= 0) component.setInstanceController(resource.listControllers()[relatedControllerIndex]?.getName() ?? "");
17499
17786
  }
17500
17787
  component.setInstancePage(childBuf.readS() ?? "");
17501
- childBuf.readS();
17502
- if (childBuf.readBool() && remainingBytes(childBuf) >= 4) childBuf.getFloat32();
17788
+ component.setInstanceSound(childBuf.readS() ?? "");
17789
+ if (childBuf.readBool() && remainingBytes(childBuf) >= 4) component.setInstanceSoundVolumeScale(childBuf.getFloat32());
17503
17790
  if (remainingBytes(childBuf) >= 1) component.setInstanceChecked(childBuf.readBool());
17504
17791
  break;
17505
17792
  case "Label":
@@ -18073,8 +18360,9 @@ const BinItemType$1 = {
18073
18360
  Font: 5,
18074
18361
  Swf: 6,
18075
18362
  Misc: 7,
18076
- Spine: 8,
18077
- DragonBones: 9
18363
+ Unknown: 8,
18364
+ Spine: 9,
18365
+ DragonBones: 10
18078
18366
  };
18079
18367
  function normalizePackageResourcePath(path) {
18080
18368
  const normalized = path.replace(/\\/g, "/").trim();
@@ -18375,11 +18663,13 @@ var BinaryReader = class {
18375
18663
  if (branchCnt2 > 0) if (branchIncluded) branchItemIds = buf.readSArray(branchCnt2);
18376
18664
  else branchItemIds = [buf.readS() ?? ""];
18377
18665
  const highResCnt = buf.getUint8();
18378
- if (highResCnt > 0) buf.readSArray(highResCnt);
18666
+ const highResolutionItemIds = [];
18667
+ for (let highResIndex = 0; highResIndex < highResCnt; highResIndex++) highResolutionItemIds.push(buf.readS());
18379
18668
  if (createdResource) {
18380
18669
  createdResource.setPath(itemPath);
18381
18670
  createdResource.setBranch(branchName);
18382
18671
  createdResource.setBranchItemIds(branchItemIds);
18672
+ createdResource.setHighResolutionItemIds?.(highResolutionItemIds);
18383
18673
  }
18384
18674
  }
18385
18675
  buf.pos = nextPos;
@@ -19910,8 +20200,13 @@ function _writeExtensionInstanceData(buf, extType, child, comp, pkg, version) {
19910
20200
  buf.writeInt16(ctrlIdx >= 0 ? ctrlIdx : -1);
19911
20201
  } else buf.writeInt16(-1);
19912
20202
  buf.writeS(child.getInstancePage?.() ?? null);
19913
- buf.writeSEx(null, false, false);
19914
- 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);
19915
20210
  buf.writeBool(child.getInstanceChecked?.() ?? false);
19916
20211
  break;
19917
20212
  }
@@ -20092,8 +20387,9 @@ const BinItemType = {
20092
20387
  Atlas: 4,
20093
20388
  Font: 5,
20094
20389
  Misc: 7,
20095
- Spine: 8,
20096
- DragonBones: 9
20390
+ Unknown: 8,
20391
+ Spine: 9,
20392
+ DragonBones: 10
20097
20393
  };
20098
20394
  /**
20099
20395
  * Maps our PropertyType to the editor's type string used for sorting.
@@ -20422,7 +20718,9 @@ var BinaryWriter = class {
20422
20718
  data.writeSEx(branchName || null);
20423
20719
  data.writeUint8(branchItemIds.length);
20424
20720
  for (const branchItemId of branchItemIds) data.writeSEx(branchItemId || null);
20425
- data.writeUint8(0);
20721
+ const highResolutionItemIds = getItemHighResolutionItemIds(res, publishedItemIdMap, packageItemIds);
20722
+ data.writeUint8(highResolutionItemIds.length);
20723
+ for (const highResolutionItemId of highResolutionItemIds) data.writeS(highResolutionItemId);
20426
20724
  }
20427
20725
  const nextPos = data.pos;
20428
20726
  const savedPos = data.pos;
@@ -20693,6 +20991,15 @@ function getItemBranchItemIds(item, branchNames, branchItemIdsMap) {
20693
20991
  if (!inferred) return [];
20694
20992
  return inferred.some((value) => !!value) ? [...inferred] : [];
20695
20993
  }
20994
+ function getItemHighResolutionItemIds(item, publishedItemIdMap, packageItemIds) {
20995
+ const resolvedIds = (item.getHighResolutionItemIds?.() ?? []).map((id) => {
20996
+ if (!id) return null;
20997
+ const publishedId = publishedItemIdMap.get(id) ?? id;
20998
+ return packageItemIds.has(publishedId) ? publishedId : null;
20999
+ });
21000
+ while (resolvedIds.length > 0 && !resolvedIds[resolvedIds.length - 1]) resolvedIds.pop();
21001
+ return resolvedIds;
21002
+ }
20696
21003
  function getAtlasId(atlas) {
20697
21004
  return `atlas${atlas.getIndex()}`;
20698
21005
  }
@@ -20721,8 +21028,8 @@ function getPixelHitTestEntry(resource) {
20721
21028
  /**
20722
21029
  * Abstract I/O base class for reading and writing FairyGUI projects.
20723
21030
  *
20724
- * Platform-specific subclasses (NodeIO, WebIO) implement the file system
20725
- * abstraction required by the reader/writer.
21031
+ * Platform-specific adapters provide the file system abstraction required by
21032
+ * the reader/writer.
20726
21033
  *
20727
21034
  * @category I/O
20728
21035
  */
@@ -20741,183 +21048,15 @@ var PlatformIO = class {
20741
21048
  }
20742
21049
  };
20743
21050
  //#endregion
20744
- //#region ../core/src/io/node-io.ts
20745
- /**
20746
- * Node.js I/O implementation for reading and writing FairyGUI projects.
20747
- *
20748
- * Usage:
20749
- *
20750
- * ```ts
20751
- * import { NodeIO } from '@openfairygui/core';
20752
- *
20753
- * const io = new NodeIO();
20754
- * const doc = await io.readProject('./path/to/project.fairy');
20755
- * await io.writeProject(doc, './path/to/output.fairy');
20756
- * const doc2 = await io.readBinary('./path/to/package_fui.bytes');
20757
- * ```
20758
- *
20759
- * @category I/O
20760
- */
20761
- var NodeIO = class extends PlatformIO {
20762
- createFileSystem() {
20763
- return {
20764
- async readFile(filePath) {
20765
- return fs$1.readFile(filePath, "utf-8");
20766
- },
20767
- async readFileRaw(filePath) {
20768
- const buf = await fs$1.readFile(filePath);
20769
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
20770
- },
20771
- async writeFile(filePath, content) {
20772
- await fs$1.writeFile(filePath, content, "utf-8");
20773
- },
20774
- async writeFileRaw(filePath, data) {
20775
- await fs$1.writeFile(filePath, data);
20776
- },
20777
- async mkdir(dirPath) {
20778
- await fs$1.mkdir(dirPath, { recursive: true });
20779
- },
20780
- async readdir(dirPath) {
20781
- return (await fs$1.readdir(dirPath, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
20782
- },
20783
- async exists(filePath) {
20784
- try {
20785
- await fs$1.access(filePath);
20786
- return true;
20787
- } catch {
20788
- return false;
20789
- }
20790
- },
20791
- join(...paths) {
20792
- return path$1.join(...paths);
20793
- },
20794
- dirname(filePath) {
20795
- return path$1.dirname(filePath);
20796
- }
20797
- };
20798
- }
20799
- };
20800
- //#endregion
20801
- //#region ../functions/src/inspect.ts
20802
- function mapResource(resource) {
20803
- return {
20804
- name: resource.getName(),
20805
- id: resource.getId(),
20806
- path: resource.getPath?.() ?? "/",
20807
- exported: resource.getExported?.() ?? false
20808
- };
20809
- }
20810
- function mapComponentDetail(component, totals) {
20811
- const children = component.listChildren();
20812
- const controllers = component.listControllers();
20813
- const transitions = component.listTransitions();
20814
- totals.displayObjects += children.length;
20815
- totals.controllers += controllers.length;
20816
- totals.transitions += transitions.length;
20817
- for (const child of children) totals.gears += child.listGears().length;
20818
- return {
20819
- name: component.getName(),
20820
- id: component.getId(),
20821
- childCount: children.length,
20822
- controllerCount: controllers.length,
20823
- transitionCount: transitions.length
20824
- };
20825
- }
20826
- /**
20827
- * Generates a detailed report of the project contents.
20828
- *
20829
- * Unlike other transforms, `inspect()` does NOT modify the document —
20830
- * it returns a structured report.
20831
- *
20832
- * ```ts
20833
- * const report = inspect(doc);
20834
- * console.log(`${report.totals.packages} packages, ${report.totals.components} components`);
20835
- * ```
20836
- */
20837
- function inspect(doc) {
20838
- const root = doc.getRoot();
20839
- const totals = {
20840
- packages: 0,
20841
- images: 0,
20842
- sounds: 0,
20843
- fonts: 0,
20844
- movieClips: 0,
20845
- components: 0,
20846
- displayObjects: 0,
20847
- gears: 0,
20848
- controllers: 0,
20849
- transitions: 0
20850
- };
20851
- const packages = root.listPackages().map((pkg) => {
20852
- totals.packages++;
20853
- const resources = pkg.listResources();
20854
- const images = resources.filter((r) => r.propertyType === "ImageResource");
20855
- const sounds = resources.filter((r) => r.propertyType === "SoundResource");
20856
- const fonts = resources.filter((r) => r.propertyType === "FontResource");
20857
- const movieClips = resources.filter((r) => r.propertyType === "MovieClipResource");
20858
- const components = pkg.listComponents();
20859
- totals.images += images.length;
20860
- totals.sounds += sounds.length;
20861
- totals.fonts += fonts.length;
20862
- totals.movieClips += movieClips.length;
20863
- totals.components += components.length;
20864
- const componentDetails = components.map((component) => mapComponentDetail(component, totals));
20865
- return {
20866
- name: pkg.getName(),
20867
- id: pkg.getId(),
20868
- publishName: pkg.getPublishName() || pkg.getName(),
20869
- resources: {
20870
- images: {
20871
- count: images.length,
20872
- details: images.map(mapResource)
20873
- },
20874
- sounds: {
20875
- count: sounds.length,
20876
- details: sounds.map(mapResource)
20877
- },
20878
- fonts: {
20879
- count: fonts.length,
20880
- details: fonts.map(mapResource)
20881
- },
20882
- movieClips: {
20883
- count: movieClips.length,
20884
- details: movieClips.map(mapResource)
20885
- },
20886
- components: {
20887
- count: components.length,
20888
- details: components.map(mapResource)
20889
- }
20890
- },
20891
- componentDetails
20892
- };
20893
- });
20894
- return {
20895
- projectId: root.getProjectId(),
20896
- projectType: root.getProjectType(),
20897
- version: root.getVersion(),
20898
- packages,
20899
- totals
20900
- };
20901
- }
20902
- //#endregion
20903
- //#region ../functions/src/utils.ts
20904
- /**
20905
- * Wraps a transform function, assigning it a name for the transform stack.
20906
- */
20907
- function createTransform(name, fn) {
20908
- Object.defineProperty(fn, "name", { value: name });
20909
- return fn;
20910
- }
20911
- //#endregion
20912
- //#region ../functions/src/max-rects-compat.ts
20913
- const NO_ROTATION = 2;
20914
- const MAX_SCORE = 2147483647;
20915
- const MAX_RECTS_METHOD = {
20916
- BestShortSideFit: 0,
20917
- BestLongSideFit: 1,
20918
- BestAreaFit: 2,
20919
- BottomLeftRule: 3,
20920
- ContactPointRule: 4
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
20921
21060
  };
20922
21061
  const COMPAT_NODE_RECT_FLAGS = {
20923
21062
  DUPLICATE_PADDING: 1,
@@ -21623,6 +21762,19 @@ const ATLAS_DEFAULTS = {
21623
21762
  function getPublishedItemId(resource) {
21624
21763
  return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
21625
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
+ }
21626
21778
  function resolveFontFileName(fontName) {
21627
21779
  return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
21628
21780
  }
@@ -21759,12 +21911,18 @@ function atlas(_options = {}) {
21759
21911
  const logger = doc.getLogger();
21760
21912
  const encoder = options.encoder;
21761
21913
  const doTrim = options.trimImage && !!encoder && !!options.basePath;
21914
+ const packageFilter = options.packages ? new Set(options.packages) : null;
21762
21915
  for (const pkg of root.listPackages()) {
21916
+ if (packageFilter && !packageFilter.has(pkg.getName())) continue;
21763
21917
  const selectedPublishIds = new Set((pkg.getExtras() ?? {}).publishedResourceIds ?? []);
21764
21918
  const allResources = selectedPublishIds.size > 0 ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
21919
+ const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
21765
21920
  const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
21766
21921
  const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
21767
- 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;
21768
21926
  const inputs = [];
21769
21927
  const referencedIds = /* @__PURE__ */ new Set();
21770
21928
  const resourceMap = /* @__PURE__ */ new Map();
@@ -21833,148 +21991,72 @@ function atlas(_options = {}) {
21833
21991
  }
21834
21992
  for (const res of orderedAllResources) if (isImageResource$1(res)) {
21835
21993
  const resId = res.getId();
21836
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21994
+ if (skeletonDependencyImageIds.has(resId)) continue;
21995
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21837
21996
  await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
21838
21997
  } else if (isMovieClipResource$1(res)) {
21839
21998
  const resId = res.getId();
21840
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21999
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21841
22000
  await _collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
21842
22001
  } else if (isFontResource$1(res)) {
21843
22002
  const resId = res.getId();
21844
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
22003
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
21845
22004
  await _collectFontTexture(doc, res, pkg, options);
21846
22005
  }
21847
22006
  if (inputs.length === 0) continue;
21848
- const branchGroups = buildBranchAtlasGroups(doc, inputs, options);
21849
22007
  let totalPageCount = 0;
21850
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();
21851
22012
  for (const group of branchGroups) {
21852
- const directOutput = resolveDirectImageOutput(group.inputs, options);
22013
+ const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0 ? resolveDirectImageOutput(group.inputs, options) : null;
21853
22014
  if (directOutput) {
21854
22015
  await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
21855
22016
  usedDirectOutput = true;
21856
22017
  totalPageCount += 1;
21857
22018
  continue;
21858
22019
  }
21859
- const hasDuplicatePadding = group.inputs.some((i) => {
21860
- 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
21861
22029
  });
21862
- const pages = new MaxRectsPackerCompat({
21863
- pot: options.powerOfTwo,
21864
- mof: !options.powerOfTwo,
21865
- padding: options.padding,
21866
- rotation: options.allowRotation,
21867
- minWidth: 16,
21868
- minHeight: 16,
21869
- maxWidth: options.maxSize,
21870
- maxHeight: options.maxSize,
21871
- square: options.square,
21872
- fast: options.fast,
21873
- edgePadding: false,
21874
- duplicatePadding: hasDuplicatePadding,
21875
- multiPage: options.multiPage,
21876
- preserveInputOrderOnTie: options.preserveInputOrderOnTie
21877
- }).pack(group.inputs.map((input, index) => inputToCompatRect(input, index)));
21878
- if (!pages || pages.length === 0) continue;
21879
- totalPageCount += pages.length;
21880
- for (let p = 0; p < pages.length; p++) {
21881
- const page = pages[p];
21882
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, p)}`);
21883
- atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, p));
21884
- atlasNode.setFile(resolveAtlasOutputFileName(pkg, p, group.branchName));
21885
- atlasNode.setWidth(page.width);
21886
- atlasNode.setHeight(page.height);
21887
- pkg.addAtlas(atlasNode);
21888
- for (const pr of page.outputRects) {
21889
- const input = group.inputs[pr.index];
21890
- if (!input) continue;
21891
- const packedSize = resolvePackedRectSize(input, pr.width, pr.height, pr.rotated);
21892
- const rotated = pr.rotated;
21893
- const sprite = doc.createSprite();
21894
- sprite.setItemId(input.id);
21895
- sprite.setRectX(pr.x);
21896
- sprite.setRectY(pr.y);
21897
- sprite.setRectWidth(packedSize.width);
21898
- sprite.setRectHeight(packedSize.height);
21899
- sprite.setRotated(rotated);
21900
- sprite.setOffsetX(input.offsetX);
21901
- sprite.setOffsetY(input.offsetY);
21902
- sprite.setOriginalWidth(input.originalWidth);
21903
- sprite.setOriginalHeight(input.originalHeight);
21904
- sprite.setAtlas(atlasNode);
21905
- atlasNode.addSprite(sprite);
21906
- }
21907
- for (const res of allResources) {
21908
- if (!isFontResource$1(res)) continue;
21909
- const alias = res.getExtras()?._fontSpriteAlias;
21910
- if (!alias) continue;
21911
- const imgSprite = page.outputRects.find((result) => group.inputs[result.index]?.id === alias.textureId);
21912
- if (!imgSprite) continue;
21913
- const imgInput = group.inputs[imgSprite.index];
21914
- const fontSprite = doc.createSprite();
21915
- fontSprite.setItemId(alias.fontId);
21916
- fontSprite.setRectX(imgSprite.x);
21917
- fontSprite.setRectY(imgSprite.y);
21918
- fontSprite.setRectWidth(imgSprite.width);
21919
- fontSprite.setRectHeight(imgSprite.height);
21920
- fontSprite.setRotated(imgSprite.rotated);
21921
- if (imgInput) {
21922
- fontSprite.setOffsetX(imgInput.offsetX);
21923
- fontSprite.setOffsetY(imgInput.offsetY);
21924
- fontSprite.setOriginalWidth(imgInput.originalWidth);
21925
- fontSprite.setOriginalHeight(imgInput.originalHeight);
21926
- }
21927
- fontSprite.setAtlas(atlasNode);
21928
- atlasNode.addSprite(fontSprite);
21929
- }
21930
- }
21931
- if (encoder && options.outputPath) {
21932
- if (options.mkdir) await options.mkdir(options.outputPath);
21933
- for (let p = 0; p < pages.length; p++) {
21934
- const page = pages[p];
21935
- const compositeInputs = [];
21936
- for (const pr of page.outputRects) {
21937
- const input = group.inputs[pr.index];
21938
- if (!input) continue;
21939
- if (pr.width <= 0 || pr.height <= 0 || input.width <= 0 || input.height <= 0) continue;
21940
- try {
21941
- let imgBuffer;
21942
- if (input.trimBuffer) {
21943
- imgBuffer = input.trimBuffer;
21944
- if (imgBuffer.length === 0) continue;
21945
- } else {
21946
- if (!isImageResource$1(input.resource)) {
21947
- logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
21948
- continue;
21949
- }
21950
- imgBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
21951
- }
21952
- if (pr.rotated) imgBuffer = await encoder(imgBuffer).rotate(270).toBuffer();
21953
- compositeInputs.push({
21954
- input: imgBuffer,
21955
- left: pr.x,
21956
- top: pr.y
21957
- });
21958
- } catch {
21959
- logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
21960
- }
21961
- }
21962
- const atlasFileName = resolveAtlasOutputFileName(pkg, p, group.branchName);
21963
- const outputFile = `${options.outputPath}/${atlasFileName}`;
21964
- await encoder({ create: {
21965
- width: page.width,
21966
- height: page.height,
21967
- channels: 4,
21968
- background: {
21969
- r: 0,
21970
- g: 0,
21971
- b: 0,
21972
- alpha: 0
21973
- }
21974
- } }).composite(compositeInputs).png().toFile(outputFile);
21975
- logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
21976
- }
21977
- }
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);
21978
22060
  }
21979
22061
  if (usedDirectOutput) logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
21980
22062
  logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
@@ -22011,6 +22093,171 @@ function buildBranchAtlasGroups(doc, inputs, options) {
22011
22093
  inputs: groups.get(branchName) ?? []
22012
22094
  }));
22013
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
+ }
22014
22261
  function inputToCompatRect(input, index) {
22015
22262
  const duplicatePadding = isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
22016
22263
  return {
@@ -22125,14 +22372,47 @@ function resolveAtlasOutputFileName(pkg, pageIndex, branchName) {
22125
22372
  const suffix = branchName ? `_${branchName}` : "";
22126
22373
  return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
22127
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
+ }
22128
22392
  function resolveImageFileName$1(resource) {
22129
22393
  const extras = resource.getExtras();
22130
22394
  return resource.getFileName() || extras._fileName || resource.getName();
22131
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
+ }
22132
22408
  function nextPow2(value) {
22133
22409
  if (value <= 1) return 1;
22134
22410
  return 2 ** Math.ceil(Math.log2(value));
22135
22411
  }
22412
+ function roundUpToMultiple(value, base) {
22413
+ if (value <= 0) return 0;
22414
+ return Math.ceil(value / base) * base;
22415
+ }
22136
22416
  function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
22137
22417
  const ordered = [...resources];
22138
22418
  ordered.sort((left, right) => {
@@ -22148,14 +22428,71 @@ function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
22148
22428
  });
22149
22429
  return ordered;
22150
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
+ }
22151
22488
  /**
22152
22489
  * Trim transparent edges from an image using sharp.
22153
22490
  * Returns the trimmed buffer, dimensions, and offsets.
22154
22491
  * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
22155
22492
  */
22156
- async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
22493
+ async function _trimImage(encoder, input, originalWidth, originalHeight) {
22157
22494
  try {
22158
- const trimResult = await encoder(filePath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
22495
+ const trimResult = await encoder(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
22159
22496
  if (!isResolvedBuffer(trimResult)) throw new Error("atlas: encoder raw alpha trim did not return resolved metadata.");
22160
22497
  const { data, info } = trimResult;
22161
22498
  const width = info.width;
@@ -22184,7 +22521,7 @@ async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
22184
22521
  const trimmedWidth = maxX - minX + 1;
22185
22522
  const trimmedHeight = maxY - minY + 1;
22186
22523
  return {
22187
- buffer: await encoder(filePath).extract({
22524
+ buffer: await encoder(input).extract({
22188
22525
  left: minX,
22189
22526
  top: minY,
22190
22527
  width: trimmedWidth,
@@ -22199,7 +22536,7 @@ async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
22199
22536
  };
22200
22537
  } catch {
22201
22538
  return {
22202
- buffer: await encoder(filePath).png().toBuffer(),
22539
+ buffer: await encoder(input).png().toBuffer(),
22203
22540
  width: originalWidth,
22204
22541
  height: originalHeight,
22205
22542
  offsetX: 0,
@@ -22223,7 +22560,10 @@ function _resolveImagePath(resource, pkg, basePath) {
22223
22560
  async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, logger) {
22224
22561
  let origW = resource.getWidth() ?? 0;
22225
22562
  let origH = resource.getHeight() ?? 0;
22563
+ const declaredWidth = origW;
22564
+ const declaredHeight = origH;
22226
22565
  let sourceHasAlpha = false;
22566
+ let rasterizedBuffer;
22227
22567
  if (encoder && options.basePath) {
22228
22568
  const filePath = _resolveImagePath(resource, pkg, options.basePath);
22229
22569
  try {
@@ -22235,6 +22575,10 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
22235
22575
  resource.setHeight(origH);
22236
22576
  }
22237
22577
  sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
22578
+ if (/\.svg$/i.test(resolveImageFileName$1(resource)) && declaredWidth > 0 && declaredHeight > 0) {
22579
+ rasterizedBuffer = await encoder(filePath).resize(declaredWidth, declaredHeight, { fit: "fill" }).png().toBuffer();
22580
+ sourceHasAlpha = true;
22581
+ }
22238
22582
  } catch {
22239
22583
  if (origW === 0 || origH === 0) {
22240
22584
  logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
@@ -22248,7 +22592,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
22248
22592
  if (doTrim && sourceHasAlpha && options.basePath && encoder) {
22249
22593
  const filePath = _resolveImagePath(resource, pkg, options.basePath);
22250
22594
  try {
22251
- const trimResult = await _trimImage(encoder, filePath, origW, origH);
22595
+ const trimResult = await _trimImage(encoder, rasterizedBuffer ?? filePath, origW, origH);
22252
22596
  packW = trimResult.width;
22253
22597
  packH = trimResult.height;
22254
22598
  offX = trimResult.offsetX;
@@ -22268,6 +22612,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
22268
22612
  offsetY: offY,
22269
22613
  resource,
22270
22614
  trimBuffer: trimBuf,
22615
+ rasterizedBuffer,
22271
22616
  sourceKind: "image"
22272
22617
  });
22273
22618
  }
@@ -22700,6 +23045,62 @@ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
22700
23045
  }
22701
23046
  `;
22702
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
22703
23104
  //#region ../functions/src/codegen.ts
22704
23105
  const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
22705
23106
  const DEFAULT_CLASS_NAME_PREFIX = "UI_";
@@ -22735,6 +23136,18 @@ async function publishCodeGeneration(doc, options) {
22735
23136
  const logger = doc.getLogger();
22736
23137
  const settings = resolveCodeGenerationSettings(doc);
22737
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
+ }
22738
23151
  for (const pkg of options.packages) {
22739
23152
  if (!pkg.getGenCode()) continue;
22740
23153
  const plan = resolvePackageCodegenPlan(pkg, settings, options);
@@ -22833,9 +23246,9 @@ async function cleanupGeneratedFiles(directory, fs, extension = ".cs") {
22833
23246
  }
22834
23247
  }
22835
23248
  function buildCodegenClasses(doc, pkg, plan) {
22836
- 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()));
22837
23250
  const generatedById = /* @__PURE__ */ new Map();
22838
- for (const component of exportedComponents) {
23251
+ for (const component of codegenComponents) {
22839
23252
  const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || "Component"}`;
22840
23253
  generatedById.set(component.getId(), {
22841
23254
  classId: component.getId(),
@@ -22848,7 +23261,13 @@ function buildCodegenClasses(doc, pkg, plan) {
22848
23261
  members: []
22849
23262
  });
22850
23263
  }
22851
- 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) {
22852
23271
  const classInfo = generatedById.get(component.getId());
22853
23272
  if (!classInfo) continue;
22854
23273
  classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
@@ -22862,7 +23281,12 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
22862
23281
  let childIndex = 0;
22863
23282
  let transitionIndex = 0;
22864
23283
  for (const controller of component.listControllers()) members.push(createMember(ownerType, "controller", "Controller", controller.getName(), controllerIndex++, plan));
22865
- 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
+ }
22866
23290
  for (const transition of component.listTransitions()) members.push(createMember(ownerType, "transition", "Transition", transition.getName(), transitionIndex++, plan));
22867
23291
  const usedNames = /* @__PURE__ */ new Map();
22868
23292
  for (const member of members) {
@@ -22874,7 +23298,10 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
22874
23298
  }
22875
23299
  return members;
22876
23300
  }
22877
- 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) {
22878
23305
  const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
22879
23306
  return {
22880
23307
  index,
@@ -22882,30 +23309,41 @@ function createMember(ownerType, kind, type, originalName, index, plan) {
22882
23309
  name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
22883
23310
  originalName,
22884
23311
  type,
22885
- ignored
23312
+ ignored,
23313
+ referencedComponent
22886
23314
  };
22887
23315
  }
22888
23316
  function resolveChildType(doc, pkg, child, generatedById) {
22889
23317
  const src = child.getSrc?.();
22890
23318
  if (src) {
22891
- const localResource = resolveChildSourceComponent(doc, pkg, src);
22892
- 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
+ };
22893
23343
  }
22894
23344
  const instanceExtType = child.getInstanceExtType?.();
22895
- if (instanceExtType) return `G${instanceExtType}`;
22896
- return child.propertyType;
22897
- }
22898
- function resolveChildSourceComponent(doc, pkg, src) {
22899
- if (!src) return null;
22900
- if (src.startsWith("ui://")) {
22901
- const rest = src.slice(5);
22902
- const pkgId = rest.slice(0, 8);
22903
- const resourceId = rest.slice(8);
22904
- const targetResource = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId)?.getResourceById(resourceId);
22905
- return targetResource?.propertyType === "Component" ? targetResource : null;
22906
- }
22907
- const localResource = pkg.getResourceById(src);
22908
- return localResource?.propertyType === "Component" ? localResource : null;
23345
+ if (instanceExtType) return { type: `G${instanceExtType}` };
23346
+ return { type: child.propertyType };
22909
23347
  }
22910
23348
  function resolveComponentBaseType(component) {
22911
23349
  const extensionType = component.getExtensionType();
@@ -22976,21 +23414,21 @@ function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant)
22976
23414
  return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
22977
23415
  }
22978
23416
  function resolveCodePath(codePath, basePath, fs) {
22979
- if (isAbsolutePath(codePath)) return trimTrailingSlashes$1(codePath);
23417
+ if (isAbsolutePath(codePath)) return trimTrailingSlashes$2(codePath);
22980
23418
  const projectBasePath = resolveProjectBasePath(basePath);
22981
- return projectBasePath ? trimTrailingSlashes$1(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$1(codePath);
23419
+ return projectBasePath ? trimTrailingSlashes$2(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$2(codePath);
22982
23420
  }
22983
23421
  function resolveProjectBasePath(basePath) {
22984
23422
  if (!basePath) return "";
22985
- const normalized = trimTrailingSlashes$1(basePath);
23423
+ const normalized = trimTrailingSlashes$2(basePath);
22986
23424
  const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
22987
23425
  if (assetsMatch?.[1]) return assetsMatch[1];
22988
23426
  return dirname$2(normalized);
22989
23427
  }
22990
23428
  function dirname$2(filePath) {
22991
- return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23429
+ return trimTrailingSlashes$2(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
22992
23430
  }
22993
- function trimTrailingSlashes$1(value) {
23431
+ function trimTrailingSlashes$2(value) {
22994
23432
  return value.replace(/[/\\]+$/, "");
22995
23433
  }
22996
23434
  function isAbsolutePath(value) {
@@ -22999,9 +23437,15 @@ function isAbsolutePath(value) {
22999
23437
  function isDefaultMemberName(ownerType, kind, name) {
23000
23438
  if (kind === "controller") return (ownerType === "GButton" || ownerType === "GComboBox") && name === "button";
23001
23439
  if (kind === "transition") return false;
23002
- if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") return name === "title" || name === "icon";
23003
- if (ownerType === "GProgressBar") return name === "bar" || name === "bar_v" || name === "title" || name === "ani";
23004
- 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
+ }
23005
23449
  return /^n\d+(?:_.*)?$/i.test(name);
23006
23450
  }
23007
23451
  function applyMemberNamePrefix(name, prefix) {
@@ -23290,11 +23734,11 @@ function inferPackageName(fileName) {
23290
23734
  if (/\.fui$/i.test(fileName)) return fileName.replace(/\.fui$/i, "");
23291
23735
  return fileName.replace(/\.bin$/i, "");
23292
23736
  }
23293
- function trimTrailingSlashes(value) {
23737
+ function trimTrailingSlashes$1(value) {
23294
23738
  return value.replace(/[/\\]+$/, "");
23295
23739
  }
23296
23740
  function normalizeComparablePath(value) {
23297
- const normalized = trimTrailingSlashes(value).replace(/\\/g, "/");
23741
+ const normalized = trimTrailingSlashes$1(value).replace(/\\/g, "/");
23298
23742
  const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
23299
23743
  const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
23300
23744
  const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
@@ -23314,14 +23758,14 @@ function normalizeComparablePath(value) {
23314
23758
  return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
23315
23759
  }
23316
23760
  function dirname$1(filePath) {
23317
- return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23761
+ return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23318
23762
  }
23319
23763
  function basename(filePath) {
23320
- return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
23764
+ return trimTrailingSlashes$1(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
23321
23765
  }
23322
23766
  function resolveOutputProjectPath(output, fs) {
23323
23767
  if (/\.fairy$/i.test(output)) return output;
23324
- const normalizedOutput = trimTrailingSlashes(output);
23768
+ const normalizedOutput = trimTrailingSlashes$1(output);
23325
23769
  const projectName = basename(normalizedOutput) || "Restored";
23326
23770
  return fs.join(normalizedOutput, `${projectName}.fairy`);
23327
23771
  }
@@ -23367,7 +23811,7 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
23367
23811
  await fs.mkdir(outputDir);
23368
23812
  }
23369
23813
  async function restore(options) {
23370
- const sourceDir = trimTrailingSlashes(options.inputDir);
23814
+ const sourceDir = trimTrailingSlashes$1(options.inputDir);
23371
23815
  const outputIsProjectFile = /\.fairy$/i.test(options.output);
23372
23816
  const outputProjectPath = resolveOutputProjectPath(options.output, options.fs);
23373
23817
  await prepareRestoreOutputDir(sourceDir, dirname$1(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
@@ -23509,16 +23953,23 @@ var RestoreWorkflow = class {
23509
23953
  async _ensureLooseImageResource(doc, pkg, owner, sourceDir, fileName) {
23510
23954
  const resources = pkg.listResources();
23511
23955
  const existing = this._findResourceByFile(resources, owner, "ImageResource", fileName);
23512
- if (existing) return existing;
23513
23956
  const sourcePath = await this._resolveLooseSourceFile(pkg, sourceDir, fileName);
23514
- 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
+ }
23515
23966
  const resource = doc.createImageResource(stripExtension(fileName));
23516
23967
  resource.setId(generateId()).setPath(owner.getPath?.() ?? "/").setBranch(owner.getBranch?.() ?? "").setBranchItemIds(owner.getBranchItemIds?.() ?? []).setExported(false).setFileName(fileName);
23517
23968
  resource.setExtras?.({
23518
23969
  ...resource.getExtras?.() ?? {},
23519
23970
  _publishedFile: fileBaseName(sourcePath),
23520
23971
  _suppressPackageSize: true,
23521
- _syntheticLooseImage: true
23972
+ _restoreAsLooseImage: true
23522
23973
  });
23523
23974
  pkg.addResource(resource);
23524
23975
  return resource;
@@ -23692,13 +24143,13 @@ var RestoreWorkflow = class {
23692
24143
  }
23693
24144
  async _copyLooseResources(pkg, options, warnings) {
23694
24145
  for (const resource of pkg.listResources()) {
23695
- const syntheticLooseImage = resource.getExtras?.()?._syntheticLooseImage === true;
24146
+ const restoreAsLooseImage = resource.getExtras?.()?._restoreAsLooseImage === true;
23696
24147
  if (![
23697
24148
  "SoundResource",
23698
24149
  "MiscResource",
23699
24150
  "SpineResource",
23700
24151
  "DragonBonesResource"
23701
- ].includes(resource.propertyType) && !syntheticLooseImage) continue;
24152
+ ].includes(resource.propertyType) && !restoreAsLooseImage) continue;
23702
24153
  const fileName = resourceFileName(resource);
23703
24154
  if (!fileName) continue;
23704
24155
  const sourcePath = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, resourcePublishedFileName(resource), fileName));
@@ -23868,6 +24319,26 @@ var RestoreWorkflow = class {
23868
24319
  };
23869
24320
  //#endregion
23870
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
+ }
23871
24342
  const UNITY_PROJECT_TYPE = ProjectType.Unity;
23872
24343
  const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
23873
24344
  function resolveDefaultPublishFileExtension(projectType, publishSettings) {
@@ -23917,6 +24388,19 @@ function resolvePublishOptions(doc, overrides = {}) {
23917
24388
  atlas: atlasOptions
23918
24389
  };
23919
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
+ }
23920
24404
  function dirname(filePath) {
23921
24405
  return filePath.replace(/[/\\]+$/, "").match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
23922
24406
  }
@@ -23947,6 +24431,9 @@ function isImageResource(resource) {
23947
24431
  function isMovieClipResource(resource) {
23948
24432
  return resource.propertyType === "MovieClipResource";
23949
24433
  }
24434
+ function isHighResolutionResource(resource) {
24435
+ return isImageResource(resource) || isMovieClipResource(resource);
24436
+ }
23950
24437
  function isMiscResource(resource) {
23951
24438
  return resource.propertyType === "MiscResource";
23952
24439
  }
@@ -24029,13 +24516,14 @@ function extname(fileName) {
24029
24516
  if (lastDot <= lastSlash) return "";
24030
24517
  return normalized.slice(lastDot);
24031
24518
  }
24032
- function resolvePublishedMiscFileName(resource) {
24519
+ function resolvePublishedMiscFileName(resource, projectType) {
24033
24520
  const file = resource.getFile();
24521
+ if (projectType !== UNITY_PROJECT_TYPE) return file;
24034
24522
  if (file.toLowerCase().endsWith(".atlas")) return `${file}.txt`;
24035
24523
  return file;
24036
24524
  }
24037
- function resolvePublishedSkeletonFileName(resource) {
24038
- 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`;
24039
24527
  return resource.getFile();
24040
24528
  }
24041
24529
  function setPublishedFileExtra(resource, fileName) {
@@ -24067,6 +24555,70 @@ function getBranchName(resource) {
24067
24555
  function buildBranchResourceKey(resource) {
24068
24556
  return `${resource.propertyType}|${resource.getPath() ?? ""}|${resource.getName() ?? ""}`;
24069
24557
  }
24558
+ const HIGH_RESOLUTION_LEVELS = [
24559
+ {
24560
+ scale: 2,
24561
+ bit: 1,
24562
+ slot: 0
24563
+ },
24564
+ {
24565
+ scale: 3,
24566
+ bit: 2,
24567
+ slot: 1
24568
+ },
24569
+ {
24570
+ scale: 4,
24571
+ bit: 4,
24572
+ slot: 2
24573
+ }
24574
+ ];
24575
+ function buildHighResolutionResourceKey(resource, name = resource.getName()) {
24576
+ return `${resource.propertyType}|${resource.getBranch?.() ?? ""}|${resource.getPath() ?? ""}|${name}`;
24577
+ }
24578
+ function isHighResolutionVariantName(name) {
24579
+ return /@(?:2|3|4)x(?:\.[^./\\]+)?$/iu.test(name);
24580
+ }
24581
+ function appendHighResolutionScaleToName(name, scale) {
24582
+ const extensionIndex = name.lastIndexOf(".");
24583
+ if (extensionIndex > 0) return `${name.slice(0, extensionIndex)}@${scale}x${name.slice(extensionIndex)}`;
24584
+ return `${name}@${scale}x`;
24585
+ }
24586
+ function trimTrailingMissingHighResolutionIds(ids) {
24587
+ while (ids.length > 0 && !ids[ids.length - 1]) ids.pop();
24588
+ return ids;
24589
+ }
24590
+ function collectHighResolutionItemIds(resources, publishedResourceIds, includeHighResolution) {
24591
+ const result = /* @__PURE__ */ new Map();
24592
+ if (includeHighResolution <= 0) return result;
24593
+ const highResolutionResourceByKey = /* @__PURE__ */ new Map();
24594
+ for (const resource of resources) {
24595
+ if (!isHighResolutionResource(resource)) continue;
24596
+ highResolutionResourceByKey.set(buildHighResolutionResourceKey(resource), resource);
24597
+ }
24598
+ for (const resource of resources) {
24599
+ if (!isHighResolutionResource(resource)) continue;
24600
+ if (!publishedResourceIds.has(resource.getId())) continue;
24601
+ if (isHighResolutionVariantName(resource.getName())) continue;
24602
+ const ids = [];
24603
+ for (const level of HIGH_RESOLUTION_LEVELS) {
24604
+ if ((includeHighResolution & level.bit) === 0) {
24605
+ ids[level.slot] = null;
24606
+ continue;
24607
+ }
24608
+ const highResolutionResource = highResolutionResourceByKey.get(buildHighResolutionResourceKey(resource, appendHighResolutionScaleToName(resource.getName(), level.scale)));
24609
+ if (!highResolutionResource) {
24610
+ ids[level.slot] = null;
24611
+ continue;
24612
+ }
24613
+ const highResolutionId = highResolutionResource.getId();
24614
+ publishedResourceIds.add(highResolutionId);
24615
+ ids[level.slot] = highResolutionId;
24616
+ }
24617
+ trimTrailingMissingHighResolutionIds(ids);
24618
+ if (ids.length > 0) result.set(resource.getId(), ids);
24619
+ }
24620
+ return result;
24621
+ }
24070
24622
  function collectPackagePublishContext(pkg, options) {
24071
24623
  const pkgId = pkg.getId();
24072
24624
  const resources = pkg.listResources();
@@ -24074,6 +24626,24 @@ function collectPackagePublishContext(pkg, options) {
24074
24626
  const referencedIds = /* @__PURE__ */ new Set();
24075
24627
  const pixelHitTestImageIds = /* @__PURE__ */ new Set();
24076
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
+ };
24077
24647
  for (const atlas of pkg.listAtlases()) for (const sprite of atlas.listSprites()) spriteItemIds.add(sprite.getItemId());
24078
24648
  for (const resource of resources) {
24079
24649
  if (!isComponentResource(resource)) continue;
@@ -24100,6 +24670,7 @@ function collectPackagePublishContext(pkg, options) {
24100
24670
  child.getSelectedIcon?.(),
24101
24671
  child.getDropdown?.(),
24102
24672
  child.getSound?.(),
24673
+ child.getInstanceSound?.(),
24103
24674
  child.getInstanceIcon?.(),
24104
24675
  child.getInstanceSelectedIcon?.(),
24105
24676
  child.getVtScrollBarRes?.(),
@@ -24157,19 +24728,8 @@ function collectPackagePublishContext(pkg, options) {
24157
24728
  }
24158
24729
  if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
24159
24730
  }
24160
- let changed = true;
24161
- while (changed) {
24162
- changed = false;
24163
- for (const resource of resources) {
24164
- if (!isSkeletonResource(resource)) continue;
24165
- if (!publishedResourceIds.has(resource.getId())) continue;
24166
- for (const requiredId of resource.getRequireIds()) {
24167
- if (!requiredId || publishedResourceIds.has(requiredId)) continue;
24168
- publishedResourceIds.add(requiredId);
24169
- changed = true;
24170
- }
24171
- }
24172
- }
24731
+ for (const resourceId of collectExportedResourceIds(resources, publishedResourceIds)) publishedResourceIds.add(resourceId);
24732
+ const highResolutionItemIds = collectHighResolutionItemIds(resources, publishedResourceIds, options.includeHighResolution);
24173
24733
  if (!options.includeBranches) {
24174
24734
  const mainByKey = /* @__PURE__ */ new Map();
24175
24735
  const activeBranchByKey = /* @__PURE__ */ new Map();
@@ -24217,7 +24777,9 @@ function collectPackagePublishContext(pkg, options) {
24217
24777
  return {
24218
24778
  referencedIds,
24219
24779
  publishedResourceIds,
24780
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
24220
24781
  pixelHitTestImageIds,
24782
+ highResolutionItemIds,
24221
24783
  effectiveResourceIds,
24222
24784
  includeBranches: false
24223
24785
  };
@@ -24225,7 +24787,9 @@ function collectPackagePublishContext(pkg, options) {
24225
24787
  return {
24226
24788
  referencedIds,
24227
24789
  publishedResourceIds,
24790
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
24228
24791
  pixelHitTestImageIds,
24792
+ highResolutionItemIds,
24229
24793
  effectiveResourceIds: new Map([...publishedResourceIds].map((resourceId) => [resourceId, resourceId])),
24230
24794
  includeBranches: true
24231
24795
  };
@@ -24274,28 +24838,36 @@ async function applyPixelHitTests(pkg, imageIds, basePath, encoder) {
24274
24838
  }
24275
24839
  }
24276
24840
  async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options) {
24277
- const { publishedResourceIds, pixelHitTestImageIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
24278
- for (const resource of pkg.listResources()) setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
24841
+ const { publishedResourceIds, exportedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
24842
+ for (const resource of pkg.listResources()) {
24843
+ setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
24844
+ if (isHighResolutionResource(resource)) resource.setHighResolutionItemIds(highResolutionItemIds.get(resource.getId()) ?? []);
24845
+ }
24279
24846
  await applyPixelHitTests(pkg, pixelHitTestImageIds, basePath, encoder);
24280
24847
  const extras = pkg.getExtras() ?? {};
24281
24848
  pkg.setExtras({
24282
24849
  ...extras,
24283
24850
  publishedResourceIds: [...publishedResourceIds].sort((a, b) => a.localeCompare(b)),
24851
+ exportedResourceIds: [...exportedResourceIds].sort((a, b) => a.localeCompare(b)),
24284
24852
  publishedIncludeBranches: includeBranches,
24285
24853
  publishedEffectiveResourceIds: Object.fromEntries(effectiveResourceIds)
24286
24854
  });
24287
24855
  for (const resource of pkg.listResources()) {
24288
24856
  if (isMiscResource(resource)) {
24289
- setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource));
24857
+ setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
24290
24858
  continue;
24291
24859
  }
24292
- if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource));
24860
+ if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
24293
24861
  }
24294
24862
  }
24295
24863
  function getAnnotatedPublishedResourceIds(pkg) {
24296
24864
  const extras = pkg.getExtras() ?? {};
24297
24865
  return new Set(extras.publishedResourceIds ?? []);
24298
24866
  }
24867
+ function getAnnotatedExportedResourceIds(pkg) {
24868
+ const extras = pkg.getExtras() ?? {};
24869
+ return new Set(extras.exportedResourceIds ?? []);
24870
+ }
24299
24871
  function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
24300
24872
  const imageIds = /* @__PURE__ */ new Set();
24301
24873
  const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource]));
@@ -24334,18 +24906,18 @@ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, lo
24334
24906
  }
24335
24907
  }
24336
24908
  async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw, logger) {
24337
- const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
24338
- const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds);
24339
- if (publishedResourceIds.size === 0) return;
24909
+ const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
24910
+ const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
24911
+ if (exportedResourceIds.size === 0) return;
24340
24912
  if (!basePath || !readFileRaw) {
24341
24913
  if (pkg.listResources().some((resource) => {
24342
- 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());
24343
24915
  })) logger.warn(`publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
24344
24916
  return;
24345
24917
  }
24346
24918
  for (const resource of pkg.listResources()) {
24347
24919
  const resourceId = resource.getId();
24348
- const isSkeletonExternal = publishedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
24920
+ const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
24349
24921
  const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
24350
24922
  if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
24351
24923
  let sourcePath;
@@ -24391,74 +24963,145 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
24391
24963
  */
24392
24964
  function publish(options) {
24393
24965
  return createTransform("publish", async (doc) => {
24394
- const root = doc.getRoot();
24395
- const logger = doc.getLogger();
24396
- const publishSettings = (root.getSettings?.() ?? {}).publish ?? {};
24397
- const resolved = resolvePublishOptions(doc, {
24398
- compressed: options.compressed,
24399
- fileExtension: options.fileExtension,
24400
- packages: options.packages,
24401
- atlas: options.atlas
24402
- });
24403
- const ext = resolved.fileExtension;
24404
- let allPackages = root.listPackages();
24405
- if (resolved.packages && resolved.packages.length > 0) {
24406
- const names = new Set(resolved.packages);
24407
- allPackages = allPackages.filter((p) => names.has(p.getName()));
24408
- }
24409
- if (allPackages.length === 0) {
24410
- logger.warn("publish: No packages to publish.");
24411
- return;
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
+ };
25055
+ const root = doc.getRoot();
25056
+ const logger = doc.getLogger();
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();
25062
+ let allPackages = root.listPackages();
25063
+ if (resolved.packages && resolved.packages.length > 0) {
25064
+ const names = new Set(resolved.packages);
25065
+ allPackages = allPackages.filter((p) => names.has(p.getName()));
25066
+ }
25067
+ if (allPackages.length === 0) {
25068
+ logger.warn("publish: No packages to publish.");
25069
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
25070
+ return;
24412
25071
  }
24413
- const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
24414
- const activeBranch = includeBranches ? "" : options.branch ?? "";
24415
- const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(ext);
24416
25072
  const allDocPackages = root.listPackages();
24417
25073
  const pkgMap = /* @__PURE__ */ new Map();
24418
25074
  for (const p of allDocPackages) pkgMap.set(p.getId(), p);
24419
25075
  for (const pkg of allPackages) {
24420
- _computeDependencies(pkg, pkgMap);
25076
+ _computeDependencies(doc, pkg, pkgMap);
24421
25077
  await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
24422
- includeBranches,
24423
- activeBranch
25078
+ projectType: resolved.projectType,
25079
+ includeBranches: resolved.includeBranches,
25080
+ activeBranch: resolved.activeBranch,
25081
+ includeHighResolution: resolved.includeHighResolution
24424
25082
  });
24425
25083
  }
24426
- await atlas({
24427
- ...resolved.atlas,
24428
- ...options.atlas ?? {},
24429
- separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
24430
- encoder: options.encoder,
24431
- basePath: options.basePath,
24432
- outputPath: options.fs ? options.output : void 0,
24433
- mkdir: options.fs ? options.fs.mkdir : void 0,
24434
- readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
24435
- ...atlasRuntimeOptions
24436
- })(doc);
25084
+ const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
24437
25085
  if (!options.fs) {
24438
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);
24439
25090
  return;
24440
25091
  }
24441
- 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.`);
24442
25094
  const writerFs = toBinaryWriterFileSystem(options.fs);
24443
- for (const pkg of allPackages) {
24444
- const pkgIndex = allDocPackages.indexOf(pkg);
24445
- const fileName = resolvePublishFileName(pkg.getPublishName() || pkg.getName(), ext);
24446
- const filePath = options.fs.join(options.output, fileName);
24447
- const bwOptions = {
24448
- compressed: resolved.compressed,
24449
- packageIndex: pkgIndex
24450
- };
24451
- await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
24452
- await exportPackageSounds(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
24453
- await exportPackageExternalResources(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
24454
- logger.info(`publish: Written ${fileName}`);
24455
- }
25095
+ for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
24456
25096
  await publishCodeGeneration(doc, {
24457
25097
  basePath: options.basePath,
24458
25098
  fs: options.fs,
24459
- packages: allPackages
25099
+ packages: allPackages,
25100
+ plugins
24460
25101
  });
24461
- 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);
24462
25105
  });
24463
25106
  }
24464
25107
  /**
@@ -24466,25 +25109,104 @@ function publish(options) {
24466
25109
  * The editor only adds dependencies for packages referenced via bitmap font URLs.
24467
25110
  * @internal
24468
25111
  */
24469
- function _computeDependencies(pkg, pkgMap) {
25112
+ function _computeDependencies(doc, pkg, pkgMap) {
24470
25113
  const referencedPkgIds = /* @__PURE__ */ new Set();
24471
- function scanFontUrl(font) {
24472
- if (!font) return;
24473
- const fontStr = Array.isArray(font) ? font[0] : String(font);
24474
- if (typeof fontStr !== "string" || !fontStr.startsWith("ui://")) return;
24475
- const rest = fontStr.slice(5);
24476
- if (rest.length >= 8) {
24477
- const depPkgId = rest.slice(0, 8);
24478
- 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;
24479
25143
  }
24480
- }
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
+ };
24481
25156
  for (const res of pkg.listResources()) {
24482
25157
  if (res.propertyType !== "Component") continue;
24483
- 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
+ }
24484
25201
  }
24485
25202
  for (const dep of pkg.listDependencies()) pkg.removeDependency(dep);
24486
25203
  if (referencedPkgIds.size > 0) {
24487
- 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
+ });
24488
25210
  for (const refId of sortedIds) {
24489
25211
  const depPkg = pkgMap.get(refId);
24490
25212
  if (depPkg) pkg.addDependency(depPkg);
@@ -24492,83 +25214,238 @@ function _computeDependencies(pkg, pkgMap) {
24492
25214
  }
24493
25215
  }
24494
25216
  //#endregion
24495
- //#region src/cli.ts
24496
- const HELP = `
24497
- ofgui FairyGUI Headless Authoring CLI
24498
-
24499
- Alias:
24500
- openfairygui
24501
-
24502
- Commands:
24503
- inspect <project-dir> Show project contents report
24504
- publish <project-dir> --output <dir> [options] Publish project to binary outputs and configured generated code
24505
- restore <release-dir> --output <dir> [options] Restore a FairyGUI project from published binaries
24506
- backend-capabilities <project-dir> Open a backend session, print runtime capabilities, then close it
24507
-
24508
- Publish options:
24509
- --output, -o <dir> Output directory (required)
24510
- --compressed Compress binary data (overrides project setting)
24511
- --packages <a,b,c> Only publish specific packages (comma-separated)
24512
- --branch <name> Active branch used by "主干合并活跃分支"; omit for main branch
24513
- --project-type <name|id> Override project type (for example: unity, layabox, cocoscreator, 0, 4, 3)
24514
-
24515
- Restore options:
24516
- --output, -o <dir> Output project directory (required)
24517
- --packages <a,b,c> Only restore specific packages (comma-separated)
24518
- --force Overwrite a non-empty output directory
24519
- --project-type <name|id> Override restored project type; default is unity
24520
-
24521
- Options:
24522
- --help, -h Show this help
24523
- --version, -v Show version
24524
-
24525
- Input can be a .fairy file or a project root directory (auto-discovers .fairy file).
24526
- File extension and binary format are read from project settings.
24527
- `;
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
24528
25284
  /** Resolve input to a .fairy file path. Accepts a directory or a .fairy file. */
24529
25285
  async function resolveFairyPath(input) {
24530
25286
  const resolved = path.resolve(input);
24531
25287
  const stat = await fs.stat(resolved);
24532
25288
  if (stat.isFile() && resolved.endsWith(".fairy")) return resolved;
24533
25289
  if (stat.isDirectory()) {
24534
- const fairyFiles = (await fs.readdir(resolved)).filter((e) => e.endsWith(".fairy"));
25290
+ const fairyFiles = (await fs.readdir(resolved)).filter((entry) => entry.endsWith(".fairy"));
24535
25291
  if (fairyFiles.length === 1) return path.join(resolved, fairyFiles[0]);
24536
25292
  if (fairyFiles.length > 1) throw new Error(`Multiple .fairy files found in ${resolved}: ${fairyFiles.join(", ")}. Please specify one.`);
24537
25293
  throw new Error(`No .fairy file found in ${resolved}`);
24538
25294
  }
24539
25295
  throw new Error(`Input is not a .fairy file or directory: ${resolved}`);
24540
25296
  }
24541
- async function main() {
24542
- const args = process.argv.slice(2);
24543
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
24544
- console.log(HELP);
24545
- return;
24546
- }
24547
- if (args.includes("--version") || args.includes("-v")) {
24548
- console.log("0.1.0");
24549
- return;
24550
- }
24551
- const command = args[0];
24552
- const rest = args.slice(1);
24553
- switch (command) {
24554
- case "inspect":
24555
- await cmdInspect(rest);
24556
- break;
24557
- case "publish":
24558
- await cmdPublish(rest);
24559
- break;
24560
- case "restore":
24561
- await cmdRestore(rest);
24562
- break;
24563
- case "backend-capabilities":
24564
- await cmdBackendCapabilities(rest);
24565
- break;
24566
- default:
24567
- console.error(`Unknown command: ${command}\n`);
24568
- console.log(HELP);
24569
- 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`);
24570
25323
  }
24571
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
+ }
24572
25449
  function createNodeRestoreFs() {
24573
25450
  return {
24574
25451
  async readFile(filePath) {
@@ -24677,214 +25554,47 @@ async function createRestoreImageProcessors() {
24677
25554
  }
24678
25555
  };
24679
25556
  }
24680
- async function cmdInspect(args) {
24681
- if (args.length === 0) {
24682
- console.error("Usage: ofgui inspect <project-dir>");
24683
- process.exit(1);
24684
- }
24685
- const fairyPath = await resolveFairyPath(args[0]);
24686
- console.log(`Project: ${fairyPath}\n`);
24687
- printReport(inspect(await new NodeIO().readProject(fairyPath)));
24688
- }
24689
- function printReport(report) {
24690
- console.log(`ID: ${report.projectId}`);
24691
- console.log(`Type: ${report.projectType}, Version: ${report.version}`);
24692
- console.log(`\nPackages: ${report.totals.packages}`);
24693
- console.log(` Images: ${report.totals.images}`);
24694
- console.log(` Sounds: ${report.totals.sounds}`);
24695
- console.log(` Fonts: ${report.totals.fonts}`);
24696
- console.log(` MovieClips: ${report.totals.movieClips}`);
24697
- console.log(` Components: ${report.totals.components}`);
24698
- console.log(` DisplayObjs: ${report.totals.displayObjects}`);
24699
- console.log(` Gears: ${report.totals.gears}`);
24700
- console.log(` Controllers: ${report.totals.controllers}`);
24701
- console.log(` Transitions: ${report.totals.transitions}`);
24702
- console.log("\nPackage details:");
24703
- for (const pkg of report.packages) {
24704
- const res = pkg.resources;
24705
- console.log(` ${pkg.name} (${pkg.id}): ${res.images.count} img, ${res.sounds.count} snd, ${res.fonts.count} font, ${res.components.count} comp`);
24706
- }
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;
25563
+ }
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";
24707
25572
  }
24708
- function parseProjectType(value) {
24709
- if (!value) return void 0;
24710
- const trimmed = value.trim();
24711
- if (trimmed === "") return void 0;
24712
- if (/^\d+$/u.test(trimmed)) return Number(trimmed);
24713
- const normalized = trimmed.toLowerCase();
24714
- const map = {
24715
- unity: ProjectType.Unity,
24716
- flash: ProjectType.Flash,
24717
- starling: ProjectType.Starling,
24718
- cocoscreator: ProjectType.CocosCreator,
24719
- cocos: ProjectType.CocosCreator,
24720
- layabox: ProjectType.LayaBox,
24721
- laya: ProjectType.LayaBox,
24722
- egret: ProjectType.Egret,
24723
- haxe: ProjectType.Haxe,
24724
- pixi: ProjectType.Pixi,
24725
- libgdx: ProjectType.LibGDX,
24726
- unreal: ProjectType.Unreal,
24727
- cryengine: ProjectType.CryEngine,
24728
- monogame: ProjectType.MonoGame,
24729
- vision: ProjectType.Vision
24730
- };
24731
- const resolved = map[normalized];
24732
- if (resolved === void 0) throw new Error(`Unknown project type: ${value}. Use a numeric id or one of: ${Object.keys(map).join(", ")}`);
24733
- 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;
24734
25592
  }
24735
- async function cmdRestore(args) {
24736
- const { values, positionals } = parseArgs({
24737
- args,
24738
- options: {
24739
- output: {
24740
- type: "string",
24741
- short: "o"
24742
- },
24743
- packages: { type: "string" },
24744
- force: { type: "boolean" },
24745
- "project-type": { type: "string" }
24746
- },
24747
- allowPositionals: true
24748
- });
24749
- if (positionals.length === 0 || !values.output) {
24750
- console.error("Usage: ofgui restore <release-dir> --output <dir> [--packages a,b,c] [--force]");
24751
- process.exit(1);
24752
- }
24753
- const releaseDir = path.resolve(positionals[0]);
24754
- const outputDir = path.resolve(values.output);
24755
- const pkgFilter = values.packages ? values.packages.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
24756
- const projectType = parseProjectType(values["project-type"]);
24757
- const { cropImage, extractImage } = await createRestoreImageProcessors();
24758
- console.log(`Restoring published FairyGUI project: ${releaseDir}`);
24759
- const result = await restore({
24760
- inputDir: releaseDir,
24761
- output: outputDir,
24762
- fs: createNodeRestoreFs(),
24763
- packages: pkgFilter,
24764
- force: values.force,
24765
- projectType,
24766
- cropImage,
24767
- extractImage
24768
- });
24769
- const packages = result.document.getRoot().listPackages();
24770
- console.log(`\nDone! Output: ${result.projectPath}`);
24771
- console.log(`Packages: ${packages.map((pkg) => pkg.getName()).join(", ")}`);
24772
- for (const warning of result.warnings) console.warn(`Warning: ${warning}`);
24773
- }
24774
- async function cmdPublish(args) {
24775
- const { values, positionals } = parseArgs({
24776
- args,
24777
- options: {
24778
- output: {
24779
- type: "string",
24780
- short: "o"
24781
- },
24782
- compressed: { type: "boolean" },
24783
- packages: { type: "string" },
24784
- branch: { type: "string" },
24785
- "project-type": { type: "string" }
24786
- },
24787
- allowPositionals: true
24788
- });
24789
- if (positionals.length === 0 || !values.output) {
24790
- console.error("Usage: ofgui publish <project-dir> --output <dir> [--compressed] [--packages a,b,c] [--branch name]");
24791
- process.exit(1);
24792
- }
24793
- const fairyPath = await resolveFairyPath(positionals[0]);
24794
- const projectDir = path.dirname(fairyPath);
24795
- const outputDir = path.resolve(values.output);
24796
- console.log(`Reading project: ${fairyPath}`);
24797
- const doc = await new NodeIO().readProject(fairyPath);
24798
- const projectType = parseProjectType(values["project-type"]);
24799
- if (projectType !== void 0) doc.getRoot().setProjectType(projectType);
24800
- const pkgFilter = values.packages ? values.packages.split(",").map((s) => s.trim()) : void 0;
24801
- const resolved = resolvePublishOptions(doc, {
24802
- compressed: values.compressed,
24803
- packages: pkgFilter
24804
- });
24805
- console.log(`Settings: ext=${resolved.fileExtension}, compressed=${resolved.compressed}`);
24806
- if (values.branch) console.log(`Active branch: ${values.branch}`);
24807
- const atlasConfig = {
24808
- ...resolved.atlas,
24809
- readFileRaw: async (filePath) => {
24810
- const buf = await fs.readFile(filePath);
24811
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
24812
- }
24813
- };
24814
- let encoder;
24815
- try {
24816
- const sharp = await import("sharp");
24817
- encoder = sharp.default ?? sharp;
24818
- console.log("Sharp loaded — atlas PNGs will be generated.");
24819
- } catch {
24820
- console.log("Sharp not available — atlas PNGs will NOT be generated (layout only).");
24821
- console.log(" Install sharp to enable: pnpm add sharp");
24822
- }
24823
- await doc.transform(publish({
24824
- output: outputDir,
24825
- compressed: resolved.compressed,
24826
- fileExtension: resolved.fileExtension,
24827
- packages: resolved.packages,
24828
- fs: {
24829
- async readFileRaw(filePath) {
24830
- const buf = await fs.readFile(filePath);
24831
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
24832
- },
24833
- async writeFileRaw(filePath, data) {
24834
- await fs.mkdir(path.dirname(filePath), { recursive: true });
24835
- await fs.writeFile(filePath, data);
24836
- },
24837
- async mkdir(dirPath) {
24838
- await fs.mkdir(dirPath, { recursive: true });
24839
- },
24840
- async readdir(dirPath) {
24841
- return fs.readdir(dirPath);
24842
- },
24843
- async deleteFile(filePath) {
24844
- await fs.rm(filePath, { force: true });
24845
- },
24846
- join(...paths) {
24847
- return path.join(...paths);
24848
- }
24849
- },
24850
- encoder,
24851
- basePath: path.join(projectDir, "assets"),
24852
- atlas: atlasConfig,
24853
- branch: values.branch
24854
- }));
24855
- console.log(`\nDone! Output: ${outputDir}`);
24856
- }
24857
- async function cmdBackendCapabilities(args) {
24858
- if (args.length === 0) {
24859
- console.error("Usage: ofgui backend-capabilities <project-dir>");
24860
- process.exit(1);
24861
- }
24862
- const runtime = createNodeBackendRuntime();
24863
- const opened = await runtime.openSession({ projectPath: path.resolve(args[0]) });
24864
- if (!opened.ok) {
24865
- console.error(`backend-capabilities: ${opened.error.message}`);
24866
- process.exit(1);
24867
- }
24868
- const capabilities = runtime.getCapabilities();
24869
- if (!capabilities.ok) {
24870
- console.error("backend-capabilities: failed to read capabilities");
24871
- await runtime.closeSession({ sessionId: opened.data.sessionId });
24872
- process.exit(1);
24873
- }
24874
- console.log(`Session: ${opened.data.sessionId}`);
24875
- console.log(`Project: ${opened.data.canonicalProjectPath}`);
24876
- console.log(`Revision: ${opened.data.revision}`);
24877
- console.log(`Runtime owner: ${capabilities.data.runtimeOwner}`);
24878
- console.log(`Transaction owner: ${capabilities.data.transactionKernelOwner}`);
24879
- console.log(`App seam owner: ${capabilities.data.appSeamOwner}`);
24880
- const closed = await runtime.closeSession({ sessionId: opened.data.sessionId });
24881
- if (!closed.ok) {
24882
- console.error(`backend-capabilities: ${closed.error.message}`);
24883
- process.exit(1);
24884
- }
25593
+ async function main() {
25594
+ await createProgram().parseAsync(process.argv);
24885
25595
  }
24886
25596
  main().catch((err) => {
24887
- console.error(err);
25597
+ console.error(err instanceof Error ? err.message : String(err));
24888
25598
  process.exit(1);
24889
25599
  });
24890
25600
  //#endregion