@openfairygui/cli 0.2.0-alpha.13 → 0.2.0-alpha.15
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/README.md +3 -0
- package/dist/cli.mjs +418 -124
- package/package.json +4 -4
- package/src/commands/restore.ts +9 -6
package/README.md
CHANGED
|
@@ -14,9 +14,12 @@ npm install --global @openfairygui/cli
|
|
|
14
14
|
ofgui --help
|
|
15
15
|
ofgui inspect ./MyProject
|
|
16
16
|
ofgui publish ./MyProject --output ./release
|
|
17
|
+
# Trusted-local recovery only; this is not a normal authoring workflow.
|
|
17
18
|
ofgui restore ./release --output ./restored-project
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
`restore` accepts a publish directory and writes a new project directory. It validates artifact paths and completes a staged write before `--force` replaces an existing output; it does not make untrusted artifacts safe or recover the original source project.
|
|
22
|
+
|
|
20
23
|
The package also keeps `openfairygui` as a compatibility alias for the CLI command.
|
|
21
24
|
|
|
22
25
|
Repository:
|
package/dist/cli.mjs
CHANGED
|
@@ -1549,6 +1549,13 @@ var ImageResource = class extends ExtensibleProperty {
|
|
|
1549
1549
|
setImageData(buffer) {
|
|
1550
1550
|
return this.setRef("imageData", buffer);
|
|
1551
1551
|
}
|
|
1552
|
+
/** Primary source-file bytes for this image resource. */
|
|
1553
|
+
getSourceData() {
|
|
1554
|
+
return this.getImageData();
|
|
1555
|
+
}
|
|
1556
|
+
setSourceData(buffer) {
|
|
1557
|
+
return this.setImageData(buffer);
|
|
1558
|
+
}
|
|
1552
1559
|
getPixelHitTestData() {
|
|
1553
1560
|
const pixelWidth = this.get("pixelHitTestPixelWidth");
|
|
1554
1561
|
const scaleDenominator = this.get("pixelHitTestScaleDenominator");
|
|
@@ -1634,6 +1641,13 @@ var MiscResource = class extends ExtensibleProperty {
|
|
|
1634
1641
|
setResourceData(buffer) {
|
|
1635
1642
|
return this.setRef("resourceData", buffer);
|
|
1636
1643
|
}
|
|
1644
|
+
/** Primary source-file bytes for this miscellaneous resource. */
|
|
1645
|
+
getSourceData() {
|
|
1646
|
+
return this.getResourceData();
|
|
1647
|
+
}
|
|
1648
|
+
setSourceData(buffer) {
|
|
1649
|
+
return this.setResourceData(buffer);
|
|
1650
|
+
}
|
|
1637
1651
|
};
|
|
1638
1652
|
//#endregion
|
|
1639
1653
|
//#region ../core/src/properties/sound-resource.ts
|
|
@@ -1698,6 +1712,13 @@ var SoundResource = class extends ExtensibleProperty {
|
|
|
1698
1712
|
setSoundData(buffer) {
|
|
1699
1713
|
return this.setRef("soundData", buffer);
|
|
1700
1714
|
}
|
|
1715
|
+
/** Primary source-file bytes for this sound resource. */
|
|
1716
|
+
getSourceData() {
|
|
1717
|
+
return this.getSoundData();
|
|
1718
|
+
}
|
|
1719
|
+
setSourceData(buffer) {
|
|
1720
|
+
return this.setSoundData(buffer);
|
|
1721
|
+
}
|
|
1701
1722
|
};
|
|
1702
1723
|
//#endregion
|
|
1703
1724
|
//#region ../core/src/properties/font-resource.ts
|
|
@@ -1727,7 +1748,8 @@ var FontResource = class extends ExtensibleProperty {
|
|
|
1727
1748
|
fontSize: 0,
|
|
1728
1749
|
xAdvance: 0,
|
|
1729
1750
|
lineHeight: 0,
|
|
1730
|
-
glyphs: new RefList()
|
|
1751
|
+
glyphs: new RefList(),
|
|
1752
|
+
sourceData: null
|
|
1731
1753
|
});
|
|
1732
1754
|
}
|
|
1733
1755
|
getId() {
|
|
@@ -1835,6 +1857,13 @@ var FontResource = class extends ExtensibleProperty {
|
|
|
1835
1857
|
listGlyphs() {
|
|
1836
1858
|
return this.listRefs("glyphs");
|
|
1837
1859
|
}
|
|
1860
|
+
/** Primary source-file bytes for this font resource. */
|
|
1861
|
+
getSourceData() {
|
|
1862
|
+
return this.getRef("sourceData");
|
|
1863
|
+
}
|
|
1864
|
+
setSourceData(buffer) {
|
|
1865
|
+
return this.setRef("sourceData", buffer);
|
|
1866
|
+
}
|
|
1838
1867
|
};
|
|
1839
1868
|
//#endregion
|
|
1840
1869
|
//#region ../core/src/properties/movie-clip-resource.ts
|
|
@@ -1862,7 +1891,8 @@ var MovieClipResource = class extends ExtensibleProperty {
|
|
|
1862
1891
|
swing: false,
|
|
1863
1892
|
repeatDelay: 0,
|
|
1864
1893
|
smoothing: true,
|
|
1865
|
-
frames: new RefList()
|
|
1894
|
+
frames: new RefList(),
|
|
1895
|
+
sourceData: null
|
|
1866
1896
|
});
|
|
1867
1897
|
}
|
|
1868
1898
|
getId() {
|
|
@@ -1958,6 +1988,13 @@ var MovieClipResource = class extends ExtensibleProperty {
|
|
|
1958
1988
|
listFrames() {
|
|
1959
1989
|
return this.listRefs("frames");
|
|
1960
1990
|
}
|
|
1991
|
+
/** Primary source-file bytes for this movie-clip resource. */
|
|
1992
|
+
getSourceData() {
|
|
1993
|
+
return this.getRef("sourceData");
|
|
1994
|
+
}
|
|
1995
|
+
setSourceData(buffer) {
|
|
1996
|
+
return this.setRef("sourceData", buffer);
|
|
1997
|
+
}
|
|
1961
1998
|
};
|
|
1962
1999
|
//#endregion
|
|
1963
2000
|
//#region ../core/src/properties/skeleton-resource-base.ts
|
|
@@ -1979,7 +2016,8 @@ var SkeletonResourceBase = class extends ExtensibleProperty {
|
|
|
1979
2016
|
requireIds: [],
|
|
1980
2017
|
atlasNames: [],
|
|
1981
2018
|
anchorX: 0,
|
|
1982
|
-
anchorY: 0
|
|
2019
|
+
anchorY: 0,
|
|
2020
|
+
sourceData: null
|
|
1983
2021
|
});
|
|
1984
2022
|
}
|
|
1985
2023
|
getId() {
|
|
@@ -2058,6 +2096,13 @@ var SkeletonResourceBase = class extends ExtensibleProperty {
|
|
|
2058
2096
|
this.setAnchorX(x);
|
|
2059
2097
|
return this.setAnchorY(y);
|
|
2060
2098
|
}
|
|
2099
|
+
/** Primary source-file bytes for this skeleton resource. */
|
|
2100
|
+
getSourceData() {
|
|
2101
|
+
return this.getRef("sourceData");
|
|
2102
|
+
}
|
|
2103
|
+
setSourceData(buffer) {
|
|
2104
|
+
return this.setRef("sourceData", buffer);
|
|
2105
|
+
}
|
|
2061
2106
|
};
|
|
2062
2107
|
//#endregion
|
|
2063
2108
|
//#region ../core/src/properties/spine-resource.ts
|
|
@@ -10375,7 +10420,7 @@ var ProjectReader = class {
|
|
|
10375
10420
|
constructor(fs) {
|
|
10376
10421
|
this._fs = fs;
|
|
10377
10422
|
}
|
|
10378
|
-
async read(projectPath) {
|
|
10423
|
+
async read(projectPath, options = {}) {
|
|
10379
10424
|
const fs = this._fs;
|
|
10380
10425
|
const doc = new Document();
|
|
10381
10426
|
const basePath = getProjectBasePath(fs, projectPath);
|
|
@@ -10399,9 +10444,9 @@ var ProjectReader = class {
|
|
|
10399
10444
|
for (const dirName of packageDirs) {
|
|
10400
10445
|
const pkgXmlPath = fs.join(assetsPath, dirName, "package.xml");
|
|
10401
10446
|
if (!await fs.exists(pkgXmlPath)) continue;
|
|
10402
|
-
await this._readPackage(ctx, dirName, pkgXmlPath);
|
|
10447
|
+
await this._readPackage(ctx, dirName, pkgXmlPath, "", options);
|
|
10403
10448
|
}
|
|
10404
|
-
const branchNames = await this._readPackageBranches(ctx);
|
|
10449
|
+
const branchNames = await this._readPackageBranches(ctx, options);
|
|
10405
10450
|
if (branchNames.length > 0) doc.getRoot().setBranches(branchNames);
|
|
10406
10451
|
for (const [_key, resource] of ctx.resourceMap) {
|
|
10407
10452
|
if (resource.propertyType !== "Component") continue;
|
|
@@ -10417,7 +10462,7 @@ var ProjectReader = class {
|
|
|
10417
10462
|
}
|
|
10418
10463
|
return doc;
|
|
10419
10464
|
}
|
|
10420
|
-
async _readPackageBranches(ctx) {
|
|
10465
|
+
async _readPackageBranches(ctx, options) {
|
|
10421
10466
|
const fs = this._fs;
|
|
10422
10467
|
let dirNames = [];
|
|
10423
10468
|
try {
|
|
@@ -10437,7 +10482,7 @@ var ProjectReader = class {
|
|
|
10437
10482
|
for (const dirName of packageDirs) {
|
|
10438
10483
|
const pkgXmlPath = fs.join(branchAssetsPath, dirName, "package_branch.xml");
|
|
10439
10484
|
if (!await fs.exists(pkgXmlPath)) continue;
|
|
10440
|
-
await this._readPackage(ctx, dirName, pkgXmlPath, branchName);
|
|
10485
|
+
await this._readPackage(ctx, dirName, pkgXmlPath, branchName, options);
|
|
10441
10486
|
}
|
|
10442
10487
|
}
|
|
10443
10488
|
return branchNames;
|
|
@@ -10475,7 +10520,7 @@ var ProjectReader = class {
|
|
|
10475
10520
|
} catch {}
|
|
10476
10521
|
ctx.document.getRoot().setSettings(ctx.settings);
|
|
10477
10522
|
}
|
|
10478
|
-
async _readPackage(ctx, dirName, pkgXmlPath, branchName = "") {
|
|
10523
|
+
async _readPackage(ctx, dirName, pkgXmlPath, branchName = "", options = {}) {
|
|
10479
10524
|
const fs = this._fs;
|
|
10480
10525
|
const content = await fs.readFile(pkgXmlPath);
|
|
10481
10526
|
const xml = parseXML(content);
|
|
@@ -10513,6 +10558,7 @@ var ProjectReader = class {
|
|
|
10513
10558
|
if (resource) createdResources.push(resource);
|
|
10514
10559
|
}
|
|
10515
10560
|
await this._hydratePackageImageSizes(createdResources, packageDir);
|
|
10561
|
+
if (options.hydrateResourceBytes) await this._hydratePackageResourceBytes(ctx.document, createdResources, packageDir);
|
|
10516
10562
|
return;
|
|
10517
10563
|
}
|
|
10518
10564
|
for (const tagName of [
|
|
@@ -10521,6 +10567,8 @@ var ProjectReader = class {
|
|
|
10521
10567
|
"font",
|
|
10522
10568
|
"sound",
|
|
10523
10569
|
"movieclip",
|
|
10570
|
+
"spine",
|
|
10571
|
+
"dragonbones",
|
|
10524
10572
|
"swf",
|
|
10525
10573
|
"misc",
|
|
10526
10574
|
"atlas"
|
|
@@ -10534,6 +10582,7 @@ var ProjectReader = class {
|
|
|
10534
10582
|
}
|
|
10535
10583
|
}
|
|
10536
10584
|
await this._hydratePackageImageSizes(createdResources, packageDir);
|
|
10585
|
+
if (options.hydrateResourceBytes) await this._hydratePackageResourceBytes(ctx.document, createdResources, packageDir);
|
|
10537
10586
|
}
|
|
10538
10587
|
async _hydratePackageImageSizes(resources, packageDir) {
|
|
10539
10588
|
const fs = this._fs;
|
|
@@ -10544,7 +10593,9 @@ var ProjectReader = class {
|
|
|
10544
10593
|
const fileName = image.getFileName?.() ?? "";
|
|
10545
10594
|
if (!fileName) continue;
|
|
10546
10595
|
const resourcePath = image.getPath?.() ?? "/";
|
|
10547
|
-
const
|
|
10596
|
+
const sourcePath = this._packageRelativeSourcePath(resourcePath, fileName);
|
|
10597
|
+
if (!sourcePath) continue;
|
|
10598
|
+
const filePath = fs.join(packageDir, sourcePath.replace(/^\/+/, ""));
|
|
10548
10599
|
if (!await fs.exists(filePath)) continue;
|
|
10549
10600
|
try {
|
|
10550
10601
|
const size = readImageSize(await fs.readFileRaw(filePath));
|
|
@@ -10554,6 +10605,44 @@ var ProjectReader = class {
|
|
|
10554
10605
|
} catch {}
|
|
10555
10606
|
}
|
|
10556
10607
|
}
|
|
10608
|
+
async _hydratePackageResourceBytes(doc, resources, packageDir) {
|
|
10609
|
+
const fs = this._fs;
|
|
10610
|
+
for (const resource of resources) {
|
|
10611
|
+
const fileName = this._primaryResourceFileName(resource);
|
|
10612
|
+
if (!fileName) continue;
|
|
10613
|
+
const resourcePath = resource.getPath?.() ?? "/";
|
|
10614
|
+
const sourcePath = this._packageRelativeSourcePath(resourcePath, fileName);
|
|
10615
|
+
if (!sourcePath) continue;
|
|
10616
|
+
const filePath = fs.join(packageDir, sourcePath.replace(/^\/+/, ""));
|
|
10617
|
+
if (!await fs.exists(filePath)) continue;
|
|
10618
|
+
try {
|
|
10619
|
+
const data = new Uint8Array(await fs.readFileRaw(filePath));
|
|
10620
|
+
const buffer = doc.createBuffer().setURI(sourcePath).setData(data);
|
|
10621
|
+
this._asSourceDataResource(resource).setSourceData(buffer);
|
|
10622
|
+
} catch {}
|
|
10623
|
+
}
|
|
10624
|
+
}
|
|
10625
|
+
_primaryResourceFileName(resource) {
|
|
10626
|
+
switch (resource.propertyType) {
|
|
10627
|
+
case "ImageResource":
|
|
10628
|
+
case "FontResource":
|
|
10629
|
+
case "MovieClipResource": return resource.getFileName();
|
|
10630
|
+
case "SoundResource":
|
|
10631
|
+
case "MiscResource":
|
|
10632
|
+
case "SpineResource":
|
|
10633
|
+
case "DragonBonesResource": return resource.getFile();
|
|
10634
|
+
default: return "";
|
|
10635
|
+
}
|
|
10636
|
+
}
|
|
10637
|
+
_packageRelativeSourcePath(resourcePath, fileName) {
|
|
10638
|
+
if (!fileName || /[\\/:]/.test(fileName) || fileName === "." || fileName === "..") return null;
|
|
10639
|
+
const segments = resourcePath.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
10640
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":"))) return null;
|
|
10641
|
+
return `/${[...segments, fileName].join("/")}`;
|
|
10642
|
+
}
|
|
10643
|
+
_asSourceDataResource(resource) {
|
|
10644
|
+
return resource;
|
|
10645
|
+
}
|
|
10557
10646
|
_createResourceFromXML(ctx, pkg, tagName, attrs, packageDir, branchName = "") {
|
|
10558
10647
|
const doc = ctx.document;
|
|
10559
10648
|
const fs = this._fs;
|
|
@@ -12425,21 +12514,18 @@ function assertDisplayListVariantAllowed(propertyType, tagName, childName) {
|
|
|
12425
12514
|
const variantName = getDisplayListVariantName(propertyType, tagName);
|
|
12426
12515
|
if (!DISPLAY_LIST_ALLOWED_VARIANTS.has(variantName)) throw new Error(`displayList variant "${variantName}" derived from propertyType "${propertyType}" is not declared in protocol for child "${childName}"`);
|
|
12427
12516
|
}
|
|
12428
|
-
/**
|
|
12429
|
-
* Writes a {@link Document} to disk as a FairyGUI project
|
|
12430
|
-
* (.fairy file + settings JSON + assets directory with package.xml and component XML files).
|
|
12431
|
-
*
|
|
12432
|
-
* @category I/O
|
|
12433
|
-
*/
|
|
12434
12517
|
var ProjectWriter = class {
|
|
12435
12518
|
_fs;
|
|
12436
12519
|
constructor(fs) {
|
|
12437
12520
|
this._fs = fs;
|
|
12438
12521
|
}
|
|
12439
|
-
async write(doc, projectPath) {
|
|
12522
|
+
async write(doc, projectPath, options = {}) {
|
|
12440
12523
|
const fs = this._fs;
|
|
12441
12524
|
const root = doc.getRoot();
|
|
12442
12525
|
const basePath = fs.dirname(projectPath);
|
|
12526
|
+
const currentSourceFilePaths = /* @__PURE__ */ new Set();
|
|
12527
|
+
const staleSourceFilePaths = new Set((options.staleSourceFiles ?? []).map((source) => this._projectSourceFilePath(basePath, source)));
|
|
12528
|
+
for (const pkg of root.listPackages()) this._assertPackageOutputTargets(pkg);
|
|
12443
12529
|
const fairyXml = `<?xml version="1.0" encoding="utf-8"?>\n<projectDescription id="${root.getProjectId()}" type="${this._projectTypeName(root.getProjectType())}" version="${root.getVersion() || "3.0"}"/>\n`;
|
|
12444
12530
|
await fs.writeFile(projectPath, fairyXml);
|
|
12445
12531
|
const settings = root.getSettings?.() ?? {};
|
|
@@ -12452,10 +12538,12 @@ var ProjectWriter = class {
|
|
|
12452
12538
|
})) if (settings[key]) await fs.writeFile(fs.join(settingsPath, fileName), JSON.stringify(settings[key], null, " "));
|
|
12453
12539
|
const assetsPath = fs.join(basePath, "assets");
|
|
12454
12540
|
await fs.mkdir(assetsPath);
|
|
12455
|
-
for (const pkg of root.listPackages()) await this._writePackage(doc, pkg, assetsPath);
|
|
12541
|
+
for (const pkg of root.listPackages()) await this._writePackage(doc, pkg, assetsPath, currentSourceFilePaths);
|
|
12542
|
+
await this._removeStaleSourceFiles(currentSourceFilePaths, staleSourceFilePaths);
|
|
12456
12543
|
}
|
|
12457
|
-
async _writePackage(_doc, pkg, assetsPath) {
|
|
12544
|
+
async _writePackage(_doc, pkg, assetsPath, currentSourceFilePaths) {
|
|
12458
12545
|
const fs = this._fs;
|
|
12546
|
+
this._assertSafePathSegment(pkg.getName(), "package name");
|
|
12459
12547
|
const pkgDir = fs.join(assetsPath, pkg.getName());
|
|
12460
12548
|
await fs.mkdir(pkgDir);
|
|
12461
12549
|
const basePath = fs.dirname(assetsPath);
|
|
@@ -12496,13 +12584,93 @@ var ProjectWriter = class {
|
|
|
12496
12584
|
if (publishAtlases.length > 0) publishAttrs.atlas = publishAtlases;
|
|
12497
12585
|
await fs.writeFile(fs.join(pkgDir, "package.xml"), this._renderPackageDescriptionXml(packageDescriptionAttrs, mainResources, publishAttrs));
|
|
12498
12586
|
for (const comp of mainResources.filter((resource) => resource.propertyType === "Component")) await this._writeComponent(comp, pkgDir);
|
|
12587
|
+
await this._writeResourceSourceFiles(mainResources, pkgDir, currentSourceFilePaths);
|
|
12499
12588
|
for (const [branchName, branchResources] of resourcesByBranch) {
|
|
12500
12589
|
if (!branchName) continue;
|
|
12590
|
+
this._assertSafePathSegment(branchName, "branch name");
|
|
12501
12591
|
const branchPkgDir = fs.join(basePath, `assets_${branchName}`, pkg.getName());
|
|
12502
12592
|
await fs.mkdir(branchPkgDir);
|
|
12503
12593
|
await fs.writeFile(fs.join(branchPkgDir, "package_branch.xml"), this._renderBranchDescriptionXml(branchResources));
|
|
12504
12594
|
for (const comp of branchResources.filter((resource) => resource.propertyType === "Component")) await this._writeComponent(comp, branchPkgDir);
|
|
12595
|
+
await this._writeResourceSourceFiles(branchResources, branchPkgDir, currentSourceFilePaths);
|
|
12596
|
+
}
|
|
12597
|
+
}
|
|
12598
|
+
async _writeResourceSourceFiles(resources, packageDir, currentSourceFilePaths) {
|
|
12599
|
+
const fs = this._fs;
|
|
12600
|
+
for (const resource of resources) {
|
|
12601
|
+
if (resource.propertyType === "Component") continue;
|
|
12602
|
+
const fileName = this._resourceFileName(resource);
|
|
12603
|
+
if (!fileName) continue;
|
|
12604
|
+
const relativePath = this._resourceSourceRelativePath(resource, fileName);
|
|
12605
|
+
const targetPath = fs.join(packageDir, relativePath);
|
|
12606
|
+
currentSourceFilePaths.add(targetPath);
|
|
12607
|
+
const sourceData = resource.getSourceData?.();
|
|
12608
|
+
if (!sourceData) continue;
|
|
12609
|
+
const data = sourceData.getData();
|
|
12610
|
+
if (!data) continue;
|
|
12611
|
+
await fs.mkdir(fs.dirname(targetPath));
|
|
12612
|
+
await fs.writeFileRaw(targetPath, new Uint8Array(data));
|
|
12613
|
+
}
|
|
12614
|
+
}
|
|
12615
|
+
async _removeStaleSourceFiles(currentSourceFilePaths, staleSourceFilePaths) {
|
|
12616
|
+
const fs = this._fs;
|
|
12617
|
+
const candidates = [...staleSourceFilePaths].filter((filePath) => !currentSourceFilePaths.has(filePath));
|
|
12618
|
+
if (candidates.length === 0) return;
|
|
12619
|
+
if (!fs.unlink) throw new Error("Project source cleanup requires a FileSystem.unlink() implementation.");
|
|
12620
|
+
for (const filePath of candidates) {
|
|
12621
|
+
if (!await fs.exists(filePath)) continue;
|
|
12622
|
+
await fs.unlink(filePath);
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
_assertPackageOutputTargets(pkg) {
|
|
12626
|
+
this._assertSafePathSegment(pkg.getName(), "package name");
|
|
12627
|
+
const resourcesByBranch = /* @__PURE__ */ new Map();
|
|
12628
|
+
for (const resource of pkg.listResources()) {
|
|
12629
|
+
const branchName = resource.getBranch?.() ?? "";
|
|
12630
|
+
const bucket = resourcesByBranch.get(branchName) ?? [];
|
|
12631
|
+
bucket.push(resource);
|
|
12632
|
+
resourcesByBranch.set(branchName, bucket);
|
|
12505
12633
|
}
|
|
12634
|
+
for (const [branchName, resources] of resourcesByBranch) {
|
|
12635
|
+
if (branchName) this._assertSafePathSegment(branchName, "branch name");
|
|
12636
|
+
const targets = new Map([[branchName ? "package_branch.xml" : "package.xml", "package descriptor"]]);
|
|
12637
|
+
for (const resource of resources) {
|
|
12638
|
+
const target = resource.propertyType === "Component" ? this._componentSourceRelativePath(resource) : this._resourceSourceRelativePath(resource, this._resourceFileName(resource));
|
|
12639
|
+
if (!target) continue;
|
|
12640
|
+
const previous = targets.get(target);
|
|
12641
|
+
if (previous) throw new Error(`Package "${pkg.getName()}" output "${target}" conflicts with ${previous}.`);
|
|
12642
|
+
targets.set(target, `resource "${resource.getId?.() ?? resource.getName()}"`);
|
|
12643
|
+
}
|
|
12644
|
+
}
|
|
12645
|
+
}
|
|
12646
|
+
_projectSourceFilePath(basePath, source) {
|
|
12647
|
+
this._assertSafePathSegment(source.packageName, "stale source package name");
|
|
12648
|
+
if (source.branch) this._assertSafePathSegment(source.branch, "stale source branch name");
|
|
12649
|
+
this._assertSafePathSegment(source.fileName, "stale source file name");
|
|
12650
|
+
const relativePath = this._normalizeSourceRelativePath([source.path, source.fileName].filter(Boolean).join("/"));
|
|
12651
|
+
const assetRoot = source.branch ? `assets_${source.branch}` : "assets";
|
|
12652
|
+
return this._fs.join(basePath, assetRoot, source.packageName, relativePath);
|
|
12653
|
+
}
|
|
12654
|
+
_resourceSourceRelativePath(resource, fileName) {
|
|
12655
|
+
if (!fileName) return "";
|
|
12656
|
+
this._assertSafePathSegment(fileName, "resource file name");
|
|
12657
|
+
const normalizedPath = (resource.getPath?.() ?? "/").replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
|
|
12658
|
+
return this._normalizeSourceRelativePath([normalizedPath, fileName].filter(Boolean).join("/"));
|
|
12659
|
+
}
|
|
12660
|
+
_componentSourceRelativePath(component) {
|
|
12661
|
+
const typedComponent = component;
|
|
12662
|
+
const name = component.getName();
|
|
12663
|
+
this._assertSafePathSegment(name, "component name");
|
|
12664
|
+
const componentPath = typedComponent.getPath?.() ?? "/";
|
|
12665
|
+
return this._normalizeSourceRelativePath([componentPath, `${name}.xml`].filter(Boolean).join("/"));
|
|
12666
|
+
}
|
|
12667
|
+
_assertSafePathSegment(value, label) {
|
|
12668
|
+
if (!value || value === "." || value === ".." || /[\\/:]/.test(value)) throw new Error(`Invalid ${label} "${value}".`);
|
|
12669
|
+
}
|
|
12670
|
+
_normalizeSourceRelativePath(value) {
|
|
12671
|
+
const segments = value.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
12672
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":"))) throw new Error(`Invalid project source path "${value}".`);
|
|
12673
|
+
return segments.join("/");
|
|
12506
12674
|
}
|
|
12507
12675
|
_renderPackageDescriptionXml(packageDescriptionAttrs, resources, publishAttrs) {
|
|
12508
12676
|
const publishNodeAttrs = Object.fromEntries(Object.entries(publishAttrs).filter(([key]) => key !== "atlas"));
|
|
@@ -12647,11 +12815,8 @@ var ProjectWriter = class {
|
|
|
12647
12815
|
async _writeComponent(comp, pkgDir) {
|
|
12648
12816
|
const fs = this._fs;
|
|
12649
12817
|
const typedComp = comp;
|
|
12650
|
-
const
|
|
12651
|
-
|
|
12652
|
-
const subDir = path.replace(/^\//, "").replace(/\/$/, "");
|
|
12653
|
-
const fileDir = subDir ? fs.join(pkgDir, subDir) : pkgDir;
|
|
12654
|
-
if (subDir) await fs.mkdir(fileDir);
|
|
12818
|
+
const targetPath = fs.join(pkgDir, this._componentSourceRelativePath(comp));
|
|
12819
|
+
await fs.mkdir(fs.dirname(targetPath));
|
|
12655
12820
|
const compAttrs = {};
|
|
12656
12821
|
const [w, h] = [typedComp.getWidth?.() ?? 0, typedComp.getHeight?.() ?? 0];
|
|
12657
12822
|
if (w || h) writeXmlAttr(compAttrs, PROJECT_XML_PROTOCOL.componentRoot.attrs.size, `${w},${h}`);
|
|
@@ -12793,7 +12958,7 @@ var ProjectWriter = class {
|
|
|
12793
12958
|
},
|
|
12794
12959
|
component: compNode
|
|
12795
12960
|
};
|
|
12796
|
-
await fs.writeFile(
|
|
12961
|
+
await fs.writeFile(targetPath, builder.build(xmlObj));
|
|
12797
12962
|
}
|
|
12798
12963
|
_serializeController(ctrl) {
|
|
12799
12964
|
const pagesStr = ctrl.listPages().map((p) => `${p.getId()},${p.getName()}`).join(",");
|
|
@@ -21033,11 +21198,11 @@ function getPixelHitTestEntry(resource) {
|
|
|
21033
21198
|
* @category I/O
|
|
21034
21199
|
*/
|
|
21035
21200
|
var PlatformIO = class {
|
|
21036
|
-
async readProject(projectPath) {
|
|
21037
|
-
return new ProjectReader(this.createFileSystem()).read(projectPath);
|
|
21201
|
+
async readProject(projectPath, options) {
|
|
21202
|
+
return new ProjectReader(this.createFileSystem()).read(projectPath, options);
|
|
21038
21203
|
}
|
|
21039
|
-
async writeProject(doc, projectPath) {
|
|
21040
|
-
return new ProjectWriter(this.createFileSystem()).write(doc, projectPath);
|
|
21204
|
+
async writeProject(doc, projectPath, options) {
|
|
21205
|
+
return new ProjectWriter(this.createFileSystem()).write(doc, projectPath, options);
|
|
21041
21206
|
}
|
|
21042
21207
|
async readBinary(filePath) {
|
|
21043
21208
|
return new BinaryReader(this.createFileSystem()).read(filePath);
|
|
@@ -21756,7 +21921,8 @@ const ATLAS_DEFAULTS = {
|
|
|
21756
21921
|
preserveInputOrderOnTie: false,
|
|
21757
21922
|
directSingleImageOutput: false,
|
|
21758
21923
|
extractAlpha: false,
|
|
21759
|
-
separatedAtlasForBranch: false
|
|
21924
|
+
separatedAtlasForBranch: false,
|
|
21925
|
+
strictOutput: false
|
|
21760
21926
|
};
|
|
21761
21927
|
function getPublishedItemId(resource) {
|
|
21762
21928
|
return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
|
|
@@ -21913,8 +22079,9 @@ function atlas(_options = {}) {
|
|
|
21913
22079
|
const packageFilter = options.packages ? new Set(options.packages) : null;
|
|
21914
22080
|
for (const pkg of root.listPackages()) {
|
|
21915
22081
|
if (packageFilter && !packageFilter.has(pkg.getName())) continue;
|
|
21916
|
-
const
|
|
21917
|
-
const
|
|
22082
|
+
const publishedResourceIds = pkg.getExtras()?.publishedResourceIds;
|
|
22083
|
+
const selectedPublishIds = new Set(publishedResourceIds);
|
|
22084
|
+
const allResources = publishedResourceIds !== void 0 && (options.strictOutput || selectedPublishIds.size > 0) ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
|
|
21918
22085
|
const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
|
|
21919
22086
|
const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
|
|
21920
22087
|
const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
|
|
@@ -22003,6 +22170,7 @@ function atlas(_options = {}) {
|
|
|
22003
22170
|
await _collectFontTexture(doc, res, pkg, options);
|
|
22004
22171
|
}
|
|
22005
22172
|
if (inputs.length === 0) continue;
|
|
22173
|
+
if (options.strictOutput && (!encoder || !options.basePath || !options.outputPath)) throw new Error(`atlas: Package "${pkg.getName()}" requires encoder, basePath, and outputPath for complete raster output.`);
|
|
22006
22174
|
let totalPageCount = 0;
|
|
22007
22175
|
let usedDirectOutput = false;
|
|
22008
22176
|
const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
|
|
@@ -22100,7 +22268,7 @@ function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageInde
|
|
|
22100
22268
|
async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
|
|
22101
22269
|
if (inputs.length === 0) return 0;
|
|
22102
22270
|
const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
|
|
22103
|
-
|
|
22271
|
+
assertPackedInputCoverage(pages, inputs.length, `package "${pkg.getName()}"`);
|
|
22104
22272
|
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
22105
22273
|
const page = pages[pageOffset];
|
|
22106
22274
|
const pageIndex = context.pageStart + pageOffset;
|
|
@@ -22126,7 +22294,7 @@ async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
|
|
|
22126
22294
|
multipleOfFour: true,
|
|
22127
22295
|
square: false
|
|
22128
22296
|
} : void 0);
|
|
22129
|
-
|
|
22297
|
+
assertPackedInputCoverage(pages, group.inputs.length, `standalone texture in package "${pkg.getName()}"`);
|
|
22130
22298
|
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
22131
22299
|
const page = pages[pageOffset];
|
|
22132
22300
|
const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
|
|
@@ -22169,6 +22337,12 @@ function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
|
|
|
22169
22337
|
preserveInputOrderOnTie: options.preserveInputOrderOnTie
|
|
22170
22338
|
}).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
|
|
22171
22339
|
}
|
|
22340
|
+
function assertPackedInputCoverage(pages, inputCount, label) {
|
|
22341
|
+
const packedIndexes = /* @__PURE__ */ new Set();
|
|
22342
|
+
for (const page of pages) for (const outputRect of page.outputRects) packedIndexes.add(outputRect.index);
|
|
22343
|
+
const hasEveryInput = Array.from({ length: inputCount }, (_, index) => packedIndexes.has(index)).every(Boolean);
|
|
22344
|
+
if (packedIndexes.size !== inputCount || !hasEveryInput) throw new Error(`atlas: Could not pack every input for ${label}.`);
|
|
22345
|
+
}
|
|
22172
22346
|
function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
|
|
22173
22347
|
for (const packedRect of outputRects) {
|
|
22174
22348
|
const input = inputs[packedRect.index];
|
|
@@ -22228,7 +22402,9 @@ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, op
|
|
|
22228
22402
|
} else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
|
|
22229
22403
|
else {
|
|
22230
22404
|
if (!isImageResource$1(input.resource)) {
|
|
22231
|
-
|
|
22405
|
+
const message = `atlas: Non-image input "${input.id}" is missing inline buffer.`;
|
|
22406
|
+
if (options.strictOutput) throw new Error(message);
|
|
22407
|
+
logger.warn(`${message} Skipping compositing.`);
|
|
22232
22408
|
continue;
|
|
22233
22409
|
}
|
|
22234
22410
|
imageBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
|
|
@@ -22240,7 +22416,9 @@ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, op
|
|
|
22240
22416
|
top: packedRect.y
|
|
22241
22417
|
});
|
|
22242
22418
|
} catch {
|
|
22243
|
-
|
|
22419
|
+
const message = `atlas: Could not read image "${input.id}" for compositing.`;
|
|
22420
|
+
if (options.strictOutput) throw new Error(message);
|
|
22421
|
+
logger.warn(message);
|
|
22244
22422
|
}
|
|
22245
22423
|
}
|
|
22246
22424
|
const outputFile = `${options.outputPath}/${atlasFileName}`;
|
|
@@ -22357,7 +22535,9 @@ async function emitDirectImageOutput(doc, pkg, input, encoder, options, logger,
|
|
|
22357
22535
|
}]).png().toFile(outputFile);
|
|
22358
22536
|
}
|
|
22359
22537
|
} catch {
|
|
22360
|
-
|
|
22538
|
+
const message = `atlas: Could not write direct-output atlas "${atlasFileName}".`;
|
|
22539
|
+
if (options.strictOutput) throw new Error(message);
|
|
22540
|
+
logger.warn(message);
|
|
22361
22541
|
}
|
|
22362
22542
|
}
|
|
22363
22543
|
function getInputBranchName(input) {
|
|
@@ -22583,6 +22763,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
|
|
|
22583
22763
|
sourceHasAlpha = true;
|
|
22584
22764
|
}
|
|
22585
22765
|
} catch {
|
|
22766
|
+
if (options.strictOutput) throw new Error(`atlas: Could not read image "${filePath}".`);
|
|
22586
22767
|
if (origW === 0 || origH === 0) {
|
|
22587
22768
|
logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
|
|
22588
22769
|
return;
|
|
@@ -22621,7 +22802,11 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
|
|
|
22621
22802
|
}
|
|
22622
22803
|
/** Collect MovieClip frame textures from a .jta file into the inputs array. */
|
|
22623
22804
|
async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, options, logger) {
|
|
22624
|
-
if (!options.basePath || !options.readFileRaw)
|
|
22805
|
+
if (!options.basePath || !options.readFileRaw) {
|
|
22806
|
+
if (options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires basePath and readFileRaw for complete raster output.`);
|
|
22807
|
+
return;
|
|
22808
|
+
}
|
|
22809
|
+
if (!encoder && options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires an encoder for complete raster output.`);
|
|
22625
22810
|
const mcId = resource.getId();
|
|
22626
22811
|
const mcName = resource.getName() + ".jta";
|
|
22627
22812
|
const mcPath = resource.getPath() ?? "/";
|
|
@@ -22644,7 +22829,7 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
22644
22829
|
const exportFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
|
|
22645
22830
|
if (exportFrameIndex === void 0) continue;
|
|
22646
22831
|
const itemId = `${mcId}_${exportFrameIndex}`;
|
|
22647
|
-
const input = await _createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder);
|
|
22832
|
+
const input = await _createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder, options.strictOutput);
|
|
22648
22833
|
if (!input) continue;
|
|
22649
22834
|
inputs.push(input);
|
|
22650
22835
|
spriteIdByTextureIndex.set(textureIndex, itemId);
|
|
@@ -22658,7 +22843,7 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
22658
22843
|
}
|
|
22659
22844
|
} else for (let frameIndex = 0; frameIndex < jta.frames.length; frameIndex += 1) {
|
|
22660
22845
|
const itemId = `${mcId}_${frameIndex}`;
|
|
22661
|
-
const input = await _createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder);
|
|
22846
|
+
const input = await _createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder, options.strictOutput);
|
|
22662
22847
|
if (!input) continue;
|
|
22663
22848
|
inputs.push(input);
|
|
22664
22849
|
const frame = doc.createMovieFrame(itemId);
|
|
@@ -22670,10 +22855,12 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
22670
22855
|
resource.setHeight(jta.meta?.height ?? 0);
|
|
22671
22856
|
}
|
|
22672
22857
|
} catch {
|
|
22673
|
-
|
|
22858
|
+
const message = `atlas: Could not parse MovieClip "${filePath}".`;
|
|
22859
|
+
if (options.strictOutput) throw new Error(message);
|
|
22860
|
+
logger.warn(`${message} Skipping frames.`);
|
|
22674
22861
|
}
|
|
22675
22862
|
}
|
|
22676
|
-
async function _createMovieClipFrameInput(buffer, itemId, resource, encoder) {
|
|
22863
|
+
async function _createMovieClipFrameInput(buffer, itemId, resource, encoder, strictOutput) {
|
|
22677
22864
|
if (!encoder || buffer.length === 0) return null;
|
|
22678
22865
|
try {
|
|
22679
22866
|
const meta = await encoder(buffer).metadata();
|
|
@@ -22693,6 +22880,7 @@ async function _createMovieClipFrameInput(buffer, itemId, resource, encoder) {
|
|
|
22693
22880
|
sourceKind: "movieclip-frame"
|
|
22694
22881
|
};
|
|
22695
22882
|
} catch {
|
|
22883
|
+
if (strictOutput) throw new Error(`atlas: Could not decode MovieClip frame "${itemId}".`);
|
|
22696
22884
|
return null;
|
|
22697
22885
|
}
|
|
22698
22886
|
}
|
|
@@ -23375,9 +23563,9 @@ function resolveProjectBasePath(basePath) {
|
|
|
23375
23563
|
const normalized = trimTrailingSlashes$2(basePath);
|
|
23376
23564
|
const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
|
|
23377
23565
|
if (assetsMatch?.[1]) return assetsMatch[1];
|
|
23378
|
-
return dirname$
|
|
23566
|
+
return dirname$1(normalized);
|
|
23379
23567
|
}
|
|
23380
|
-
function dirname$
|
|
23568
|
+
function dirname$1(filePath) {
|
|
23381
23569
|
return trimTrailingSlashes$2(filePath).match(/^(.*)[/\\][^/\\]+$/)?.[1] ?? "";
|
|
23382
23570
|
}
|
|
23383
23571
|
function trimTrailingSlashes$2(value) {
|
|
@@ -23522,10 +23710,16 @@ const TRANSPARENT_PNG_1X1 = Uint8Array.from([
|
|
|
23522
23710
|
96,
|
|
23523
23711
|
130
|
|
23524
23712
|
]);
|
|
23713
|
+
function assertSafeRestoreSegment(value, label) {
|
|
23714
|
+
if (!value || value === "." || value === ".." || value.includes("\0") || /[\\/:]/u.test(value)) throw new Error(`restore: Invalid ${label} "${value}".`);
|
|
23715
|
+
}
|
|
23525
23716
|
function normalizeVirtualPath(path) {
|
|
23526
|
-
const
|
|
23527
|
-
if (!
|
|
23528
|
-
|
|
23717
|
+
const raw = (path ?? "").trim();
|
|
23718
|
+
if (!raw || raw === "/") return "";
|
|
23719
|
+
if (raw.includes("\0") || raw.startsWith("\\") || raw.startsWith("//") || /^[a-z]:/iu.test(raw)) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
23720
|
+
const segments = raw.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
23721
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":"))) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
23722
|
+
return segments.join("/");
|
|
23529
23723
|
}
|
|
23530
23724
|
function resourceFileName(resource) {
|
|
23531
23725
|
return resource.getFileName?.() || resource.getFile?.() || resource.getName?.() || "";
|
|
@@ -23709,44 +23903,40 @@ function normalizeComparablePath(value) {
|
|
|
23709
23903
|
const joined = segments.join("/");
|
|
23710
23904
|
return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
|
|
23711
23905
|
}
|
|
23712
|
-
function
|
|
23713
|
-
|
|
23906
|
+
function isPathWithin(root, candidate) {
|
|
23907
|
+
const normalizedRoot = normalizeComparablePath(root);
|
|
23908
|
+
return normalizeComparablePath(candidate).startsWith(`${normalizedRoot}/`);
|
|
23714
23909
|
}
|
|
23715
23910
|
function basename(filePath) {
|
|
23716
23911
|
return trimTrailingSlashes$1(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
|
|
23717
23912
|
}
|
|
23718
|
-
function
|
|
23719
|
-
|
|
23720
|
-
const
|
|
23721
|
-
|
|
23722
|
-
return
|
|
23723
|
-
}
|
|
23724
|
-
|
|
23725
|
-
|
|
23726
|
-
|
|
23727
|
-
|
|
23728
|
-
|
|
23729
|
-
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
|
|
23742
|
-
|
|
23743
|
-
|
|
23744
|
-
|
|
23745
|
-
}
|
|
23746
|
-
if (!await fs.exists(outputDir)) {
|
|
23747
|
-
await fs.mkdir(outputDir);
|
|
23748
|
-
return;
|
|
23749
|
-
}
|
|
23913
|
+
function normalizeRestoreOutputDir(output) {
|
|
23914
|
+
const normalized = trimTrailingSlashes$1(output);
|
|
23915
|
+
const name = basename(normalized);
|
|
23916
|
+
if (!normalized || /\.fairy$/i.test(normalized) || !name || name === "." || name === ".." || /^[a-z]:$/iu.test(name)) throw new Error("restore: Output must be a non-root project directory, not a .fairy file.");
|
|
23917
|
+
return normalized;
|
|
23918
|
+
}
|
|
23919
|
+
function resolveOutputProjectPath(outputDir, fs) {
|
|
23920
|
+
return fs.join(outputDir, `${basename(outputDir)}.fairy`);
|
|
23921
|
+
}
|
|
23922
|
+
async function resolvePathForContainment(filePath, fs) {
|
|
23923
|
+
const missingSegments = [];
|
|
23924
|
+
let existingPath = filePath;
|
|
23925
|
+
while (!await fs.exists(existingPath)) {
|
|
23926
|
+
const parentPath = fs.dirname(existingPath);
|
|
23927
|
+
if (!parentPath || parentPath === existingPath) return Promise.resolve(fs.resolvePath(filePath));
|
|
23928
|
+
missingSegments.unshift(basename(existingPath));
|
|
23929
|
+
existingPath = parentPath;
|
|
23930
|
+
}
|
|
23931
|
+
const resolvedExistingPath = await Promise.resolve(fs.resolvePath(existingPath));
|
|
23932
|
+
return missingSegments.reduce((resolvedPath, segment) => fs.join(resolvedPath, segment), resolvedExistingPath);
|
|
23933
|
+
}
|
|
23934
|
+
async function assertRestoreOutputDir(inputDir, outputDir, fs, force) {
|
|
23935
|
+
const [resolvedInputDir, resolvedOutputDir] = await Promise.all([resolvePathForContainment(inputDir, fs), resolvePathForContainment(outputDir, fs)]);
|
|
23936
|
+
const normalizedInputDir = normalizeComparablePath(resolvedInputDir);
|
|
23937
|
+
const normalizedOutputDir = normalizeComparablePath(resolvedOutputDir);
|
|
23938
|
+
if (normalizedInputDir === normalizedOutputDir || isPathWithin(normalizedInputDir, normalizedOutputDir) || isPathWithin(normalizedOutputDir, normalizedInputDir)) throw new Error("Restore output directory must be independent from the published input directory.");
|
|
23939
|
+
if (!await fs.exists(outputDir)) return;
|
|
23750
23940
|
let entries;
|
|
23751
23941
|
try {
|
|
23752
23942
|
entries = await fs.readdir(outputDir);
|
|
@@ -23755,23 +23945,63 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
|
|
|
23755
23945
|
}
|
|
23756
23946
|
if (entries.length === 0) return;
|
|
23757
23947
|
if (!force) throw new Error(`Restore output directory is not empty: ${outputDir}. Use --force to overwrite it.`);
|
|
23758
|
-
|
|
23759
|
-
|
|
23760
|
-
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
23948
|
+
}
|
|
23949
|
+
async function createRestoreStagingDir(outputDir, fs) {
|
|
23950
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
23951
|
+
await fs.mkdir(parentDir);
|
|
23952
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
23953
|
+
const stagingDir = fs.join(parentDir, `.${basename(outputDir)}.restore-${generateId()}`);
|
|
23954
|
+
if (await fs.exists(stagingDir)) continue;
|
|
23955
|
+
await fs.mkdir(stagingDir);
|
|
23956
|
+
return stagingDir;
|
|
23957
|
+
}
|
|
23958
|
+
throw new Error(`restore: Could not allocate a staging directory beside ${outputDir}.`);
|
|
23959
|
+
}
|
|
23960
|
+
async function commitRestoreOutput(stagingDir, outputDir, fs) {
|
|
23961
|
+
if (!await fs.exists(outputDir)) {
|
|
23962
|
+
await fs.rename(stagingDir, outputDir);
|
|
23963
|
+
return null;
|
|
23964
|
+
}
|
|
23965
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
23966
|
+
let backupDir = "";
|
|
23967
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
23968
|
+
const candidate = fs.join(parentDir, `.${basename(outputDir)}.restore-backup-${generateId()}`);
|
|
23969
|
+
if (!await fs.exists(candidate)) {
|
|
23970
|
+
backupDir = candidate;
|
|
23971
|
+
break;
|
|
23972
|
+
}
|
|
23973
|
+
}
|
|
23974
|
+
if (!backupDir) throw new Error(`restore: Could not allocate a backup directory beside ${outputDir}.`);
|
|
23975
|
+
await fs.rename(outputDir, backupDir);
|
|
23976
|
+
try {
|
|
23977
|
+
await fs.rename(stagingDir, outputDir);
|
|
23978
|
+
} catch (error) {
|
|
23979
|
+
await fs.rename(backupDir, outputDir);
|
|
23980
|
+
throw error;
|
|
23981
|
+
}
|
|
23982
|
+
try {
|
|
23983
|
+
await fs.rm(backupDir, {
|
|
23984
|
+
recursive: true,
|
|
23985
|
+
force: true
|
|
23986
|
+
});
|
|
23987
|
+
return null;
|
|
23988
|
+
} catch {
|
|
23989
|
+
return `restore: Previous output retained at ${backupDir}; remove it after checking the restored project.`;
|
|
23990
|
+
}
|
|
23764
23991
|
}
|
|
23765
23992
|
async function restore(options) {
|
|
23766
23993
|
const sourceDir = trimTrailingSlashes$1(options.inputDir);
|
|
23767
|
-
const
|
|
23768
|
-
const outputProjectPath = resolveOutputProjectPath(
|
|
23769
|
-
await
|
|
23994
|
+
const outputDir = normalizeRestoreOutputDir(options.output);
|
|
23995
|
+
const outputProjectPath = resolveOutputProjectPath(outputDir, options.fs);
|
|
23996
|
+
await assertRestoreOutputDir(sourceDir, outputDir, options.fs, options.force === true);
|
|
23770
23997
|
const packageFilter = options.packages?.length ? new Set(options.packages) : null;
|
|
23771
|
-
const
|
|
23998
|
+
const binaryNames = (await options.fs.readdir(sourceDir)).filter((name) => isPublishedBinaryFile(name)).filter((name) => !packageFilter || packageFilter.has(inferPackageName(name)));
|
|
23999
|
+
for (const binaryName of binaryNames) assertSafeRestoreSegment(binaryName, "published binary file name");
|
|
24000
|
+
const candidateBinaryPaths = binaryNames.map((name) => options.fs.join(sourceDir, name)).sort((left, right) => left.localeCompare(right));
|
|
23772
24001
|
const binaryPaths = (await Promise.all(candidateBinaryPaths.map(async (filePath) => await options.fs.isFile(filePath) ? filePath : null))).filter((filePath) => !!filePath).sort((left, right) => left.localeCompare(right));
|
|
23773
24002
|
if (binaryPaths.length === 0) throw new Error(`No FairyGUI published binary files found in ${sourceDir}.`);
|
|
23774
|
-
|
|
24003
|
+
const restorer = new RestoreWorkflow(options.fs);
|
|
24004
|
+
const document = await restorer.prepare({
|
|
23775
24005
|
binaryPaths,
|
|
23776
24006
|
sourceDir,
|
|
23777
24007
|
outputProjectPath,
|
|
@@ -23779,18 +24009,45 @@ async function restore(options) {
|
|
|
23779
24009
|
cropImage: options.cropImage,
|
|
23780
24010
|
extractImage: options.extractImage
|
|
23781
24011
|
});
|
|
24012
|
+
const stagingDir = await createRestoreStagingDir(outputDir, options.fs);
|
|
24013
|
+
const stagingProjectPath = options.fs.join(stagingDir, basename(outputProjectPath));
|
|
24014
|
+
const warnings = [];
|
|
24015
|
+
try {
|
|
24016
|
+
await restorer.write(document, {
|
|
24017
|
+
binaryPaths,
|
|
24018
|
+
sourceDir,
|
|
24019
|
+
outputProjectPath: stagingProjectPath,
|
|
24020
|
+
projectType: options.projectType,
|
|
24021
|
+
cropImage: options.cropImage,
|
|
24022
|
+
extractImage: options.extractImage
|
|
24023
|
+
}, warnings);
|
|
24024
|
+
const cleanupWarning = await commitRestoreOutput(stagingDir, outputDir, options.fs);
|
|
24025
|
+
if (cleanupWarning) warnings.push(cleanupWarning);
|
|
24026
|
+
} catch (error) {
|
|
24027
|
+
await options.fs.rm(stagingDir, {
|
|
24028
|
+
recursive: true,
|
|
24029
|
+
force: true
|
|
24030
|
+
}).catch(() => void 0);
|
|
24031
|
+
throw error;
|
|
24032
|
+
}
|
|
24033
|
+
return {
|
|
24034
|
+
document,
|
|
24035
|
+
projectPath: outputProjectPath,
|
|
24036
|
+
warnings
|
|
24037
|
+
};
|
|
23782
24038
|
}
|
|
23783
24039
|
var RestoreWorkflow = class {
|
|
23784
24040
|
_fs;
|
|
23785
24041
|
constructor(fs) {
|
|
23786
24042
|
this._fs = fs;
|
|
23787
24043
|
}
|
|
23788
|
-
async
|
|
23789
|
-
const warnings = [];
|
|
24044
|
+
async prepare(options) {
|
|
23790
24045
|
const doc = await new BinaryReader(this._fs).readMany(options.binaryPaths);
|
|
24046
|
+
this._assertDocumentPaths(doc);
|
|
23791
24047
|
this._initializeProjectDefaults(doc, options.projectType);
|
|
23792
24048
|
this._initializeImageFileNames(doc);
|
|
23793
24049
|
this._initializeLooseResourceFileNames(doc);
|
|
24050
|
+
this._assertDocumentPaths(doc);
|
|
23794
24051
|
await this._synthesizeLooseSkeletonResources(doc, options.sourceDir);
|
|
23795
24052
|
this._initializeRestoredResourceRelations(doc);
|
|
23796
24053
|
this._initializePublishedFontTextureIds(doc);
|
|
@@ -23799,13 +24056,27 @@ var RestoreWorkflow = class {
|
|
|
23799
24056
|
this._initializePublishedTextFontResources(doc);
|
|
23800
24057
|
this._initializeDisplayObjectFileNames(doc);
|
|
23801
24058
|
this._initializePublishedFontDefaults(doc);
|
|
24059
|
+
this._assertDocumentPaths(doc);
|
|
24060
|
+
return doc;
|
|
24061
|
+
}
|
|
24062
|
+
async write(doc, options, warnings) {
|
|
23802
24063
|
await new ProjectWriter(this._fs).write(doc, options.outputProjectPath);
|
|
23803
24064
|
await this._restoreAssets(doc, options, warnings);
|
|
23804
|
-
|
|
23805
|
-
|
|
23806
|
-
|
|
23807
|
-
|
|
23808
|
-
|
|
24065
|
+
}
|
|
24066
|
+
_assertDocumentPaths(doc) {
|
|
24067
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
24068
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
24069
|
+
assertSafeRestoreSegment(pkg.getPublishName() || pkg.getName(), "package publish name");
|
|
24070
|
+
for (const resource of pkg.listResources()) {
|
|
24071
|
+
normalizeVirtualPath(resource.getPath?.());
|
|
24072
|
+
const branch = resource.getBranch?.() ?? "";
|
|
24073
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
24074
|
+
const fileName = resourceFileName(resource);
|
|
24075
|
+
if (fileName) assertSafeRestoreSegment(fileName, "resource file name");
|
|
24076
|
+
const publishedFileName = resourcePublishedFileName(resource);
|
|
24077
|
+
if (publishedFileName) assertSafeRestoreSegment(publishedFileName, "published resource file name");
|
|
24078
|
+
}
|
|
24079
|
+
}
|
|
23809
24080
|
}
|
|
23810
24081
|
_initializeProjectDefaults(doc, projectType) {
|
|
23811
24082
|
doc.getRoot().setProjectId(generateId()).setProjectType(projectType ?? ProjectType.Unity).setVersion("3.0").setSettings({
|
|
@@ -24239,27 +24510,40 @@ var RestoreWorkflow = class {
|
|
|
24239
24510
|
}
|
|
24240
24511
|
_sourceFileCandidates(pkg, fileName, outputFileName = fileName) {
|
|
24241
24512
|
const publishName = pkg.getPublishName() || pkg.getName();
|
|
24242
|
-
|
|
24513
|
+
assertSafeRestoreSegment(publishName, "package publish name");
|
|
24514
|
+
assertSafeRestoreSegment(fileName, "published source file name");
|
|
24515
|
+
assertSafeRestoreSegment(outputFileName, "published source file name");
|
|
24516
|
+
const candidates = Array.from(new Set([
|
|
24243
24517
|
`${publishName}_${fileName}`,
|
|
24244
24518
|
fileName,
|
|
24245
24519
|
`${publishName}_${outputFileName}`,
|
|
24246
24520
|
outputFileName
|
|
24247
24521
|
]));
|
|
24522
|
+
for (const candidate of candidates) assertSafeRestoreSegment(candidate, "published source file name");
|
|
24523
|
+
return candidates;
|
|
24248
24524
|
}
|
|
24249
24525
|
async _resolveLooseSourceFile(pkg, sourceDir, outputFileName) {
|
|
24250
24526
|
const candidates = outputFileName.endsWith(".atlas") ? this._sourceFileCandidates(pkg, `${outputFileName}.txt`, outputFileName) : outputFileName.endsWith(".skel") ? this._sourceFileCandidates(pkg, `${outputFileName}.bytes`, outputFileName) : this._sourceFileCandidates(pkg, outputFileName);
|
|
24251
24527
|
return this._resolveSourceFile(sourceDir, candidates);
|
|
24252
24528
|
}
|
|
24253
24529
|
async _resolveSourceFile(sourceDir, candidates) {
|
|
24530
|
+
const resolvedSourceDir = await Promise.resolve(this._fs.resolvePath(sourceDir));
|
|
24254
24531
|
for (const candidate of candidates) {
|
|
24532
|
+
assertSafeRestoreSegment(candidate, "published source file name");
|
|
24255
24533
|
const sourcePath = this._fs.join(sourceDir, candidate);
|
|
24256
|
-
if (await this._fs.isFile(sourcePath))
|
|
24534
|
+
if (!await this._fs.isFile(sourcePath)) continue;
|
|
24535
|
+
const resolvedSourcePath = await Promise.resolve(this._fs.resolvePath(sourcePath));
|
|
24536
|
+
if (!isPathWithin(resolvedSourceDir, resolvedSourcePath)) throw new Error(`restore: Published source file resolves outside the input directory: ${candidate}.`);
|
|
24537
|
+
return resolvedSourcePath;
|
|
24257
24538
|
}
|
|
24258
24539
|
return null;
|
|
24259
24540
|
}
|
|
24260
24541
|
_resourceOutputPath(outputProjectPath, pkg, resource, fileName) {
|
|
24261
24542
|
const basePath = this._fs.dirname(outputProjectPath);
|
|
24262
24543
|
const branch = resource.getBranch?.() ?? "";
|
|
24544
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
24545
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
24546
|
+
assertSafeRestoreSegment(fileName, "resource file name");
|
|
24263
24547
|
const assetsDir = branch ? `assets_${branch}` : "assets";
|
|
24264
24548
|
const virtualPath = normalizeVirtualPath(resource.getPath?.());
|
|
24265
24549
|
const pkgDir = this._fs.join(basePath, assetsDir, pkg.getName());
|
|
@@ -24826,13 +25110,13 @@ function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
|
|
|
24826
25110
|
}
|
|
24827
25111
|
return imageIds;
|
|
24828
25112
|
}
|
|
24829
|
-
async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw
|
|
25113
|
+
async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw) {
|
|
24830
25114
|
const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
|
|
24831
25115
|
if (publishedResourceIds.size === 0) return;
|
|
24832
25116
|
if (!basePath || !readFileRaw) {
|
|
24833
25117
|
if (pkg.listResources().some((resource) => {
|
|
24834
25118
|
return isSoundResource(resource) && publishedResourceIds.has(resource.getId());
|
|
24835
|
-
}))
|
|
25119
|
+
})) throw new Error(`publish: Sound resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
|
|
24836
25120
|
return;
|
|
24837
25121
|
}
|
|
24838
25122
|
for (const resource of pkg.listResources()) {
|
|
@@ -24845,18 +25129,18 @@ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, lo
|
|
|
24845
25129
|
const data = await readFileRaw(sourcePath);
|
|
24846
25130
|
await fs.writeFileRaw(targetPath, data);
|
|
24847
25131
|
} catch {
|
|
24848
|
-
|
|
25132
|
+
throw new Error(`publish: Could not export sound "${resource.getId()}" from package "${pkg.getName()}".`);
|
|
24849
25133
|
}
|
|
24850
25134
|
}
|
|
24851
25135
|
}
|
|
24852
|
-
async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw
|
|
25136
|
+
async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw) {
|
|
24853
25137
|
const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
|
|
24854
25138
|
const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
|
|
24855
25139
|
if (exportedResourceIds.size === 0) return;
|
|
24856
25140
|
if (!basePath || !readFileRaw) {
|
|
24857
25141
|
if (pkg.listResources().some((resource) => {
|
|
24858
25142
|
return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
|
|
24859
|
-
}))
|
|
25143
|
+
})) throw new Error(`publish: External resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
|
|
24860
25144
|
return;
|
|
24861
25145
|
}
|
|
24862
25146
|
for (const resource of pkg.listResources()) {
|
|
@@ -24878,7 +25162,7 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
|
|
|
24878
25162
|
const data = await readFileRaw(sourcePath);
|
|
24879
25163
|
await fs.writeFileRaw(targetPath, data);
|
|
24880
25164
|
} catch {
|
|
24881
|
-
|
|
25165
|
+
throw new Error(`publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`);
|
|
24882
25166
|
}
|
|
24883
25167
|
}
|
|
24884
25168
|
}
|
|
@@ -24894,18 +25178,17 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
|
|
|
24894
25178
|
* `publishNode()` or `publishBrowser()` through their dedicated entries.
|
|
24895
25179
|
*
|
|
24896
25180
|
* ```ts
|
|
24897
|
-
* import
|
|
24898
|
-
*
|
|
24899
|
-
* const doc = await
|
|
25181
|
+
* import { NodeIO } from '@openfairygui/core/node';
|
|
25182
|
+
* import { publishNode } from '@openfairygui/functions/node';
|
|
25183
|
+
* const doc = await new NodeIO().readProject('./project.fairy');
|
|
24900
25184
|
*
|
|
24901
|
-
* await
|
|
25185
|
+
* await publishNode({
|
|
25186
|
+
* document: doc,
|
|
24902
25187
|
* output: './release/',
|
|
24903
25188
|
* compressed: true,
|
|
24904
|
-
*
|
|
24905
|
-
* basePath: './assets/',
|
|
25189
|
+
* assetsPath: './assets/',
|
|
24906
25190
|
* fileExtension: 'bytes',
|
|
24907
|
-
*
|
|
24908
|
-
* }));
|
|
25191
|
+
* });
|
|
24909
25192
|
* ```
|
|
24910
25193
|
*/
|
|
24911
25194
|
function publish(options) {
|
|
@@ -24973,6 +25256,12 @@ function publish(options) {
|
|
|
24973
25256
|
}
|
|
24974
25257
|
});
|
|
24975
25258
|
const publishPackage = async (plan, writerFs, packageIndex) => {
|
|
25259
|
+
if (options.fs && !plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
|
|
25260
|
+
if (options.fs) {
|
|
25261
|
+
await options.fs.mkdir(plan.outputDir);
|
|
25262
|
+
await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
|
|
25263
|
+
await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
|
|
25264
|
+
}
|
|
24976
25265
|
const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
|
|
24977
25266
|
await atlas({
|
|
24978
25267
|
...plan.atlas,
|
|
@@ -24983,20 +25272,17 @@ function publish(options) {
|
|
|
24983
25272
|
outputPath: options.fs ? plan.outputDir : void 0,
|
|
24984
25273
|
mkdir: options.fs ? options.fs.mkdir : void 0,
|
|
24985
25274
|
readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
|
|
25275
|
+
strictOutput: options.fs !== void 0,
|
|
24986
25276
|
packages: [plan.pkg.getName()],
|
|
24987
25277
|
...atlasRuntimeOptions
|
|
24988
25278
|
})(doc);
|
|
24989
25279
|
if (!options.fs) return;
|
|
24990
|
-
if (!plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
|
|
24991
|
-
await options.fs.mkdir(plan.outputDir);
|
|
24992
25280
|
const filePath = options.fs.join(plan.outputDir, plan.fileName);
|
|
24993
25281
|
const bwOptions = {
|
|
24994
25282
|
compressed: plan.compressed,
|
|
24995
25283
|
packageIndex
|
|
24996
25284
|
};
|
|
24997
25285
|
await new BinaryWriter(writerFs).write(doc, filePath, bwOptions);
|
|
24998
|
-
await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
|
|
24999
|
-
await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
|
|
25000
25286
|
logger.info(`publish: Written ${plan.fileName}`);
|
|
25001
25287
|
};
|
|
25002
25288
|
const root = doc.getRoot();
|
|
@@ -25029,7 +25315,9 @@ function publish(options) {
|
|
|
25029
25315
|
}
|
|
25030
25316
|
const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
|
|
25031
25317
|
if (!options.fs) {
|
|
25032
|
-
|
|
25318
|
+
const outputPlan = plans.find((plan) => !!plan.outputDir);
|
|
25319
|
+
if (outputPlan) throw new Error(`publish: Output for package "${outputPlan.pkg.getName()}" requires a filesystem. Omit output and publish paths to run a layout-only transform.`);
|
|
25320
|
+
logger.info(`publish: Layout computed for ${allPackages.length} package(s); no output directory was requested.`);
|
|
25033
25321
|
const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
|
|
25034
25322
|
for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
|
|
25035
25323
|
await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
|
|
@@ -25216,6 +25504,9 @@ var NodeIO = class extends PlatformIO {
|
|
|
25216
25504
|
return false;
|
|
25217
25505
|
}
|
|
25218
25506
|
},
|
|
25507
|
+
async unlink(filePath) {
|
|
25508
|
+
await fs$1.unlink(filePath);
|
|
25509
|
+
},
|
|
25219
25510
|
join(...paths) {
|
|
25220
25511
|
return path$1.join(...paths);
|
|
25221
25512
|
},
|
|
@@ -25358,8 +25649,8 @@ async function resolveNodeAssetsPath(document, assetsPath) {
|
|
|
25358
25649
|
}
|
|
25359
25650
|
async function loadSharpBackend() {
|
|
25360
25651
|
try {
|
|
25361
|
-
const
|
|
25362
|
-
return
|
|
25652
|
+
const loaded = await importNative("sharp");
|
|
25653
|
+
return loaded.default ?? loaded;
|
|
25363
25654
|
} catch {
|
|
25364
25655
|
return;
|
|
25365
25656
|
}
|
|
@@ -25380,7 +25671,7 @@ async function publishNode(options) {
|
|
|
25380
25671
|
const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
|
|
25381
25672
|
const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
|
|
25382
25673
|
const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
|
|
25383
|
-
if (!encoder)
|
|
25674
|
+
if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
|
|
25384
25675
|
await document.transform(publish({
|
|
25385
25676
|
...publishOptions,
|
|
25386
25677
|
basePath: assetsPath,
|
|
@@ -25456,7 +25747,7 @@ function registerPublishCommand(program) {
|
|
|
25456
25747
|
//#endregion
|
|
25457
25748
|
//#region src/commands/restore.ts
|
|
25458
25749
|
function registerRestoreCommand(program) {
|
|
25459
|
-
program.command("restore").description("
|
|
25750
|
+
program.command("restore").description("Recover a project directory from trusted local published artifacts").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", "Replace a non-empty output directory only after a complete staged restore").option("-t, --project-type <name|id>", "Override restored project type; default is unity").action(async (releaseDir, options) => {
|
|
25460
25751
|
const inputDir = path.resolve(releaseDir);
|
|
25461
25752
|
const outputDir = path.resolve(options.output);
|
|
25462
25753
|
const pkgFilter = options.packages ? options.packages.split(",").map((value) => value.trim()).filter(Boolean) : void 0;
|
|
@@ -25530,6 +25821,9 @@ function createNodeRestoreFs() {
|
|
|
25530
25821
|
force: options?.force ?? false
|
|
25531
25822
|
});
|
|
25532
25823
|
},
|
|
25824
|
+
async rename(from, to) {
|
|
25825
|
+
await fs.rename(from, to);
|
|
25826
|
+
},
|
|
25533
25827
|
join(...paths) {
|
|
25534
25828
|
return path.join(...paths);
|
|
25535
25829
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfairygui/cli",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.15",
|
|
4
4
|
"description": "FairyGUI Headless Authoring SDK — command-line interface.",
|
|
5
5
|
"author": "OpenFairyGUI Contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"restore"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@openfairygui/
|
|
37
|
-
"@openfairygui/
|
|
36
|
+
"@openfairygui/functions": "0.2.0-alpha.15",
|
|
37
|
+
"@openfairygui/core": "0.2.0-alpha.15"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"commander": "^14.0.2",
|
|
41
41
|
"jiti": "^2.7.0",
|
|
42
|
-
"@openfairygui/backend": "0.2.0-alpha.
|
|
42
|
+
"@openfairygui/backend": "0.2.0-alpha.15"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
45
|
"sharp": ">=0.33.0"
|
package/src/commands/restore.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import {
|
|
3
|
-
restore,
|
|
4
4
|
type RestoreFileSystem,
|
|
5
5
|
type RestoreImageCropInput,
|
|
6
6
|
type RestoreImageCropper,
|
|
7
7
|
type RestoreImageExtractInput,
|
|
8
8
|
type RestoreImageExtractor,
|
|
9
|
+
restore,
|
|
9
10
|
} from '@openfairygui/functions';
|
|
10
|
-
import
|
|
11
|
-
import path from 'node:path';
|
|
11
|
+
import type { Command } from 'commander';
|
|
12
12
|
import { parseProjectType } from '../utils/project-type.js';
|
|
13
13
|
|
|
14
14
|
type RestoreCommandOptions = {
|
|
@@ -26,11 +26,11 @@ interface RestoreImageProcessors {
|
|
|
26
26
|
export function registerRestoreCommand(program: Command): void {
|
|
27
27
|
program
|
|
28
28
|
.command('restore')
|
|
29
|
-
.description('
|
|
29
|
+
.description('Recover a project directory from trusted local published artifacts')
|
|
30
30
|
.argument('<release-dir>', 'Published release directory')
|
|
31
31
|
.requiredOption('-o, --output <dir>', 'Output project directory')
|
|
32
32
|
.option('-p, --packages <a,b,c>', 'Only restore specific packages (comma-separated)')
|
|
33
|
-
.option('-f, --force', '
|
|
33
|
+
.option('-f, --force', 'Replace a non-empty output directory only after a complete staged restore')
|
|
34
34
|
.option('-t, --project-type <name|id>', 'Override restored project type; default is unity')
|
|
35
35
|
.action(async (releaseDir: string, options: RestoreCommandOptions) => {
|
|
36
36
|
const inputDir = path.resolve(releaseDir);
|
|
@@ -113,6 +113,9 @@ function createNodeRestoreFs(): RestoreFileSystem {
|
|
|
113
113
|
async rm(targetPath: string, options?: { recursive?: boolean; force?: boolean }): Promise<void> {
|
|
114
114
|
await fs.rm(targetPath, { recursive: options?.recursive ?? false, force: options?.force ?? false });
|
|
115
115
|
},
|
|
116
|
+
async rename(from: string, to: string): Promise<void> {
|
|
117
|
+
await fs.rename(from, to);
|
|
118
|
+
},
|
|
116
119
|
join(...paths: string[]): string {
|
|
117
120
|
return path.join(...paths);
|
|
118
121
|
},
|