@openfairygui/functions 0.2.0-alpha.0 → 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/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_uam_transaction = require("./uam-transaction.cjs");
2
3
  let _openfairygui_core = require("@openfairygui/core");
4
+ let jiti = require("jiti");
3
5
  //#region src/inspect.ts
4
6
  function mapResource(resource) {
5
7
  return {
@@ -110,6 +112,40 @@ function createTransform(name, fn) {
110
112
  Object.defineProperty(fn, "name", { value: name });
111
113
  return fn;
112
114
  }
115
+ function parseTextureSetMode(value) {
116
+ const raw = value?.trim() ?? "";
117
+ if (!raw) return {
118
+ kind: "auto",
119
+ raw: ""
120
+ };
121
+ if (raw === "alone") return {
122
+ kind: "standalone",
123
+ raw,
124
+ sizeMode: "default"
125
+ };
126
+ if (raw === "alone_npot") return {
127
+ kind: "standalone",
128
+ raw,
129
+ sizeMode: "npot"
130
+ };
131
+ if (raw === "alone_mof") return {
132
+ kind: "standalone",
133
+ raw,
134
+ sizeMode: "multipleOf4"
135
+ };
136
+ if (/^\d+$/.test(raw)) {
137
+ const pageIndex = Number(raw);
138
+ if (pageIndex >= 0 && pageIndex <= 10) return {
139
+ kind: "page",
140
+ raw,
141
+ pageIndex
142
+ };
143
+ }
144
+ return {
145
+ kind: "auto",
146
+ raw
147
+ };
148
+ }
113
149
  //#endregion
114
150
  //#region src/validate.ts
115
151
  /**
@@ -1041,6 +1077,19 @@ const ATLAS_DEFAULTS = {
1041
1077
  function getPublishedItemId(resource) {
1042
1078
  return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
1043
1079
  }
1080
+ function getSelectedSkeletonDependencyImageIds(resources) {
1081
+ const imageIds = /* @__PURE__ */ new Set();
1082
+ const resourcesById = new Map(resources.map((resource) => [resource.getId(), resource]));
1083
+ for (const resource of resources) {
1084
+ if (!isSkeletonResource$1(resource)) continue;
1085
+ for (const requiredId of resource.getRequireIds()) {
1086
+ if (!requiredId) continue;
1087
+ const required = resourcesById.get(requiredId);
1088
+ if (required && isImageResource$1(required)) imageIds.add(requiredId);
1089
+ }
1090
+ }
1091
+ return imageIds;
1092
+ }
1044
1093
  function resolveFontFileName(fontName) {
1045
1094
  return /\.fnt$/i.test(fontName) ? fontName : `${fontName}.fnt`;
1046
1095
  }
@@ -1177,12 +1226,18 @@ function atlas(_options = {}) {
1177
1226
  const logger = doc.getLogger();
1178
1227
  const encoder = options.encoder;
1179
1228
  const doTrim = options.trimImage && !!encoder && !!options.basePath;
1229
+ const packageFilter = options.packages ? new Set(options.packages) : null;
1180
1230
  for (const pkg of root.listPackages()) {
1231
+ if (packageFilter && !packageFilter.has(pkg.getName())) continue;
1181
1232
  const selectedPublishIds = new Set((pkg.getExtras() ?? {}).publishedResourceIds ?? []);
1182
1233
  const allResources = selectedPublishIds.size > 0 ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
1234
+ const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
1183
1235
  const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
1184
1236
  const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
1185
- if (!allResources.some((resource) => isPackableResource(resource))) continue;
1237
+ if (!allResources.some((resource) => {
1238
+ if (isImageResource$1(resource) && skeletonDependencyImageIds.has(resource.getId())) return false;
1239
+ return isPackableResource(resource);
1240
+ })) continue;
1186
1241
  const inputs = [];
1187
1242
  const referencedIds = /* @__PURE__ */ new Set();
1188
1243
  const resourceMap = /* @__PURE__ */ new Map();
@@ -1251,148 +1306,72 @@ function atlas(_options = {}) {
1251
1306
  }
1252
1307
  for (const res of orderedAllResources) if (isImageResource$1(res)) {
1253
1308
  const resId = res.getId();
1254
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1309
+ if (skeletonDependencyImageIds.has(resId)) continue;
1310
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1255
1311
  await _collectImage(res, pkg, inputs, encoder, options, doTrim, logger);
1256
1312
  } else if (isMovieClipResource$1(res)) {
1257
1313
  const resId = res.getId();
1258
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1314
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1259
1315
  await _collectMovieClipFrames(doc, res, pkg, inputs, encoder, options, logger);
1260
1316
  } else if (isFontResource$1(res)) {
1261
1317
  const resId = res.getId();
1262
- if (!res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1318
+ if (selectedPublishIds.size === 0 && !res.getExported() && referencedIds.size > 0 && !referencedIds.has(resId)) continue;
1263
1319
  await _collectFontTexture(doc, res, pkg, options);
1264
1320
  }
1265
1321
  if (inputs.length === 0) continue;
1266
- const branchGroups = buildBranchAtlasGroups(doc, inputs, options);
1267
1322
  let totalPageCount = 0;
1268
1323
  let usedDirectOutput = false;
1324
+ const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
1325
+ const branchGroups = buildBranchAtlasGroups(doc, autoInputs, options);
1326
+ const branchPageOffsets = /* @__PURE__ */ new Map();
1269
1327
  for (const group of branchGroups) {
1270
- const directOutput = resolveDirectImageOutput(group.inputs, options);
1328
+ const directOutput = fixedPageGroups.length === 0 && standaloneGroups.length === 0 ? resolveDirectImageOutput(group.inputs, options) : null;
1271
1329
  if (directOutput) {
1272
1330
  await emitDirectImageOutput(doc, pkg, directOutput, encoder, options, logger, group.branchName, group.branchOrdinal);
1273
1331
  usedDirectOutput = true;
1274
1332
  totalPageCount += 1;
1275
1333
  continue;
1276
1334
  }
1277
- const hasDuplicatePadding = group.inputs.some((i) => {
1278
- return isImageResource$1(i.resource) && i.resource.getDuplicatePadding?.() === true;
1335
+ const pageStart = reserveAutoPageStart(branchPageOffsets, group.branchOrdinal, reservedPageIndexes);
1336
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1337
+ branchName: group.branchName,
1338
+ branchOrdinal: group.branchOrdinal,
1339
+ pageStart,
1340
+ fileNameAt: (pageIndex) => resolveAtlasOutputFileName(pkg, pageIndex, group.branchName),
1341
+ options,
1342
+ encoder,
1343
+ logger
1279
1344
  });
1280
- const pages = new MaxRectsPackerCompat({
1281
- pot: options.powerOfTwo,
1282
- mof: !options.powerOfTwo,
1283
- padding: options.padding,
1284
- rotation: options.allowRotation,
1285
- minWidth: 16,
1286
- minHeight: 16,
1287
- maxWidth: options.maxSize,
1288
- maxHeight: options.maxSize,
1289
- square: options.square,
1290
- fast: options.fast,
1291
- edgePadding: false,
1292
- duplicatePadding: hasDuplicatePadding,
1293
- multiPage: options.multiPage,
1294
- preserveInputOrderOnTie: options.preserveInputOrderOnTie
1295
- }).pack(group.inputs.map((input, index) => inputToCompatRect(input, index)));
1296
- if (!pages || pages.length === 0) continue;
1297
- totalPageCount += pages.length;
1298
- for (let p = 0; p < pages.length; p++) {
1299
- const page = pages[p];
1300
- const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, p)}`);
1301
- atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, p));
1302
- atlasNode.setFile(resolveAtlasOutputFileName(pkg, p, group.branchName));
1303
- atlasNode.setWidth(page.width);
1304
- atlasNode.setHeight(page.height);
1305
- pkg.addAtlas(atlasNode);
1306
- for (const pr of page.outputRects) {
1307
- const input = group.inputs[pr.index];
1308
- if (!input) continue;
1309
- const packedSize = resolvePackedRectSize(input, pr.width, pr.height, pr.rotated);
1310
- const rotated = pr.rotated;
1311
- const sprite = doc.createSprite();
1312
- sprite.setItemId(input.id);
1313
- sprite.setRectX(pr.x);
1314
- sprite.setRectY(pr.y);
1315
- sprite.setRectWidth(packedSize.width);
1316
- sprite.setRectHeight(packedSize.height);
1317
- sprite.setRotated(rotated);
1318
- sprite.setOffsetX(input.offsetX);
1319
- sprite.setOffsetY(input.offsetY);
1320
- sprite.setOriginalWidth(input.originalWidth);
1321
- sprite.setOriginalHeight(input.originalHeight);
1322
- sprite.setAtlas(atlasNode);
1323
- atlasNode.addSprite(sprite);
1324
- }
1325
- for (const res of allResources) {
1326
- if (!isFontResource$1(res)) continue;
1327
- const alias = res.getExtras()?._fontSpriteAlias;
1328
- if (!alias) continue;
1329
- const imgSprite = page.outputRects.find((result) => group.inputs[result.index]?.id === alias.textureId);
1330
- if (!imgSprite) continue;
1331
- const imgInput = group.inputs[imgSprite.index];
1332
- const fontSprite = doc.createSprite();
1333
- fontSprite.setItemId(alias.fontId);
1334
- fontSprite.setRectX(imgSprite.x);
1335
- fontSprite.setRectY(imgSprite.y);
1336
- fontSprite.setRectWidth(imgSprite.width);
1337
- fontSprite.setRectHeight(imgSprite.height);
1338
- fontSprite.setRotated(imgSprite.rotated);
1339
- if (imgInput) {
1340
- fontSprite.setOffsetX(imgInput.offsetX);
1341
- fontSprite.setOffsetY(imgInput.offsetY);
1342
- fontSprite.setOriginalWidth(imgInput.originalWidth);
1343
- fontSprite.setOriginalHeight(imgInput.originalHeight);
1344
- }
1345
- fontSprite.setAtlas(atlasNode);
1346
- atlasNode.addSprite(fontSprite);
1347
- }
1348
- }
1349
- if (encoder && options.outputPath) {
1350
- if (options.mkdir) await options.mkdir(options.outputPath);
1351
- for (let p = 0; p < pages.length; p++) {
1352
- const page = pages[p];
1353
- const compositeInputs = [];
1354
- for (const pr of page.outputRects) {
1355
- const input = group.inputs[pr.index];
1356
- if (!input) continue;
1357
- if (pr.width <= 0 || pr.height <= 0 || input.width <= 0 || input.height <= 0) continue;
1358
- try {
1359
- let imgBuffer;
1360
- if (input.trimBuffer) {
1361
- imgBuffer = input.trimBuffer;
1362
- if (imgBuffer.length === 0) continue;
1363
- } else {
1364
- if (!isImageResource$1(input.resource)) {
1365
- logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
1366
- continue;
1367
- }
1368
- imgBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
1369
- }
1370
- if (pr.rotated) imgBuffer = await encoder(imgBuffer).rotate(270).toBuffer();
1371
- compositeInputs.push({
1372
- input: imgBuffer,
1373
- left: pr.x,
1374
- top: pr.y
1375
- });
1376
- } catch {
1377
- logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
1378
- }
1379
- }
1380
- const atlasFileName = resolveAtlasOutputFileName(pkg, p, group.branchName);
1381
- const outputFile = `${options.outputPath}/${atlasFileName}`;
1382
- await encoder({ create: {
1383
- width: page.width,
1384
- height: page.height,
1385
- channels: 4,
1386
- background: {
1387
- r: 0,
1388
- g: 0,
1389
- b: 0,
1390
- alpha: 0
1391
- }
1392
- } }).composite(compositeInputs).png().toFile(outputFile);
1393
- logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
1394
- }
1395
- }
1345
+ totalPageCount += emittedPageCount;
1346
+ branchPageOffsets.set(group.branchOrdinal, pageStart + emittedPageCount);
1347
+ }
1348
+ for (const group of fixedPageGroups) {
1349
+ const emittedPageCount = await emitPagedAtlasGroup(doc, pkg, allResources, group.inputs, {
1350
+ branchName: group.branchName,
1351
+ branchOrdinal: group.branchOrdinal,
1352
+ pageStart: group.pageIndex,
1353
+ forceSinglePage: true,
1354
+ fileNameAt: () => resolveAtlasOutputFileName(pkg, group.pageIndex, group.branchName),
1355
+ options,
1356
+ encoder,
1357
+ logger
1358
+ });
1359
+ totalPageCount += emittedPageCount;
1360
+ }
1361
+ const standalonePageOffsets = new Map(branchPageOffsets);
1362
+ for (const group of fixedPageGroups) {
1363
+ const nextPageIndex = group.pageIndex + 1;
1364
+ if (nextPageIndex > (standalonePageOffsets.get(group.branchOrdinal) ?? 0)) standalonePageOffsets.set(group.branchOrdinal, nextPageIndex);
1365
+ }
1366
+ for (const group of standaloneGroups) {
1367
+ const emittedPageCount = await emitStandaloneAtlasGroup(doc, pkg, group, {
1368
+ atlasIndexStart: standalonePageOffsets.get(group.branchOrdinal) ?? 0,
1369
+ options,
1370
+ encoder,
1371
+ logger
1372
+ });
1373
+ totalPageCount += emittedPageCount;
1374
+ standalonePageOffsets.set(group.branchOrdinal, (standalonePageOffsets.get(group.branchOrdinal) ?? 0) + emittedPageCount);
1396
1375
  }
1397
1376
  if (usedDirectOutput) logger.info(`atlas: Direct output for single image package "${pkg.getName()}".`);
1398
1377
  logger.info(`atlas: Packed ${inputs.length} images into ${totalPageCount} atlas(es) for package "${pkg.getName()}".`);
@@ -1429,6 +1408,171 @@ function buildBranchAtlasGroups(doc, inputs, options) {
1429
1408
  inputs: groups.get(branchName) ?? []
1430
1409
  }));
1431
1410
  }
1411
+ function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageIndexes) {
1412
+ let pageIndex = branchPageOffsets.get(branchOrdinal) ?? 0;
1413
+ while (branchOrdinal === 0 && reservedPageIndexes.has(pageIndex)) pageIndex += 1;
1414
+ return pageIndex;
1415
+ }
1416
+ async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
1417
+ if (inputs.length === 0) return 0;
1418
+ const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
1419
+ if (pages.length === 0) return 0;
1420
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1421
+ const page = pages[pageOffset];
1422
+ const pageIndex = context.pageStart + pageOffset;
1423
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(context.branchOrdinal, pageIndex)}`);
1424
+ atlasNode.setIndex(resolveAtlasIndex(context.branchOrdinal, pageIndex));
1425
+ atlasNode.setFile(context.fileNameAt(pageIndex));
1426
+ atlasNode.setWidth(page.width);
1427
+ atlasNode.setHeight(page.height);
1428
+ pkg.addAtlas(atlasNode);
1429
+ attachSpritesToAtlas(doc, allResources, inputs, page.outputRects, atlasNode);
1430
+ await writeAtlasPageImage(pkg, inputs, page, atlasNode.getFile(), context.encoder, context.options, context.logger);
1431
+ }
1432
+ return pages.length;
1433
+ }
1434
+ async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
1435
+ if (group.inputs.length === 0) return 0;
1436
+ const pages = packAtlasPages(group.inputs, context.options, true, group.sizeMode === "npot" ? {
1437
+ powerOfTwo: false,
1438
+ multipleOfFour: false,
1439
+ square: false
1440
+ } : group.sizeMode === "multipleOf4" ? {
1441
+ powerOfTwo: false,
1442
+ multipleOfFour: true,
1443
+ square: false
1444
+ } : void 0);
1445
+ if (pages.length === 0) return 0;
1446
+ for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
1447
+ const page = pages[pageOffset];
1448
+ const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
1449
+ const atlasFileName = pages.length <= 1 ? baseFileName : insertFileNameSuffix(baseFileName, `_${pageOffset}`);
1450
+ const atlasIndex = context.atlasIndexStart + pageOffset;
1451
+ const atlasNode = doc.createAtlas(`atlas${resolveAtlasIndex(group.branchOrdinal, atlasIndex)}`);
1452
+ atlasNode.setIndex(resolveAtlasIndex(group.branchOrdinal, atlasIndex));
1453
+ atlasNode.setFile(atlasFileName);
1454
+ const standaloneSize = resolveStandaloneAtlasSize(page.width, page.height, group.sizeMode, context.options);
1455
+ atlasNode.setWidth(standaloneSize.width);
1456
+ atlasNode.setHeight(standaloneSize.height);
1457
+ pkg.addAtlas(atlasNode);
1458
+ attachSpritesToAtlas(doc, [], group.inputs, page.outputRects, atlasNode);
1459
+ await writeAtlasPageImage(pkg, group.inputs, {
1460
+ ...page,
1461
+ width: standaloneSize.width,
1462
+ height: standaloneSize.height
1463
+ }, atlasFileName, context.encoder, context.options, context.logger);
1464
+ }
1465
+ return pages.length;
1466
+ }
1467
+ function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
1468
+ const hasDuplicatePadding = inputs.some((input) => {
1469
+ return isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1470
+ });
1471
+ return new MaxRectsPackerCompat({
1472
+ pot: sizeOverrides?.powerOfTwo ?? options.powerOfTwo,
1473
+ mof: sizeOverrides?.multipleOfFour ?? !options.powerOfTwo,
1474
+ padding: options.padding,
1475
+ rotation: options.allowRotation,
1476
+ minWidth: 16,
1477
+ minHeight: 16,
1478
+ maxWidth: options.maxSize,
1479
+ maxHeight: options.maxSize,
1480
+ square: sizeOverrides?.square ?? options.square,
1481
+ fast: options.fast,
1482
+ edgePadding: false,
1483
+ duplicatePadding: hasDuplicatePadding,
1484
+ multiPage: forceSinglePage ? false : options.multiPage,
1485
+ preserveInputOrderOnTie: options.preserveInputOrderOnTie
1486
+ }).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
1487
+ }
1488
+ function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
1489
+ for (const packedRect of outputRects) {
1490
+ const input = inputs[packedRect.index];
1491
+ if (!input) continue;
1492
+ const packedSize = resolvePackedRectSize(input, packedRect.width, packedRect.height, packedRect.rotated);
1493
+ const sprite = doc.createSprite();
1494
+ sprite.setItemId(input.id);
1495
+ sprite.setRectX(packedRect.x);
1496
+ sprite.setRectY(packedRect.y);
1497
+ sprite.setRectWidth(packedSize.width);
1498
+ sprite.setRectHeight(packedSize.height);
1499
+ sprite.setRotated(packedRect.rotated);
1500
+ sprite.setOffsetX(input.offsetX);
1501
+ sprite.setOffsetY(input.offsetY);
1502
+ sprite.setOriginalWidth(input.originalWidth);
1503
+ sprite.setOriginalHeight(input.originalHeight);
1504
+ sprite.setAtlas(atlasNode);
1505
+ atlasNode.addSprite(sprite);
1506
+ }
1507
+ for (const resource of allResources) {
1508
+ if (!isFontResource$1(resource)) continue;
1509
+ const alias = resource.getExtras()?._fontSpriteAlias;
1510
+ if (!alias) continue;
1511
+ const imageSprite = outputRects.find((result) => inputs[result.index]?.id === alias.textureId);
1512
+ if (!imageSprite) continue;
1513
+ const imageInput = inputs[imageSprite.index];
1514
+ const fontSprite = doc.createSprite();
1515
+ fontSprite.setItemId(alias.fontId);
1516
+ fontSprite.setRectX(imageSprite.x);
1517
+ fontSprite.setRectY(imageSprite.y);
1518
+ fontSprite.setRectWidth(imageSprite.width);
1519
+ fontSprite.setRectHeight(imageSprite.height);
1520
+ fontSprite.setRotated(imageSprite.rotated);
1521
+ if (imageInput) {
1522
+ fontSprite.setOffsetX(imageInput.offsetX);
1523
+ fontSprite.setOffsetY(imageInput.offsetY);
1524
+ fontSprite.setOriginalWidth(imageInput.originalWidth);
1525
+ fontSprite.setOriginalHeight(imageInput.originalHeight);
1526
+ }
1527
+ fontSprite.setAtlas(atlasNode);
1528
+ atlasNode.addSprite(fontSprite);
1529
+ }
1530
+ }
1531
+ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, options, logger) {
1532
+ if (!encoder || !options.outputPath) return;
1533
+ if (options.mkdir) await options.mkdir(options.outputPath);
1534
+ const compositeInputs = [];
1535
+ for (const packedRect of page.outputRects) {
1536
+ const input = inputs[packedRect.index];
1537
+ if (!input) continue;
1538
+ if (packedRect.width <= 0 || packedRect.height <= 0 || input.width <= 0 || input.height <= 0) continue;
1539
+ try {
1540
+ let imageBuffer;
1541
+ if (input.trimBuffer) {
1542
+ imageBuffer = input.trimBuffer;
1543
+ if (imageBuffer.length === 0) continue;
1544
+ } else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
1545
+ else {
1546
+ if (!isImageResource$1(input.resource)) {
1547
+ logger.warn(`atlas: Non-image input "${input.id}" is missing inline buffer, skipping compositing.`);
1548
+ continue;
1549
+ }
1550
+ imageBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
1551
+ }
1552
+ if (packedRect.rotated) imageBuffer = await encoder(imageBuffer).rotate(270).toBuffer();
1553
+ compositeInputs.push({
1554
+ input: imageBuffer,
1555
+ left: packedRect.x,
1556
+ top: packedRect.y
1557
+ });
1558
+ } catch {
1559
+ logger.warn(`atlas: Could not read image "${input.id}" for compositing.`);
1560
+ }
1561
+ }
1562
+ const outputFile = `${options.outputPath}/${atlasFileName}`;
1563
+ await encoder({ create: {
1564
+ width: page.width,
1565
+ height: page.height,
1566
+ channels: 4,
1567
+ background: {
1568
+ r: 0,
1569
+ g: 0,
1570
+ b: 0,
1571
+ alpha: 0
1572
+ }
1573
+ } }).composite(compositeInputs).toFile(outputFile);
1574
+ logger.info(`atlas: Generated ${atlasFileName} (${page.width}x${page.height}, ${page.outputRects.length} sprites)`);
1575
+ }
1432
1576
  function inputToCompatRect(input, index) {
1433
1577
  const duplicatePadding = isImageResource$1(input.resource) && input.resource.getDuplicatePadding?.() === true;
1434
1578
  return {
@@ -1543,14 +1687,47 @@ function resolveAtlasOutputFileName(pkg, pageIndex, branchName) {
1543
1687
  const suffix = branchName ? `_${branchName}` : "";
1544
1688
  return `${pkg.getPublishName() || pkg.getName()}_atlas${pageIndex}${suffix}.png`;
1545
1689
  }
1690
+ function resolveStandaloneAtlasOutputFileName(pkg, resource, branchName) {
1691
+ const baseName = `${pkg.getPublishName() || pkg.getName()}_atlas_${getPublishedItemId(resource)}`;
1692
+ const suffix = branchName ? `_${branchName}` : "";
1693
+ if (isImageResource$1(resource)) return `${baseName}${suffix}${extname$1(resolveImageFileName$1(resource)) || ".png"}`;
1694
+ return `${baseName}${suffix}.png`;
1695
+ }
1696
+ function resolveStandaloneAtlasSize(width, height, sizeMode, options) {
1697
+ if (sizeMode === "npot") return {
1698
+ width,
1699
+ height
1700
+ };
1701
+ if (sizeMode === "multipleOf4") return {
1702
+ width: roundUpToMultiple(width, 4),
1703
+ height: roundUpToMultiple(height, 4)
1704
+ };
1705
+ return resolveDirectOutputAtlasSize(width, height, options);
1706
+ }
1546
1707
  function resolveImageFileName$1(resource) {
1547
1708
  const extras = resource.getExtras();
1548
1709
  return resource.getFileName() || extras._fileName || resource.getName();
1549
1710
  }
1711
+ function extname$1(fileName) {
1712
+ const normalized = fileName.replace(/\\/g, "/");
1713
+ const lastSlash = normalized.lastIndexOf("/");
1714
+ const lastDot = normalized.lastIndexOf(".");
1715
+ if (lastDot <= lastSlash) return "";
1716
+ return normalized.slice(lastDot);
1717
+ }
1718
+ function insertFileNameSuffix(fileName, suffix) {
1719
+ const extension = extname$1(fileName);
1720
+ if (!extension) return `${fileName}${suffix}`;
1721
+ return `${fileName.slice(0, -extension.length)}${suffix}${extension}`;
1722
+ }
1550
1723
  function nextPow2(value) {
1551
1724
  if (value <= 1) return 1;
1552
1725
  return 2 ** Math.ceil(Math.log2(value));
1553
1726
  }
1727
+ function roundUpToMultiple(value, base) {
1728
+ if (value <= 0) return 0;
1729
+ return Math.ceil(value / base) * base;
1730
+ }
1554
1731
  function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
1555
1732
  const ordered = [...resources];
1556
1733
  ordered.sort((left, right) => {
@@ -1566,14 +1743,71 @@ function sortResourcesByOrder(resources, orderMap, inputOrderMap) {
1566
1743
  });
1567
1744
  return ordered;
1568
1745
  }
1746
+ function getResourceTextureSetMode(resource) {
1747
+ if (isImageResource$1(resource)) return parseTextureSetMode(resource.getTextureSetMode?.());
1748
+ return parseTextureSetMode(resource.getTextureSetMode?.());
1749
+ }
1750
+ function groupStandaloneInputs(doc, inputs, options) {
1751
+ const autoInputs = [];
1752
+ const fixedInputsByPage = /* @__PURE__ */ new Map();
1753
+ const standaloneGroups = /* @__PURE__ */ new Map();
1754
+ const reservedPageIndexes = /* @__PURE__ */ new Set();
1755
+ const discoveredBranchNames = [...new Set(inputs.map((input) => getInputBranchName(input)).filter((branchName) => !!branchName))];
1756
+ const orderedBranchNames = doc.getRoot().listBranches().filter((branchName) => discoveredBranchNames.includes(branchName));
1757
+ for (const branchName of discoveredBranchNames) if (!orderedBranchNames.includes(branchName)) orderedBranchNames.push(branchName);
1758
+ const branchOrdinalByName = /* @__PURE__ */ new Map();
1759
+ branchOrdinalByName.set("", 0);
1760
+ if (options.separatedAtlasForBranch) {
1761
+ let ordinal = 1;
1762
+ for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, ordinal++);
1763
+ } else for (const branchName of orderedBranchNames) branchOrdinalByName.set(branchName, 0);
1764
+ for (const input of inputs) {
1765
+ const branchName = getInputBranchName(input);
1766
+ const branchOrdinal = branchOrdinalByName.get(branchName) ?? 0;
1767
+ const mode = getResourceTextureSetMode(input.resource);
1768
+ if (mode.kind === "standalone") {
1769
+ const key = `${branchName}\u0000${getPublishedItemId(input.resource)}`;
1770
+ const existing = standaloneGroups.get(key);
1771
+ if (existing) existing.inputs.push(input);
1772
+ else standaloneGroups.set(key, {
1773
+ resource: input.resource,
1774
+ branchName,
1775
+ branchOrdinal,
1776
+ sizeMode: mode.sizeMode,
1777
+ inputs: [input]
1778
+ });
1779
+ continue;
1780
+ }
1781
+ if (mode.kind === "page") {
1782
+ reservedPageIndexes.add(mode.pageIndex);
1783
+ const key = `${branchName}\u0000${mode.pageIndex}`;
1784
+ const existing = fixedInputsByPage.get(key);
1785
+ if (existing) existing.inputs.push(input);
1786
+ else fixedInputsByPage.set(key, {
1787
+ pageIndex: mode.pageIndex,
1788
+ branchName,
1789
+ branchOrdinal,
1790
+ inputs: [input]
1791
+ });
1792
+ continue;
1793
+ }
1794
+ autoInputs.push(input);
1795
+ }
1796
+ return {
1797
+ autoInputs,
1798
+ fixedPageGroups: [...fixedInputsByPage.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || left.pageIndex - right.pageIndex),
1799
+ standaloneGroups: [...standaloneGroups.values()].sort((left, right) => left.branchOrdinal - right.branchOrdinal || getPublishedItemId(left.resource).localeCompare(getPublishedItemId(right.resource))),
1800
+ reservedPageIndexes
1801
+ };
1802
+ }
1569
1803
  /**
1570
1804
  * Trim transparent edges from an image using sharp.
1571
1805
  * Returns the trimmed buffer, dimensions, and offsets.
1572
1806
  * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
1573
1807
  */
1574
- async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
1808
+ async function _trimImage(encoder, input, originalWidth, originalHeight) {
1575
1809
  try {
1576
- const trimResult = await encoder(filePath).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1810
+ const trimResult = await encoder(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1577
1811
  if (!isResolvedBuffer(trimResult)) throw new Error("atlas: encoder raw alpha trim did not return resolved metadata.");
1578
1812
  const { data, info } = trimResult;
1579
1813
  const width = info.width;
@@ -1602,7 +1836,7 @@ async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
1602
1836
  const trimmedWidth = maxX - minX + 1;
1603
1837
  const trimmedHeight = maxY - minY + 1;
1604
1838
  return {
1605
- buffer: await encoder(filePath).extract({
1839
+ buffer: await encoder(input).extract({
1606
1840
  left: minX,
1607
1841
  top: minY,
1608
1842
  width: trimmedWidth,
@@ -1617,7 +1851,7 @@ async function _trimImage(encoder, filePath, originalWidth, originalHeight) {
1617
1851
  };
1618
1852
  } catch {
1619
1853
  return {
1620
- buffer: await encoder(filePath).png().toBuffer(),
1854
+ buffer: await encoder(input).png().toBuffer(),
1621
1855
  width: originalWidth,
1622
1856
  height: originalHeight,
1623
1857
  offsetX: 0,
@@ -1641,7 +1875,10 @@ function _resolveImagePath(resource, pkg, basePath) {
1641
1875
  async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, logger) {
1642
1876
  let origW = resource.getWidth() ?? 0;
1643
1877
  let origH = resource.getHeight() ?? 0;
1878
+ const declaredWidth = origW;
1879
+ const declaredHeight = origH;
1644
1880
  let sourceHasAlpha = false;
1881
+ let rasterizedBuffer;
1645
1882
  if (encoder && options.basePath) {
1646
1883
  const filePath = _resolveImagePath(resource, pkg, options.basePath);
1647
1884
  try {
@@ -1653,6 +1890,10 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
1653
1890
  resource.setHeight(origH);
1654
1891
  }
1655
1892
  sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
1893
+ if (/\.svg$/i.test(resolveImageFileName$1(resource)) && declaredWidth > 0 && declaredHeight > 0) {
1894
+ rasterizedBuffer = await encoder(filePath).resize(declaredWidth, declaredHeight, { fit: "fill" }).png().toBuffer();
1895
+ sourceHasAlpha = true;
1896
+ }
1656
1897
  } catch {
1657
1898
  if (origW === 0 || origH === 0) {
1658
1899
  logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
@@ -1666,7 +1907,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
1666
1907
  if (doTrim && sourceHasAlpha && options.basePath && encoder) {
1667
1908
  const filePath = _resolveImagePath(resource, pkg, options.basePath);
1668
1909
  try {
1669
- const trimResult = await _trimImage(encoder, filePath, origW, origH);
1910
+ const trimResult = await _trimImage(encoder, rasterizedBuffer ?? filePath, origW, origH);
1670
1911
  packW = trimResult.width;
1671
1912
  packH = trimResult.height;
1672
1913
  offX = trimResult.offsetX;
@@ -1686,6 +1927,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
1686
1927
  offsetY: offY,
1687
1928
  resource,
1688
1929
  trimBuffer: trimBuf,
1930
+ rasterizedBuffer,
1689
1931
  sourceKind: "image"
1690
1932
  });
1691
1933
  }
@@ -2118,6 +2360,62 @@ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
2118
2360
  }
2119
2361
  `;
2120
2362
  //#endregion
2363
+ //#region src/plugins/loader.ts
2364
+ const importNative = new Function("id", "return import(id)");
2365
+ async function loadPlugins(doc, pluginsDir) {
2366
+ if (!pluginsDir) return [];
2367
+ const fs = await importNative("node:fs/promises");
2368
+ const path = await importNative("node:path");
2369
+ let entries;
2370
+ try {
2371
+ entries = await fs.readdir(pluginsDir, { withFileTypes: true });
2372
+ } catch {
2373
+ return [];
2374
+ }
2375
+ const plugins = [];
2376
+ for (const entry of entries) {
2377
+ if (!entry.isDirectory()) continue;
2378
+ const pluginDir = path.join(pluginsDir, entry.name);
2379
+ try {
2380
+ const manifest = await readPluginManifest(fs, path, pluginDir);
2381
+ if (!manifest) continue;
2382
+ const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
2383
+ plugins.push({
2384
+ name: manifest.name,
2385
+ plugin
2386
+ });
2387
+ } catch (error) {
2388
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
2389
+ }
2390
+ }
2391
+ return plugins;
2392
+ }
2393
+ function formatPluginError(error) {
2394
+ return error instanceof Error ? error.message : String(error);
2395
+ }
2396
+ async function readPluginManifest(fs, path, pluginDir) {
2397
+ const manifestPath = path.join(pluginDir, "package.json");
2398
+ const content = await fs.readFile(manifestPath, "utf-8");
2399
+ const manifest = JSON.parse(content);
2400
+ if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
2401
+ if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
2402
+ return manifest;
2403
+ }
2404
+ function resolvePluginMain(path, pluginDir, manifest) {
2405
+ const mainPath = path.resolve(pluginDir, manifest.main);
2406
+ const relative = path.relative(pluginDir, mainPath);
2407
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
2408
+ return mainPath;
2409
+ }
2410
+ async function loadPlugin(mainPath) {
2411
+ const mod = await (0, jiti.createJiti)(require("url").pathToFileURL(__filename).href).import(mainPath);
2412
+ const defaultExport = mod.default;
2413
+ return isObject(defaultExport) ? defaultExport : mod;
2414
+ }
2415
+ function isObject(value) {
2416
+ return value !== null && typeof value === "object";
2417
+ }
2418
+ //#endregion
2121
2419
  //#region src/codegen.ts
2122
2420
  const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
2123
2421
  const DEFAULT_CLASS_NAME_PREFIX = "UI_";
@@ -2153,6 +2451,18 @@ async function publishCodeGeneration(doc, options) {
2153
2451
  const logger = doc.getLogger();
2154
2452
  const settings = resolveCodeGenerationSettings(doc);
2155
2453
  if (!settings.allowGenCode) return;
2454
+ const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === "function") ?? [];
2455
+ if (plugins.length > 0) {
2456
+ let handled = false;
2457
+ for (const plugin of plugins) try {
2458
+ await plugin.plugin.genCode(doc, settings, options);
2459
+ handled = true;
2460
+ logger.info(`publish: Generated code using plugin "${plugin.name}"`);
2461
+ } catch (error) {
2462
+ logger.warn(`publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`);
2463
+ }
2464
+ if (handled) return;
2465
+ }
2156
2466
  for (const pkg of options.packages) {
2157
2467
  if (!pkg.getGenCode()) continue;
2158
2468
  const plan = resolvePackageCodegenPlan(pkg, settings, options);
@@ -2251,9 +2561,9 @@ async function cleanupGeneratedFiles(directory, fs, extension = ".cs") {
2251
2561
  }
2252
2562
  }
2253
2563
  function buildCodegenClasses(doc, pkg, plan) {
2254
- const exportedComponents = pkg.listComponents().filter((component) => component.getExported()).sort((left, right) => left.getId().localeCompare(right.getId()));
2564
+ const codegenComponents = pkg.listComponents().sort((left, right) => left.getId().localeCompare(right.getId()));
2255
2565
  const generatedById = /* @__PURE__ */ new Map();
2256
- for (const component of exportedComponents) {
2566
+ for (const component of codegenComponents) {
2257
2567
  const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || "Component"}`;
2258
2568
  generatedById.set(component.getId(), {
2259
2569
  classId: component.getId(),
@@ -2266,7 +2576,13 @@ function buildCodegenClasses(doc, pkg, plan) {
2266
2576
  members: []
2267
2577
  });
2268
2578
  }
2269
- for (const component of exportedComponents) {
2579
+ for (const component of codegenComponents) {
2580
+ const classInfo = generatedById.get(component.getId());
2581
+ if (!classInfo) continue;
2582
+ classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
2583
+ }
2584
+ for (const [componentId, classInfo] of generatedById) if (classInfo.members.every((member) => member.ignored)) generatedById.delete(componentId);
2585
+ for (const component of codegenComponents) {
2270
2586
  const classInfo = generatedById.get(component.getId());
2271
2587
  if (!classInfo) continue;
2272
2588
  classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
@@ -2280,7 +2596,12 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
2280
2596
  let childIndex = 0;
2281
2597
  let transitionIndex = 0;
2282
2598
  for (const controller of component.listControllers()) members.push(createMember(ownerType, "controller", "Controller", controller.getName(), controllerIndex++, plan));
2283
- for (const child of component.listChildren()) members.push(createMember(ownerType, "child", resolveChildType(doc, pkg, child, generatedById), child.getName(), childIndex++, plan));
2599
+ for (const child of component.listChildren()) {
2600
+ if (!isRuntimeChild(child)) continue;
2601
+ const index = childIndex++;
2602
+ const resolvedChild = resolveChildType(doc, pkg, child, generatedById);
2603
+ members.push(createMember(ownerType, "child", resolvedChild.type, child.getName(), index, plan, resolvedChild.referencedComponent));
2604
+ }
2284
2605
  for (const transition of component.listTransitions()) members.push(createMember(ownerType, "transition", "Transition", transition.getName(), transitionIndex++, plan));
2285
2606
  const usedNames = /* @__PURE__ */ new Map();
2286
2607
  for (const member of members) {
@@ -2292,7 +2613,10 @@ function buildCodegenMembers(doc, pkg, component, plan, generatedById) {
2292
2613
  }
2293
2614
  return members;
2294
2615
  }
2295
- function createMember(ownerType, kind, type, originalName, index, plan) {
2616
+ function isRuntimeChild(child) {
2617
+ return child.propertyType !== "GGroup" || child.getAdvanced?.() === true;
2618
+ }
2619
+ function createMember(ownerType, kind, type, originalName, index, plan, referencedComponent) {
2296
2620
  const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
2297
2621
  return {
2298
2622
  index,
@@ -2300,30 +2624,41 @@ function createMember(ownerType, kind, type, originalName, index, plan) {
2300
2624
  name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
2301
2625
  originalName,
2302
2626
  type,
2303
- ignored
2627
+ ignored,
2628
+ referencedComponent
2304
2629
  };
2305
2630
  }
2306
2631
  function resolveChildType(doc, pkg, child, generatedById) {
2307
2632
  const src = child.getSrc?.();
2308
2633
  if (src) {
2309
- const localResource = resolveChildSourceComponent(doc, pkg, src);
2310
- if (localResource) return generatedById.get(localResource.getId())?.encodedClassName ?? resolveComponentBaseType(localResource);
2634
+ let referencedComponent = null;
2635
+ if (src.startsWith("ui://")) {
2636
+ const rest = src.slice(5);
2637
+ const pkgId = rest.slice(0, 8);
2638
+ const resourceId = rest.slice(8);
2639
+ const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
2640
+ const targetResource = targetPackage?.getResourceById(resourceId);
2641
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2642
+ component: targetResource,
2643
+ package: targetPackage
2644
+ };
2645
+ } else {
2646
+ const packageId = child.getPackageId?.();
2647
+ const targetPackage = packageId ? doc.getRoot().listPackages().find((candidate) => candidate.getId() === packageId) : pkg;
2648
+ const targetResource = targetPackage?.getResourceById(src);
2649
+ if (targetPackage && targetResource?.propertyType === "Component") referencedComponent = {
2650
+ component: targetResource,
2651
+ package: targetPackage
2652
+ };
2653
+ }
2654
+ if (referencedComponent) return {
2655
+ type: (referencedComponent.package === pkg ? generatedById.get(referencedComponent.component.getId()) : void 0)?.encodedClassName ?? resolveComponentBaseType(referencedComponent.component),
2656
+ referencedComponent
2657
+ };
2311
2658
  }
2312
2659
  const instanceExtType = child.getInstanceExtType?.();
2313
- if (instanceExtType) return `G${instanceExtType}`;
2314
- return child.propertyType;
2315
- }
2316
- function resolveChildSourceComponent(doc, pkg, src) {
2317
- if (!src) return null;
2318
- if (src.startsWith("ui://")) {
2319
- const rest = src.slice(5);
2320
- const pkgId = rest.slice(0, 8);
2321
- const resourceId = rest.slice(8);
2322
- const targetResource = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId)?.getResourceById(resourceId);
2323
- return targetResource?.propertyType === "Component" ? targetResource : null;
2324
- }
2325
- const localResource = pkg.getResourceById(src);
2326
- return localResource?.propertyType === "Component" ? localResource : null;
2660
+ if (instanceExtType) return { type: `G${instanceExtType}` };
2661
+ return { type: child.propertyType };
2327
2662
  }
2328
2663
  function resolveComponentBaseType(component) {
2329
2664
  const extensionType = component.getExtensionType();
@@ -2394,21 +2729,21 @@ function renderFguiTypescriptMemberAssignment(member, getMemberByName, variant)
2394
2729
  return getMemberByName ? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));` : `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
2395
2730
  }
2396
2731
  function resolveCodePath(codePath, basePath, fs) {
2397
- if (isAbsolutePath(codePath)) return trimTrailingSlashes$1(codePath);
2732
+ if (isAbsolutePath(codePath)) return trimTrailingSlashes$2(codePath);
2398
2733
  const projectBasePath = resolveProjectBasePath(basePath);
2399
- return projectBasePath ? trimTrailingSlashes$1(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$1(codePath);
2734
+ return projectBasePath ? trimTrailingSlashes$2(fs.join(projectBasePath, codePath)) : trimTrailingSlashes$2(codePath);
2400
2735
  }
2401
2736
  function resolveProjectBasePath(basePath) {
2402
2737
  if (!basePath) return "";
2403
- const normalized = trimTrailingSlashes$1(basePath);
2738
+ const normalized = trimTrailingSlashes$2(basePath);
2404
2739
  const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
2405
2740
  if (assetsMatch?.[1]) return assetsMatch[1];
2406
2741
  return dirname$2(normalized);
2407
2742
  }
2408
2743
  function dirname$2(filePath) {
2409
- return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
2744
+ return trimTrailingSlashes$2(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
2410
2745
  }
2411
- function trimTrailingSlashes$1(value) {
2746
+ function trimTrailingSlashes$2(value) {
2412
2747
  return value.replace(/[/\\]+$/, "");
2413
2748
  }
2414
2749
  function isAbsolutePath(value) {
@@ -2417,9 +2752,15 @@ function isAbsolutePath(value) {
2417
2752
  function isDefaultMemberName(ownerType, kind, name) {
2418
2753
  if (kind === "controller") return (ownerType === "GButton" || ownerType === "GComboBox") && name === "button";
2419
2754
  if (kind === "transition") return false;
2420
- if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") return name === "title" || name === "icon";
2421
- if (ownerType === "GProgressBar") return name === "bar" || name === "bar_v" || name === "title" || name === "ani";
2422
- if (ownerType === "GSlider") return name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani";
2755
+ if (ownerType === "GButton" || ownerType === "GLabel" || ownerType === "GComboBox") {
2756
+ if (name === "title" || name === "icon") return true;
2757
+ }
2758
+ if (ownerType === "GProgressBar") {
2759
+ if (name === "bar" || name === "bar_v" || name === "title" || name === "ani") return true;
2760
+ }
2761
+ if (ownerType === "GSlider") {
2762
+ if (name === "bar" || name === "bar_v" || name === "grip" || name === "title" || name === "ani") return true;
2763
+ }
2423
2764
  return /^n\d+(?:_.*)?$/i.test(name);
2424
2765
  }
2425
2766
  function applyMemberNamePrefix(name, prefix) {
@@ -2708,11 +3049,11 @@ function inferPackageName(fileName) {
2708
3049
  if (/\.fui$/i.test(fileName)) return fileName.replace(/\.fui$/i, "");
2709
3050
  return fileName.replace(/\.bin$/i, "");
2710
3051
  }
2711
- function trimTrailingSlashes(value) {
3052
+ function trimTrailingSlashes$1(value) {
2712
3053
  return value.replace(/[/\\]+$/, "");
2713
3054
  }
2714
3055
  function normalizeComparablePath(value) {
2715
- const normalized = trimTrailingSlashes(value).replace(/\\/g, "/");
3056
+ const normalized = trimTrailingSlashes$1(value).replace(/\\/g, "/");
2716
3057
  const driveMatch = normalized.match(/^([a-z]:)(?:\/(.*))?$/i);
2717
3058
  const drivePrefix = driveMatch?.[1].toLowerCase() ?? "";
2718
3059
  const remainder = driveMatch ? driveMatch[2] ?? "" : normalized;
@@ -2732,14 +3073,14 @@ function normalizeComparablePath(value) {
2732
3073
  return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
2733
3074
  }
2734
3075
  function dirname$1(filePath) {
2735
- return trimTrailingSlashes(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
3076
+ return trimTrailingSlashes$1(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
2736
3077
  }
2737
3078
  function basename(filePath) {
2738
- return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
3079
+ return trimTrailingSlashes$1(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
2739
3080
  }
2740
3081
  function resolveOutputProjectPath(output, fs) {
2741
3082
  if (/\.fairy$/i.test(output)) return output;
2742
- const normalizedOutput = trimTrailingSlashes(output);
3083
+ const normalizedOutput = trimTrailingSlashes$1(output);
2743
3084
  const projectName = basename(normalizedOutput) || "Restored";
2744
3085
  return fs.join(normalizedOutput, `${projectName}.fairy`);
2745
3086
  }
@@ -2785,7 +3126,7 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
2785
3126
  await fs.mkdir(outputDir);
2786
3127
  }
2787
3128
  async function restore(options) {
2788
- const sourceDir = trimTrailingSlashes(options.inputDir);
3129
+ const sourceDir = trimTrailingSlashes$1(options.inputDir);
2789
3130
  const outputIsProjectFile = /\.fairy$/i.test(options.output);
2790
3131
  const outputProjectPath = resolveOutputProjectPath(options.output, options.fs);
2791
3132
  await prepareRestoreOutputDir(sourceDir, dirname$1(outputProjectPath) || ".", outputProjectPath, options.fs, options.force === true, outputIsProjectFile);
@@ -2927,16 +3268,23 @@ var RestoreWorkflow = class {
2927
3268
  async _ensureLooseImageResource(doc, pkg, owner, sourceDir, fileName) {
2928
3269
  const resources = pkg.listResources();
2929
3270
  const existing = this._findResourceByFile(resources, owner, "ImageResource", fileName);
2930
- if (existing) return existing;
2931
3271
  const sourcePath = await this._resolveLooseSourceFile(pkg, sourceDir, fileName);
2932
- if (!sourcePath) return null;
3272
+ if (!sourcePath) return existing ?? null;
3273
+ if (existing) {
3274
+ existing.setExtras?.({
3275
+ ...existing.getExtras?.() ?? {},
3276
+ _publishedFile: fileBaseName(sourcePath),
3277
+ _restoreAsLooseImage: true
3278
+ });
3279
+ return existing;
3280
+ }
2933
3281
  const resource = doc.createImageResource(stripExtension(fileName));
2934
3282
  resource.setId((0, _openfairygui_core.generateId)()).setPath(owner.getPath?.() ?? "/").setBranch(owner.getBranch?.() ?? "").setBranchItemIds(owner.getBranchItemIds?.() ?? []).setExported(false).setFileName(fileName);
2935
3283
  resource.setExtras?.({
2936
3284
  ...resource.getExtras?.() ?? {},
2937
3285
  _publishedFile: fileBaseName(sourcePath),
2938
3286
  _suppressPackageSize: true,
2939
- _syntheticLooseImage: true
3287
+ _restoreAsLooseImage: true
2940
3288
  });
2941
3289
  pkg.addResource(resource);
2942
3290
  return resource;
@@ -3110,13 +3458,13 @@ var RestoreWorkflow = class {
3110
3458
  }
3111
3459
  async _copyLooseResources(pkg, options, warnings) {
3112
3460
  for (const resource of pkg.listResources()) {
3113
- const syntheticLooseImage = resource.getExtras?.()?._syntheticLooseImage === true;
3461
+ const restoreAsLooseImage = resource.getExtras?.()?._restoreAsLooseImage === true;
3114
3462
  if (![
3115
3463
  "SoundResource",
3116
3464
  "MiscResource",
3117
3465
  "SpineResource",
3118
3466
  "DragonBonesResource"
3119
- ].includes(resource.propertyType) && !syntheticLooseImage) continue;
3467
+ ].includes(resource.propertyType) && !restoreAsLooseImage) continue;
3120
3468
  const fileName = resourceFileName(resource);
3121
3469
  if (!fileName) continue;
3122
3470
  const sourcePath = await this._resolveSourceFile(options.sourceDir, this._sourceFileCandidates(pkg, resourcePublishedFileName(resource), fileName));
@@ -3286,6 +3634,26 @@ var RestoreWorkflow = class {
3286
3634
  };
3287
3635
  //#endregion
3288
3636
  //#region src/publish.ts
3637
+ async function runPublishPluginHook(plugins, hook, doc, options) {
3638
+ const logger = doc.getLogger();
3639
+ for (const plugin of plugins) {
3640
+ const fn = plugin.plugin[hook];
3641
+ if (typeof fn !== "function") continue;
3642
+ try {
3643
+ await fn(doc, options);
3644
+ } catch (error) {
3645
+ logger.warn(`publish: Plugin "${plugin.name}" ${hook} failed: ${formatPluginError(error)}`);
3646
+ }
3647
+ }
3648
+ }
3649
+ function resolvePublishPluginsDir(doc, options) {
3650
+ const fs = options.fs;
3651
+ const projectDir = doc.getProjectDir?.() ?? "";
3652
+ if (projectDir) return fs?.join ? fs.join(projectDir, "plugins") : `${projectDir.replace(/[/\\]+$/, "")}/plugins`;
3653
+ const projectBasePath = resolveProjectBasePath(options.basePath);
3654
+ if (!projectBasePath) return "";
3655
+ return fs?.join ? fs.join(projectBasePath, "plugins") : `${projectBasePath.replace(/[/\\]+$/, "")}/plugins`;
3656
+ }
3289
3657
  const UNITY_PROJECT_TYPE = _openfairygui_core.ProjectType.Unity;
3290
3658
  const COCOS_CREATOR_PROJECT_TYPE = _openfairygui_core.ProjectType.CocosCreator;
3291
3659
  function resolveDefaultPublishFileExtension(projectType, publishSettings) {
@@ -3335,6 +3703,19 @@ function resolvePublishOptions(doc, overrides = {}) {
3335
3703
  atlas: atlasOptions
3336
3704
  };
3337
3705
  }
3706
+ function trimTrailingSlashes(value) {
3707
+ return value.replace(/[/\\]+$/, "");
3708
+ }
3709
+ function isAbsolutePathLike(value) {
3710
+ return /^(?:[a-zA-Z]:[/\\]|[/\\]{1,2})/u.test(value);
3711
+ }
3712
+ function joinPathSegments(left, right) {
3713
+ const normalizedLeft = trimTrailingSlashes(left);
3714
+ const normalizedRight = right.replace(/^[/\\]+/, "");
3715
+ if (!normalizedLeft) return normalizedRight;
3716
+ if (!normalizedRight) return normalizedLeft;
3717
+ return `${normalizedLeft}${normalizedLeft.includes("\\") ? "\\" : "/"}${normalizedRight}`;
3718
+ }
3338
3719
  function dirname(filePath) {
3339
3720
  return filePath.replace(/[/\\]+$/, "").match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
3340
3721
  }
@@ -3365,6 +3746,9 @@ function isImageResource(resource) {
3365
3746
  function isMovieClipResource(resource) {
3366
3747
  return resource.propertyType === "MovieClipResource";
3367
3748
  }
3749
+ function isHighResolutionResource(resource) {
3750
+ return isImageResource(resource) || isMovieClipResource(resource);
3751
+ }
3368
3752
  function isMiscResource(resource) {
3369
3753
  return resource.propertyType === "MiscResource";
3370
3754
  }
@@ -3447,13 +3831,14 @@ function extname(fileName) {
3447
3831
  if (lastDot <= lastSlash) return "";
3448
3832
  return normalized.slice(lastDot);
3449
3833
  }
3450
- function resolvePublishedMiscFileName(resource) {
3834
+ function resolvePublishedMiscFileName(resource, projectType) {
3451
3835
  const file = resource.getFile();
3836
+ if (projectType !== UNITY_PROJECT_TYPE) return file;
3452
3837
  if (file.toLowerCase().endsWith(".atlas")) return `${file}.txt`;
3453
3838
  return file;
3454
3839
  }
3455
- function resolvePublishedSkeletonFileName(resource) {
3456
- if (isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
3840
+ function resolvePublishedSkeletonFileName(resource, projectType) {
3841
+ if (projectType === UNITY_PROJECT_TYPE && isSpineResource(resource) && resource.getFile().toLowerCase().endsWith(".skel")) return `${resource.getFile()}.bytes`;
3457
3842
  return resource.getFile();
3458
3843
  }
3459
3844
  function setPublishedFileExtra(resource, fileName) {
@@ -3485,6 +3870,70 @@ function getBranchName(resource) {
3485
3870
  function buildBranchResourceKey(resource) {
3486
3871
  return `${resource.propertyType}|${resource.getPath() ?? ""}|${resource.getName() ?? ""}`;
3487
3872
  }
3873
+ const HIGH_RESOLUTION_LEVELS = [
3874
+ {
3875
+ scale: 2,
3876
+ bit: 1,
3877
+ slot: 0
3878
+ },
3879
+ {
3880
+ scale: 3,
3881
+ bit: 2,
3882
+ slot: 1
3883
+ },
3884
+ {
3885
+ scale: 4,
3886
+ bit: 4,
3887
+ slot: 2
3888
+ }
3889
+ ];
3890
+ function buildHighResolutionResourceKey(resource, name = resource.getName()) {
3891
+ return `${resource.propertyType}|${resource.getBranch?.() ?? ""}|${resource.getPath() ?? ""}|${name}`;
3892
+ }
3893
+ function isHighResolutionVariantName(name) {
3894
+ return /@(?:2|3|4)x(?:\.[^./\\]+)?$/iu.test(name);
3895
+ }
3896
+ function appendHighResolutionScaleToName(name, scale) {
3897
+ const extensionIndex = name.lastIndexOf(".");
3898
+ if (extensionIndex > 0) return `${name.slice(0, extensionIndex)}@${scale}x${name.slice(extensionIndex)}`;
3899
+ return `${name}@${scale}x`;
3900
+ }
3901
+ function trimTrailingMissingHighResolutionIds(ids) {
3902
+ while (ids.length > 0 && !ids[ids.length - 1]) ids.pop();
3903
+ return ids;
3904
+ }
3905
+ function collectHighResolutionItemIds(resources, publishedResourceIds, includeHighResolution) {
3906
+ const result = /* @__PURE__ */ new Map();
3907
+ if (includeHighResolution <= 0) return result;
3908
+ const highResolutionResourceByKey = /* @__PURE__ */ new Map();
3909
+ for (const resource of resources) {
3910
+ if (!isHighResolutionResource(resource)) continue;
3911
+ highResolutionResourceByKey.set(buildHighResolutionResourceKey(resource), resource);
3912
+ }
3913
+ for (const resource of resources) {
3914
+ if (!isHighResolutionResource(resource)) continue;
3915
+ if (!publishedResourceIds.has(resource.getId())) continue;
3916
+ if (isHighResolutionVariantName(resource.getName())) continue;
3917
+ const ids = [];
3918
+ for (const level of HIGH_RESOLUTION_LEVELS) {
3919
+ if ((includeHighResolution & level.bit) === 0) {
3920
+ ids[level.slot] = null;
3921
+ continue;
3922
+ }
3923
+ const highResolutionResource = highResolutionResourceByKey.get(buildHighResolutionResourceKey(resource, appendHighResolutionScaleToName(resource.getName(), level.scale)));
3924
+ if (!highResolutionResource) {
3925
+ ids[level.slot] = null;
3926
+ continue;
3927
+ }
3928
+ const highResolutionId = highResolutionResource.getId();
3929
+ publishedResourceIds.add(highResolutionId);
3930
+ ids[level.slot] = highResolutionId;
3931
+ }
3932
+ trimTrailingMissingHighResolutionIds(ids);
3933
+ if (ids.length > 0) result.set(resource.getId(), ids);
3934
+ }
3935
+ return result;
3936
+ }
3488
3937
  function collectPackagePublishContext(pkg, options) {
3489
3938
  const pkgId = pkg.getId();
3490
3939
  const resources = pkg.listResources();
@@ -3492,6 +3941,24 @@ function collectPackagePublishContext(pkg, options) {
3492
3941
  const referencedIds = /* @__PURE__ */ new Set();
3493
3942
  const pixelHitTestImageIds = /* @__PURE__ */ new Set();
3494
3943
  const spriteItemIds = /* @__PURE__ */ new Set();
3944
+ const collectExportedResourceIds = (sourceResources, sourcePublishedResourceIds) => {
3945
+ const exportedResourceIds = new Set(sourcePublishedResourceIds);
3946
+ const resourcesById = new Map(sourceResources.map((resource) => [resource.getId(), resource]));
3947
+ let changed = true;
3948
+ while (changed) {
3949
+ changed = false;
3950
+ for (const resourceId of [...exportedResourceIds]) {
3951
+ const resource = resourcesById.get(resourceId);
3952
+ if (!resource || !isSkeletonResource(resource)) continue;
3953
+ for (const requiredId of resource.getRequireIds()) {
3954
+ if (!requiredId || exportedResourceIds.has(requiredId)) continue;
3955
+ exportedResourceIds.add(requiredId);
3956
+ changed = true;
3957
+ }
3958
+ }
3959
+ }
3960
+ return exportedResourceIds;
3961
+ };
3495
3962
  for (const atlas of pkg.listAtlases()) for (const sprite of atlas.listSprites()) spriteItemIds.add(sprite.getItemId());
3496
3963
  for (const resource of resources) {
3497
3964
  if (!isComponentResource(resource)) continue;
@@ -3518,6 +3985,7 @@ function collectPackagePublishContext(pkg, options) {
3518
3985
  child.getSelectedIcon?.(),
3519
3986
  child.getDropdown?.(),
3520
3987
  child.getSound?.(),
3988
+ child.getInstanceSound?.(),
3521
3989
  child.getInstanceIcon?.(),
3522
3990
  child.getInstanceSelectedIcon?.(),
3523
3991
  child.getVtScrollBarRes?.(),
@@ -3575,19 +4043,8 @@ function collectPackagePublishContext(pkg, options) {
3575
4043
  }
3576
4044
  if (resource.getExported() || referencedIds.has(resourceId)) publishedResourceIds.add(resourceId);
3577
4045
  }
3578
- let changed = true;
3579
- while (changed) {
3580
- changed = false;
3581
- for (const resource of resources) {
3582
- if (!isSkeletonResource(resource)) continue;
3583
- if (!publishedResourceIds.has(resource.getId())) continue;
3584
- for (const requiredId of resource.getRequireIds()) {
3585
- if (!requiredId || publishedResourceIds.has(requiredId)) continue;
3586
- publishedResourceIds.add(requiredId);
3587
- changed = true;
3588
- }
3589
- }
3590
- }
4046
+ for (const resourceId of collectExportedResourceIds(resources, publishedResourceIds)) publishedResourceIds.add(resourceId);
4047
+ const highResolutionItemIds = collectHighResolutionItemIds(resources, publishedResourceIds, options.includeHighResolution);
3591
4048
  if (!options.includeBranches) {
3592
4049
  const mainByKey = /* @__PURE__ */ new Map();
3593
4050
  const activeBranchByKey = /* @__PURE__ */ new Map();
@@ -3635,7 +4092,9 @@ function collectPackagePublishContext(pkg, options) {
3635
4092
  return {
3636
4093
  referencedIds,
3637
4094
  publishedResourceIds,
4095
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
3638
4096
  pixelHitTestImageIds,
4097
+ highResolutionItemIds,
3639
4098
  effectiveResourceIds,
3640
4099
  includeBranches: false
3641
4100
  };
@@ -3643,7 +4102,9 @@ function collectPackagePublishContext(pkg, options) {
3643
4102
  return {
3644
4103
  referencedIds,
3645
4104
  publishedResourceIds,
4105
+ exportedResourceIds: collectExportedResourceIds(resources, publishedResourceIds),
3646
4106
  pixelHitTestImageIds,
4107
+ highResolutionItemIds,
3647
4108
  effectiveResourceIds: new Map([...publishedResourceIds].map((resourceId) => [resourceId, resourceId])),
3648
4109
  includeBranches: true
3649
4110
  };
@@ -3692,28 +4153,36 @@ async function applyPixelHitTests(pkg, imageIds, basePath, encoder) {
3692
4153
  }
3693
4154
  }
3694
4155
  async function annotatePackagePublishArtifacts(pkg, basePath, encoder, options) {
3695
- const { publishedResourceIds, pixelHitTestImageIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
3696
- for (const resource of pkg.listResources()) setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
4156
+ const { publishedResourceIds, exportedResourceIds, pixelHitTestImageIds, highResolutionItemIds, effectiveResourceIds, includeBranches } = collectPackagePublishContext(pkg, options);
4157
+ for (const resource of pkg.listResources()) {
4158
+ setPublishedIdExtra(resource, effectiveResourceIds.get(resource.getId()) ?? null);
4159
+ if (isHighResolutionResource(resource)) resource.setHighResolutionItemIds(highResolutionItemIds.get(resource.getId()) ?? []);
4160
+ }
3697
4161
  await applyPixelHitTests(pkg, pixelHitTestImageIds, basePath, encoder);
3698
4162
  const extras = pkg.getExtras() ?? {};
3699
4163
  pkg.setExtras({
3700
4164
  ...extras,
3701
4165
  publishedResourceIds: [...publishedResourceIds].sort((a, b) => a.localeCompare(b)),
4166
+ exportedResourceIds: [...exportedResourceIds].sort((a, b) => a.localeCompare(b)),
3702
4167
  publishedIncludeBranches: includeBranches,
3703
4168
  publishedEffectiveResourceIds: Object.fromEntries(effectiveResourceIds)
3704
4169
  });
3705
4170
  for (const resource of pkg.listResources()) {
3706
4171
  if (isMiscResource(resource)) {
3707
- setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource));
4172
+ setPublishedFileExtra(resource, resolvePublishedMiscFileName(resource, options.projectType));
3708
4173
  continue;
3709
4174
  }
3710
- if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource));
4175
+ if (isSkeletonResource(resource)) setPublishedFileExtra(resource, resolvePublishedSkeletonFileName(resource, options.projectType));
3711
4176
  }
3712
4177
  }
3713
4178
  function getAnnotatedPublishedResourceIds(pkg) {
3714
4179
  const extras = pkg.getExtras() ?? {};
3715
4180
  return new Set(extras.publishedResourceIds ?? []);
3716
4181
  }
4182
+ function getAnnotatedExportedResourceIds(pkg) {
4183
+ const extras = pkg.getExtras() ?? {};
4184
+ return new Set(extras.exportedResourceIds ?? []);
4185
+ }
3717
4186
  function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
3718
4187
  const imageIds = /* @__PURE__ */ new Set();
3719
4188
  const resourcesById = new Map(pkg.listResources().map((resource) => [resource.getId(), resource]));
@@ -3752,18 +4221,18 @@ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, lo
3752
4221
  }
3753
4222
  }
3754
4223
  async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw, logger) {
3755
- const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
3756
- const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds);
3757
- if (publishedResourceIds.size === 0) return;
4224
+ const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
4225
+ const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
4226
+ if (exportedResourceIds.size === 0) return;
3758
4227
  if (!basePath || !readFileRaw) {
3759
4228
  if (pkg.listResources().some((resource) => {
3760
- return (isMiscResource(resource) || isSkeletonResource(resource)) && publishedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
4229
+ return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
3761
4230
  })) logger.warn(`publish: External resources in package "${pkg.getName()}" were not exported because basePath/readFileRaw is unavailable.`);
3762
4231
  return;
3763
4232
  }
3764
4233
  for (const resource of pkg.listResources()) {
3765
4234
  const resourceId = resource.getId();
3766
- const isSkeletonExternal = publishedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
4235
+ const isSkeletonExternal = exportedResourceIds.has(resourceId) && (isMiscResource(resource) || isSkeletonResource(resource));
3767
4236
  const isSkeletonImageDependency = skeletonDependencyImageIds.has(resourceId) && isImageResource(resource);
3768
4237
  if (!isSkeletonExternal && !isSkeletonImageDependency) continue;
3769
4238
  let sourcePath;
@@ -3809,16 +4278,102 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
3809
4278
  */
3810
4279
  function publish(options) {
3811
4280
  return createTransform("publish", async (doc) => {
4281
+ const resolveConfiguredOutputPath = (value, projectBasePath) => {
4282
+ const trimmed = value?.trim();
4283
+ if (!trimmed) return void 0;
4284
+ if (isAbsolutePathLike(trimmed) || !projectBasePath) return trimTrailingSlashes(trimmed);
4285
+ return trimTrailingSlashes(options.fs ? options.fs.join(projectBasePath, trimmed) : joinPathSegments(projectBasePath, trimmed));
4286
+ };
4287
+ const resolveProjectPublishConfig = () => {
4288
+ const publishSettings = (doc.getRoot().getSettings?.() ?? {}).publish ?? {};
4289
+ const resolved = resolvePublishOptions(doc, {
4290
+ compressed: options.compressed,
4291
+ fileExtension: options.fileExtension,
4292
+ packages: options.packages,
4293
+ atlas: options.atlas
4294
+ });
4295
+ const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
4296
+ return {
4297
+ ...resolved,
4298
+ projectType: doc.getRoot().getProjectType(),
4299
+ includeBranches,
4300
+ activeBranch: includeBranches ? "" : options.branch ?? "",
4301
+ includeHighResolution: publishSettings.includeHighResolution ?? 0,
4302
+ separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
4303
+ globalOutputPath: publishSettings.path?.trim() ?? "",
4304
+ globalBranchOutputPath: publishSettings.branchPath?.trim() ?? ""
4305
+ };
4306
+ };
4307
+ const resolvePackagePublishPlan = (pkg, config, projectBasePath) => {
4308
+ let outputDir;
4309
+ if (options.output) outputDir = trimTrailingSlashes(options.output);
4310
+ else {
4311
+ const candidates = [];
4312
+ if (!config.includeBranches && config.activeBranch) candidates.push(pkg.getPublishBranchPath(), config.globalBranchOutputPath);
4313
+ candidates.push(pkg.getPublishPath(), config.globalOutputPath);
4314
+ for (const candidate of candidates) {
4315
+ const resolved = resolveConfiguredOutputPath(candidate, projectBasePath);
4316
+ if (!resolved) continue;
4317
+ outputDir = resolved;
4318
+ break;
4319
+ }
4320
+ }
4321
+ const publishName = pkg.getPublishName() || pkg.getName();
4322
+ return {
4323
+ pkg,
4324
+ outputDir,
4325
+ publishName,
4326
+ fileName: resolvePublishFileName(publishName, config.fileExtension),
4327
+ compressed: config.compressed,
4328
+ fileExtension: config.fileExtension,
4329
+ includeBranches: config.includeBranches,
4330
+ activeBranch: config.activeBranch,
4331
+ includeHighResolution: config.includeHighResolution,
4332
+ separatedAtlasForBranch: config.separatedAtlasForBranch,
4333
+ atlas: config.atlas
4334
+ };
4335
+ };
4336
+ const createNoopPublishFs = () => ({
4337
+ async writeFileRaw() {},
4338
+ async mkdir() {},
4339
+ join(...paths) {
4340
+ return paths.join("/");
4341
+ }
4342
+ });
4343
+ const publishPackage = async (plan, writerFs, packageIndex) => {
4344
+ const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
4345
+ await atlas({
4346
+ ...plan.atlas,
4347
+ ...options.atlas ?? {},
4348
+ separatedAtlasForBranch: plan.separatedAtlasForBranch,
4349
+ encoder: options.encoder,
4350
+ basePath: options.basePath,
4351
+ outputPath: options.fs ? plan.outputDir : void 0,
4352
+ mkdir: options.fs ? options.fs.mkdir : void 0,
4353
+ readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
4354
+ packages: [plan.pkg.getName()],
4355
+ ...atlasRuntimeOptions
4356
+ })(doc);
4357
+ if (!options.fs) return;
4358
+ if (!plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
4359
+ await options.fs.mkdir(plan.outputDir);
4360
+ const filePath = options.fs.join(plan.outputDir, plan.fileName);
4361
+ const bwOptions = {
4362
+ compressed: plan.compressed,
4363
+ packageIndex
4364
+ };
4365
+ await new _openfairygui_core.BinaryWriter(writerFs).write(doc, filePath, bwOptions);
4366
+ await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
4367
+ await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
4368
+ logger.info(`publish: Written ${plan.fileName}`);
4369
+ };
3812
4370
  const root = doc.getRoot();
3813
4371
  const logger = doc.getLogger();
3814
- const publishSettings = (root.getSettings?.() ?? {}).publish ?? {};
3815
- const resolved = resolvePublishOptions(doc, {
3816
- compressed: options.compressed,
3817
- fileExtension: options.fileExtension,
3818
- packages: options.packages,
3819
- atlas: options.atlas
3820
- });
3821
- const ext = resolved.fileExtension;
4372
+ const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || "";
4373
+ const pluginsDir = resolvePublishPluginsDir(doc, options);
4374
+ const plugins = pluginsDir ? await loadPlugins(doc, pluginsDir) : [];
4375
+ await runPublishPluginHook(plugins, "onPublishStart", doc, options);
4376
+ const resolved = resolveProjectPublishConfig();
3822
4377
  let allPackages = root.listPackages();
3823
4378
  if (resolved.packages && resolved.packages.length > 0) {
3824
4379
  const names = new Set(resolved.packages);
@@ -3826,57 +4381,42 @@ function publish(options) {
3826
4381
  }
3827
4382
  if (allPackages.length === 0) {
3828
4383
  logger.warn("publish: No packages to publish.");
4384
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3829
4385
  return;
3830
4386
  }
3831
- const includeBranches = (publishSettings.branchProcessing ?? 0) === 0;
3832
- const activeBranch = includeBranches ? "" : options.branch ?? "";
3833
- const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(ext);
3834
4387
  const allDocPackages = root.listPackages();
3835
4388
  const pkgMap = /* @__PURE__ */ new Map();
3836
4389
  for (const p of allDocPackages) pkgMap.set(p.getId(), p);
3837
4390
  for (const pkg of allPackages) {
3838
- _computeDependencies(pkg, pkgMap);
4391
+ _computeDependencies(doc, pkg, pkgMap);
3839
4392
  await annotatePackagePublishArtifacts(pkg, options.basePath, options.encoder, {
3840
- includeBranches,
3841
- activeBranch
4393
+ projectType: resolved.projectType,
4394
+ includeBranches: resolved.includeBranches,
4395
+ activeBranch: resolved.activeBranch,
4396
+ includeHighResolution: resolved.includeHighResolution
3842
4397
  });
3843
4398
  }
3844
- await atlas({
3845
- ...resolved.atlas,
3846
- ...options.atlas ?? {},
3847
- separatedAtlasForBranch: includeBranches && publishSettings.seperatedAtlasForBranch === true,
3848
- encoder: options.encoder,
3849
- basePath: options.basePath,
3850
- outputPath: options.fs ? options.output : void 0,
3851
- mkdir: options.fs ? options.fs.mkdir : void 0,
3852
- readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
3853
- ...atlasRuntimeOptions
3854
- })(doc);
4399
+ const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
3855
4400
  if (!options.fs) {
3856
4401
  logger.info(`publish: No fs provided — layout computed for ${allPackages.length} package(s), skipping file output.`);
4402
+ const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
4403
+ for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
4404
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3857
4405
  return;
3858
4406
  }
3859
- await options.fs.mkdir(options.output);
4407
+ const unresolvedPlan = plans.find((plan) => !plan.outputDir);
4408
+ if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
3860
4409
  const writerFs = toBinaryWriterFileSystem(options.fs);
3861
- for (const pkg of allPackages) {
3862
- const pkgIndex = allDocPackages.indexOf(pkg);
3863
- const fileName = resolvePublishFileName(pkg.getPublishName() || pkg.getName(), ext);
3864
- const filePath = options.fs.join(options.output, fileName);
3865
- const bwOptions = {
3866
- compressed: resolved.compressed,
3867
- packageIndex: pkgIndex
3868
- };
3869
- await new _openfairygui_core.BinaryWriter(writerFs).write(doc, filePath, bwOptions);
3870
- await exportPackageSounds(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
3871
- await exportPackageExternalResources(pkg, options.output, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
3872
- logger.info(`publish: Written ${fileName}`);
3873
- }
4410
+ for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
3874
4411
  await publishCodeGeneration(doc, {
3875
4412
  basePath: options.basePath,
3876
4413
  fs: options.fs,
3877
- packages: allPackages
4414
+ packages: allPackages,
4415
+ plugins
3878
4416
  });
3879
- logger.info(`publish: Published ${allPackages.length} package(s) to ${options.output}`);
4417
+ const publishedTargets = [...new Set(plans.map((plan) => plan.outputDir).filter((value) => Boolean(value)))];
4418
+ logger.info(publishedTargets.length > 0 ? `publish: Published ${allPackages.length} package(s) to ${publishedTargets.join(", ")}` : `publish: Published ${allPackages.length} package(s)`);
4419
+ await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
3880
4420
  });
3881
4421
  }
3882
4422
  /**
@@ -3884,25 +4424,104 @@ function publish(options) {
3884
4424
  * The editor only adds dependencies for packages referenced via bitmap font URLs.
3885
4425
  * @internal
3886
4426
  */
3887
- function _computeDependencies(pkg, pkgMap) {
4427
+ function _computeDependencies(doc, pkg, pkgMap) {
3888
4428
  const referencedPkgIds = /* @__PURE__ */ new Set();
3889
- function scanFontUrl(font) {
3890
- if (!font) return;
3891
- const fontStr = Array.isArray(font) ? font[0] : String(font);
3892
- if (typeof fontStr !== "string" || !fontStr.startsWith("ui://")) return;
3893
- const rest = fontStr.slice(5);
3894
- if (rest.length >= 8) {
3895
- const depPkgId = rest.slice(0, 8);
3896
- if (depPkgId !== pkg.getId()) referencedPkgIds.add(depPkgId);
4429
+ const pkgId = pkg.getId();
4430
+ const packageOrder = new Map(doc.getRoot().listPackages().map((entry, index) => [entry.getId(), index]));
4431
+ const addDependencyPackageId = (dependencyPkgId) => {
4432
+ const normalized = dependencyPkgId?.trim() ?? "";
4433
+ if (!normalized || normalized === pkgId) return;
4434
+ referencedPkgIds.add(normalized);
4435
+ };
4436
+ const extractPackageIdFromUiUrl = (value) => {
4437
+ if (!value.startsWith("ui://")) return null;
4438
+ const rest = value.slice(5);
4439
+ if (!rest) return null;
4440
+ const slashIndex = rest.indexOf("/");
4441
+ if (slashIndex >= 0) return rest.slice(0, slashIndex) || null;
4442
+ if (rest.length >= 8) return rest.slice(0, 8);
4443
+ return null;
4444
+ };
4445
+ const addDependencyPackageIdFromUiValue = (value) => {
4446
+ if (!value || typeof value !== "string") return;
4447
+ addDependencyPackageId(extractPackageIdFromUiUrl(value));
4448
+ };
4449
+ const addDependencyPackageIdsFromText = (value) => {
4450
+ if (!value || typeof value !== "string") return;
4451
+ const matches = value.matchAll(/ui:\/\/([0-9a-z]{8})/giu);
4452
+ for (const match of matches) addDependencyPackageId(match[1] ?? "");
4453
+ };
4454
+ const addDependencyPackageIdsFromUnknown = (value) => {
4455
+ if (Array.isArray(value)) {
4456
+ for (const entry of value) addDependencyPackageIdsFromUnknown(entry);
4457
+ return;
3897
4458
  }
3898
- }
4459
+ if (typeof value === "string") {
4460
+ addDependencyPackageIdFromUiValue(value);
4461
+ addDependencyPackageIdsFromText(value);
4462
+ }
4463
+ };
4464
+ const addDependencyFontRef = (value) => {
4465
+ if (Array.isArray(value)) {
4466
+ for (const entry of value) addDependencyPackageIdFromUiValue(entry);
4467
+ return;
4468
+ }
4469
+ addDependencyPackageIdFromUiValue(value ?? void 0);
4470
+ };
3899
4471
  for (const res of pkg.listResources()) {
3900
4472
  if (res.propertyType !== "Component") continue;
3901
- for (const child of res.listChildren?.() ?? []) scanFontUrl(child.getFont?.());
4473
+ const component = res;
4474
+ for (const child of component.listChildren?.() ?? []) {
4475
+ addDependencyPackageId(child.getPackageId?.());
4476
+ addDependencyFontRef(child.getFont?.());
4477
+ addDependencyPackageIdsFromText(child.getText?.());
4478
+ for (const ref of [
4479
+ child.getUrl?.(),
4480
+ child.getDefaultItem?.(),
4481
+ child.getIcon?.(),
4482
+ child.getSelectedIcon?.(),
4483
+ child.getDropdown?.(),
4484
+ child.getSound?.(),
4485
+ child.getInstanceSound?.(),
4486
+ child.getInstanceIcon?.(),
4487
+ child.getInstanceSelectedIcon?.(),
4488
+ child.getVtScrollBarRes?.(),
4489
+ child.getHzScrollBarRes?.(),
4490
+ child.getHeaderRes?.(),
4491
+ child.getFooterRes?.()
4492
+ ]) addDependencyPackageIdFromUiValue(ref);
4493
+ for (const item of child.getInstanceComboItems?.() ?? []) addDependencyPackageIdFromUiValue(item.icon ?? void 0);
4494
+ for (const item of child.getListItems?.() ?? []) {
4495
+ addDependencyPackageIdFromUiValue(item.icon ?? void 0);
4496
+ addDependencyPackageIdFromUiValue(item.url ?? void 0);
4497
+ }
4498
+ for (const gear of child.listGears?.() ?? []) {
4499
+ addDependencyPackageIdsFromUnknown(gear.getValues?.());
4500
+ addDependencyPackageIdsFromUnknown(gear.getDefaultValue?.());
4501
+ }
4502
+ }
4503
+ addDependencyFontRef(component.getFont?.());
4504
+ for (const ref of [
4505
+ component.getDropdown?.(),
4506
+ component.getHeaderRes?.(),
4507
+ component.getFooterRes?.(),
4508
+ component.getVtScrollBarRes?.(),
4509
+ component.getHzScrollBarRes?.(),
4510
+ component.getSound?.()
4511
+ ]) addDependencyPackageIdFromUiValue(ref);
4512
+ for (const transition of component.listTransitions?.() ?? []) for (const item of transition.listItems?.() ?? []) {
4513
+ addDependencyPackageIdsFromUnknown(item.getStartValue?.());
4514
+ addDependencyPackageIdsFromUnknown(item.getEndValue?.());
4515
+ }
3902
4516
  }
3903
4517
  for (const dep of pkg.listDependencies()) pkg.removeDependency(dep);
3904
4518
  if (referencedPkgIds.size > 0) {
3905
- const sortedIds = [...referencedPkgIds].sort((a, b) => a.localeCompare(b));
4519
+ const sortedIds = [...referencedPkgIds].sort((a, b) => {
4520
+ const orderA = packageOrder.get(a) ?? Number.MAX_SAFE_INTEGER;
4521
+ const orderB = packageOrder.get(b) ?? Number.MAX_SAFE_INTEGER;
4522
+ if (orderA !== orderB) return orderA - orderB;
4523
+ return a.localeCompare(b);
4524
+ });
3906
4525
  for (const refId of sortedIds) {
3907
4526
  const depPkg = pkgMap.get(refId);
3908
4527
  if (depPkg) pkg.addDependency(depPkg);
@@ -3910,49 +4529,20 @@ function _computeDependencies(pkg, pkgMap) {
3910
4529
  }
3911
4530
  }
3912
4531
  //#endregion
3913
- //#region src/uam-transaction.ts
3914
- function mapTransactionErrorStage(error) {
3915
- switch (error.code) {
3916
- case "transaction_unsupported": return "preflight";
3917
- case "invalid_uam": return "preflight";
3918
- case "execution_failure": return error.opIndex === void 0 && error.issues !== void 0 ? "postflight" : "execution";
3919
- case "selector_ambiguity": return error.opIndex === void 0 ? "preflight" : "execution";
3920
- }
3921
- }
3922
- function applyUamTransactionApp(input) {
3923
- try {
3924
- return {
3925
- ok: true,
3926
- project: (0, _openfairygui_core.applyUamTransaction)(input.project, input.operations)
3927
- };
3928
- } catch (error) {
3929
- if (error instanceof _openfairygui_core.UamTransactionError) return {
3930
- ok: false,
3931
- error: {
3932
- code: error.code,
3933
- stage: mapTransactionErrorStage(error),
3934
- message: error.message,
3935
- opIndex: error.opIndex,
3936
- opId: error.opId,
3937
- opKind: error.opKind,
3938
- selector: error.selector,
3939
- issues: error.issues
3940
- }
3941
- };
3942
- throw error;
3943
- }
3944
- }
3945
- //#endregion
3946
4532
  exports.AUTO_GENERATED_CODE_MARK = AUTO_GENERATED_CODE_MARK;
3947
4533
  exports.ValidationSeverity = ValidationSeverity;
3948
- exports.applyUamTransactionApp = applyUamTransactionApp;
4534
+ exports.applyUamTransactionApp = require_uam_transaction.applyUamTransactionApp;
3949
4535
  exports.atlas = atlas;
4536
+ exports.buildCodegenClasses = buildCodegenClasses;
3950
4537
  exports.createTransform = createTransform;
4538
+ exports.decodeText = decodeText;
4539
+ exports.encodeText = encodeText;
3951
4540
  exports.inspect = inspect;
3952
4541
  exports.prune = prune;
3953
4542
  exports.publish = publish;
3954
4543
  exports.publishCodeGeneration = publishCodeGeneration;
3955
4544
  exports.rename = rename;
4545
+ exports.resolvePackageCodegenPlan = resolvePackageCodegenPlan;
3956
4546
  exports.resolvePublishOptions = resolvePublishOptions;
3957
4547
  exports.restore = restore;
3958
4548
  exports.validate = validate;