@openfairygui/functions 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 +12 -4
- package/dist/{codegen-C7PPZrB3.d.cts → codegen-B8ZM1F4j.d.cts} +12 -11
- package/dist/{codegen-CEnmtjBP.d.ts → codegen-CfbDHuFt.d.ts} +12 -11
- package/dist/index.cjs +152 -56
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +152 -56
- package/dist/node.cjs +4 -4
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +4 -4
- package/dist/{publish-CtCXsMdj.cjs → publish-D7268_u4.cjs} +59 -32
- package/dist/{publish-Bl-GK9kt.js → publish-DsPK0SJ1.js} +59 -32
- package/dist/web.cjs +1 -1
- package/dist/web.js +1 -1
- package/package.json +2 -2
- package/src/adapters/node/publish.ts +6 -5
- package/src/adapters/web/publish.ts +1 -0
- package/src/atlas.ts +80 -17
- package/src/publish.ts +57 -49
- package/src/restore.ts +177 -68
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as decodeText, c as resolvePackageCodegenPlan, d as atlas, f as createTransform, i as buildCodegenClasses, n as resolvePublishOptions, o as encodeText, r as AUTO_GENERATED_CODE_MARK, s as publishCodeGeneration, t as publish } from "./publish-
|
|
1
|
+
import { a as decodeText, c as resolvePackageCodegenPlan, d as atlas, f as createTransform, i as buildCodegenClasses, n as resolvePublishOptions, o as encodeText, r as AUTO_GENERATED_CODE_MARK, s as publishCodeGeneration, t as publish } from "./publish-DsPK0SJ1.js";
|
|
2
2
|
import { applyUamTransactionApp } from "./uam-transaction.js";
|
|
3
3
|
import { BinaryReader, ProjectType, ProjectWriter, generateId } from "@openfairygui/core";
|
|
4
4
|
//#region src/inspect.ts
|
|
@@ -392,10 +392,16 @@ const TRANSPARENT_PNG_1X1 = Uint8Array.from([
|
|
|
392
392
|
96,
|
|
393
393
|
130
|
|
394
394
|
]);
|
|
395
|
+
function assertSafeRestoreSegment(value, label) {
|
|
396
|
+
if (!value || value === "." || value === ".." || value.includes("\0") || /[\\/:]/u.test(value)) throw new Error(`restore: Invalid ${label} "${value}".`);
|
|
397
|
+
}
|
|
395
398
|
function normalizeVirtualPath(path) {
|
|
396
|
-
const
|
|
397
|
-
if (!
|
|
398
|
-
|
|
399
|
+
const raw = (path ?? "").trim();
|
|
400
|
+
if (!raw || raw === "/") return "";
|
|
401
|
+
if (raw.includes("\0") || raw.startsWith("\\") || raw.startsWith("//") || /^[a-z]:/iu.test(raw)) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
402
|
+
const segments = raw.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
403
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":"))) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
404
|
+
return segments.join("/");
|
|
399
405
|
}
|
|
400
406
|
function resourceFileName(resource) {
|
|
401
407
|
return resource.getFileName?.() || resource.getFile?.() || resource.getName?.() || "";
|
|
@@ -579,44 +585,40 @@ function normalizeComparablePath(value) {
|
|
|
579
585
|
const joined = segments.join("/");
|
|
580
586
|
return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
|
|
581
587
|
}
|
|
582
|
-
function
|
|
583
|
-
|
|
588
|
+
function isPathWithin(root, candidate) {
|
|
589
|
+
const normalizedRoot = normalizeComparablePath(root);
|
|
590
|
+
return normalizeComparablePath(candidate).startsWith(`${normalizedRoot}/`);
|
|
584
591
|
}
|
|
585
592
|
function basename(filePath) {
|
|
586
593
|
return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
|
|
587
594
|
}
|
|
588
|
-
function
|
|
589
|
-
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
return
|
|
595
|
+
function normalizeRestoreOutputDir(output) {
|
|
596
|
+
const normalized = trimTrailingSlashes(output);
|
|
597
|
+
const name = basename(normalized);
|
|
598
|
+
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.");
|
|
599
|
+
return normalized;
|
|
593
600
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
throw new Error(`Restore output path is not a directory: ${outputDir}`);
|
|
606
|
-
}
|
|
607
|
-
if (!await fs.exists(outputProjectPath)) return;
|
|
608
|
-
if (!force) throw new Error(`Restore output file already exists: ${outputProjectPath}. Use --force to overwrite it.`);
|
|
609
|
-
if (!fs.rm) throw new Error("Restore output file already exists and the provided fs does not support rm(...).");
|
|
610
|
-
await fs.rm(outputProjectPath, {
|
|
611
|
-
recursive: true,
|
|
612
|
-
force: true
|
|
613
|
-
});
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
if (!await fs.exists(outputDir)) {
|
|
617
|
-
await fs.mkdir(outputDir);
|
|
618
|
-
return;
|
|
601
|
+
function resolveOutputProjectPath(outputDir, fs) {
|
|
602
|
+
return fs.join(outputDir, `${basename(outputDir)}.fairy`);
|
|
603
|
+
}
|
|
604
|
+
async function resolvePathForContainment(filePath, fs) {
|
|
605
|
+
const missingSegments = [];
|
|
606
|
+
let existingPath = filePath;
|
|
607
|
+
while (!await fs.exists(existingPath)) {
|
|
608
|
+
const parentPath = fs.dirname(existingPath);
|
|
609
|
+
if (!parentPath || parentPath === existingPath) return Promise.resolve(fs.resolvePath(filePath));
|
|
610
|
+
missingSegments.unshift(basename(existingPath));
|
|
611
|
+
existingPath = parentPath;
|
|
619
612
|
}
|
|
613
|
+
const resolvedExistingPath = await Promise.resolve(fs.resolvePath(existingPath));
|
|
614
|
+
return missingSegments.reduce((resolvedPath, segment) => fs.join(resolvedPath, segment), resolvedExistingPath);
|
|
615
|
+
}
|
|
616
|
+
async function assertRestoreOutputDir(inputDir, outputDir, fs, force) {
|
|
617
|
+
const [resolvedInputDir, resolvedOutputDir] = await Promise.all([resolvePathForContainment(inputDir, fs), resolvePathForContainment(outputDir, fs)]);
|
|
618
|
+
const normalizedInputDir = normalizeComparablePath(resolvedInputDir);
|
|
619
|
+
const normalizedOutputDir = normalizeComparablePath(resolvedOutputDir);
|
|
620
|
+
if (normalizedInputDir === normalizedOutputDir || isPathWithin(normalizedInputDir, normalizedOutputDir) || isPathWithin(normalizedOutputDir, normalizedInputDir)) throw new Error("Restore output directory must be independent from the published input directory.");
|
|
621
|
+
if (!await fs.exists(outputDir)) return;
|
|
620
622
|
let entries;
|
|
621
623
|
try {
|
|
622
624
|
entries = await fs.readdir(outputDir);
|
|
@@ -625,23 +627,63 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
|
|
|
625
627
|
}
|
|
626
628
|
if (entries.length === 0) return;
|
|
627
629
|
if (!force) throw new Error(`Restore output directory is not empty: ${outputDir}. Use --force to overwrite it.`);
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
630
|
+
}
|
|
631
|
+
async function createRestoreStagingDir(outputDir, fs) {
|
|
632
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
633
|
+
await fs.mkdir(parentDir);
|
|
634
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
635
|
+
const stagingDir = fs.join(parentDir, `.${basename(outputDir)}.restore-${generateId()}`);
|
|
636
|
+
if (await fs.exists(stagingDir)) continue;
|
|
637
|
+
await fs.mkdir(stagingDir);
|
|
638
|
+
return stagingDir;
|
|
639
|
+
}
|
|
640
|
+
throw new Error(`restore: Could not allocate a staging directory beside ${outputDir}.`);
|
|
641
|
+
}
|
|
642
|
+
async function commitRestoreOutput(stagingDir, outputDir, fs) {
|
|
643
|
+
if (!await fs.exists(outputDir)) {
|
|
644
|
+
await fs.rename(stagingDir, outputDir);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
648
|
+
let backupDir = "";
|
|
649
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
650
|
+
const candidate = fs.join(parentDir, `.${basename(outputDir)}.restore-backup-${generateId()}`);
|
|
651
|
+
if (!await fs.exists(candidate)) {
|
|
652
|
+
backupDir = candidate;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (!backupDir) throw new Error(`restore: Could not allocate a backup directory beside ${outputDir}.`);
|
|
657
|
+
await fs.rename(outputDir, backupDir);
|
|
658
|
+
try {
|
|
659
|
+
await fs.rename(stagingDir, outputDir);
|
|
660
|
+
} catch (error) {
|
|
661
|
+
await fs.rename(backupDir, outputDir);
|
|
662
|
+
throw error;
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
await fs.rm(backupDir, {
|
|
666
|
+
recursive: true,
|
|
667
|
+
force: true
|
|
668
|
+
});
|
|
669
|
+
return null;
|
|
670
|
+
} catch {
|
|
671
|
+
return `restore: Previous output retained at ${backupDir}; remove it after checking the restored project.`;
|
|
672
|
+
}
|
|
634
673
|
}
|
|
635
674
|
async function restore(options) {
|
|
636
675
|
const sourceDir = trimTrailingSlashes(options.inputDir);
|
|
637
|
-
const
|
|
638
|
-
const outputProjectPath = resolveOutputProjectPath(
|
|
639
|
-
await
|
|
676
|
+
const outputDir = normalizeRestoreOutputDir(options.output);
|
|
677
|
+
const outputProjectPath = resolveOutputProjectPath(outputDir, options.fs);
|
|
678
|
+
await assertRestoreOutputDir(sourceDir, outputDir, options.fs, options.force === true);
|
|
640
679
|
const packageFilter = options.packages?.length ? new Set(options.packages) : null;
|
|
641
|
-
const
|
|
680
|
+
const binaryNames = (await options.fs.readdir(sourceDir)).filter((name) => isPublishedBinaryFile(name)).filter((name) => !packageFilter || packageFilter.has(inferPackageName(name)));
|
|
681
|
+
for (const binaryName of binaryNames) assertSafeRestoreSegment(binaryName, "published binary file name");
|
|
682
|
+
const candidateBinaryPaths = binaryNames.map((name) => options.fs.join(sourceDir, name)).sort((left, right) => left.localeCompare(right));
|
|
642
683
|
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));
|
|
643
684
|
if (binaryPaths.length === 0) throw new Error(`No FairyGUI published binary files found in ${sourceDir}.`);
|
|
644
|
-
|
|
685
|
+
const restorer = new RestoreWorkflow(options.fs);
|
|
686
|
+
const document = await restorer.prepare({
|
|
645
687
|
binaryPaths,
|
|
646
688
|
sourceDir,
|
|
647
689
|
outputProjectPath,
|
|
@@ -649,18 +691,45 @@ async function restore(options) {
|
|
|
649
691
|
cropImage: options.cropImage,
|
|
650
692
|
extractImage: options.extractImage
|
|
651
693
|
});
|
|
694
|
+
const stagingDir = await createRestoreStagingDir(outputDir, options.fs);
|
|
695
|
+
const stagingProjectPath = options.fs.join(stagingDir, basename(outputProjectPath));
|
|
696
|
+
const warnings = [];
|
|
697
|
+
try {
|
|
698
|
+
await restorer.write(document, {
|
|
699
|
+
binaryPaths,
|
|
700
|
+
sourceDir,
|
|
701
|
+
outputProjectPath: stagingProjectPath,
|
|
702
|
+
projectType: options.projectType,
|
|
703
|
+
cropImage: options.cropImage,
|
|
704
|
+
extractImage: options.extractImage
|
|
705
|
+
}, warnings);
|
|
706
|
+
const cleanupWarning = await commitRestoreOutput(stagingDir, outputDir, options.fs);
|
|
707
|
+
if (cleanupWarning) warnings.push(cleanupWarning);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
await options.fs.rm(stagingDir, {
|
|
710
|
+
recursive: true,
|
|
711
|
+
force: true
|
|
712
|
+
}).catch(() => void 0);
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
document,
|
|
717
|
+
projectPath: outputProjectPath,
|
|
718
|
+
warnings
|
|
719
|
+
};
|
|
652
720
|
}
|
|
653
721
|
var RestoreWorkflow = class {
|
|
654
722
|
_fs;
|
|
655
723
|
constructor(fs) {
|
|
656
724
|
this._fs = fs;
|
|
657
725
|
}
|
|
658
|
-
async
|
|
659
|
-
const warnings = [];
|
|
726
|
+
async prepare(options) {
|
|
660
727
|
const doc = await new BinaryReader(this._fs).readMany(options.binaryPaths);
|
|
728
|
+
this._assertDocumentPaths(doc);
|
|
661
729
|
this._initializeProjectDefaults(doc, options.projectType);
|
|
662
730
|
this._initializeImageFileNames(doc);
|
|
663
731
|
this._initializeLooseResourceFileNames(doc);
|
|
732
|
+
this._assertDocumentPaths(doc);
|
|
664
733
|
await this._synthesizeLooseSkeletonResources(doc, options.sourceDir);
|
|
665
734
|
this._initializeRestoredResourceRelations(doc);
|
|
666
735
|
this._initializePublishedFontTextureIds(doc);
|
|
@@ -669,13 +738,27 @@ var RestoreWorkflow = class {
|
|
|
669
738
|
this._initializePublishedTextFontResources(doc);
|
|
670
739
|
this._initializeDisplayObjectFileNames(doc);
|
|
671
740
|
this._initializePublishedFontDefaults(doc);
|
|
741
|
+
this._assertDocumentPaths(doc);
|
|
742
|
+
return doc;
|
|
743
|
+
}
|
|
744
|
+
async write(doc, options, warnings) {
|
|
672
745
|
await new ProjectWriter(this._fs).write(doc, options.outputProjectPath);
|
|
673
746
|
await this._restoreAssets(doc, options, warnings);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
747
|
+
}
|
|
748
|
+
_assertDocumentPaths(doc) {
|
|
749
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
750
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
751
|
+
assertSafeRestoreSegment(pkg.getPublishName() || pkg.getName(), "package publish name");
|
|
752
|
+
for (const resource of pkg.listResources()) {
|
|
753
|
+
normalizeVirtualPath(resource.getPath?.());
|
|
754
|
+
const branch = resource.getBranch?.() ?? "";
|
|
755
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
756
|
+
const fileName = resourceFileName(resource);
|
|
757
|
+
if (fileName) assertSafeRestoreSegment(fileName, "resource file name");
|
|
758
|
+
const publishedFileName = resourcePublishedFileName(resource);
|
|
759
|
+
if (publishedFileName) assertSafeRestoreSegment(publishedFileName, "published resource file name");
|
|
760
|
+
}
|
|
761
|
+
}
|
|
679
762
|
}
|
|
680
763
|
_initializeProjectDefaults(doc, projectType) {
|
|
681
764
|
doc.getRoot().setProjectId(generateId()).setProjectType(projectType ?? ProjectType.Unity).setVersion("3.0").setSettings({
|
|
@@ -1109,27 +1192,40 @@ var RestoreWorkflow = class {
|
|
|
1109
1192
|
}
|
|
1110
1193
|
_sourceFileCandidates(pkg, fileName, outputFileName = fileName) {
|
|
1111
1194
|
const publishName = pkg.getPublishName() || pkg.getName();
|
|
1112
|
-
|
|
1195
|
+
assertSafeRestoreSegment(publishName, "package publish name");
|
|
1196
|
+
assertSafeRestoreSegment(fileName, "published source file name");
|
|
1197
|
+
assertSafeRestoreSegment(outputFileName, "published source file name");
|
|
1198
|
+
const candidates = Array.from(new Set([
|
|
1113
1199
|
`${publishName}_${fileName}`,
|
|
1114
1200
|
fileName,
|
|
1115
1201
|
`${publishName}_${outputFileName}`,
|
|
1116
1202
|
outputFileName
|
|
1117
1203
|
]));
|
|
1204
|
+
for (const candidate of candidates) assertSafeRestoreSegment(candidate, "published source file name");
|
|
1205
|
+
return candidates;
|
|
1118
1206
|
}
|
|
1119
1207
|
async _resolveLooseSourceFile(pkg, sourceDir, outputFileName) {
|
|
1120
1208
|
const candidates = outputFileName.endsWith(".atlas") ? this._sourceFileCandidates(pkg, `${outputFileName}.txt`, outputFileName) : outputFileName.endsWith(".skel") ? this._sourceFileCandidates(pkg, `${outputFileName}.bytes`, outputFileName) : this._sourceFileCandidates(pkg, outputFileName);
|
|
1121
1209
|
return this._resolveSourceFile(sourceDir, candidates);
|
|
1122
1210
|
}
|
|
1123
1211
|
async _resolveSourceFile(sourceDir, candidates) {
|
|
1212
|
+
const resolvedSourceDir = await Promise.resolve(this._fs.resolvePath(sourceDir));
|
|
1124
1213
|
for (const candidate of candidates) {
|
|
1214
|
+
assertSafeRestoreSegment(candidate, "published source file name");
|
|
1125
1215
|
const sourcePath = this._fs.join(sourceDir, candidate);
|
|
1126
|
-
if (await this._fs.isFile(sourcePath))
|
|
1216
|
+
if (!await this._fs.isFile(sourcePath)) continue;
|
|
1217
|
+
const resolvedSourcePath = await Promise.resolve(this._fs.resolvePath(sourcePath));
|
|
1218
|
+
if (!isPathWithin(resolvedSourceDir, resolvedSourcePath)) throw new Error(`restore: Published source file resolves outside the input directory: ${candidate}.`);
|
|
1219
|
+
return resolvedSourcePath;
|
|
1127
1220
|
}
|
|
1128
1221
|
return null;
|
|
1129
1222
|
}
|
|
1130
1223
|
_resourceOutputPath(outputProjectPath, pkg, resource, fileName) {
|
|
1131
1224
|
const basePath = this._fs.dirname(outputProjectPath);
|
|
1132
1225
|
const branch = resource.getBranch?.() ?? "";
|
|
1226
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
1227
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
1228
|
+
assertSafeRestoreSegment(fileName, "resource file name");
|
|
1133
1229
|
const assetsDir = branch ? `assets_${branch}` : "assets";
|
|
1134
1230
|
const virtualPath = normalizeVirtualPath(resource.getPath?.());
|
|
1135
1231
|
const pkgDir = this._fs.join(basePath, assetsDir, pkg.getName());
|
package/dist/node.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_publish = require("./publish-
|
|
2
|
+
const require_publish = require("./publish-D7268_u4.cjs");
|
|
3
3
|
//#region src/adapters/node/plugins.ts
|
|
4
4
|
const importNative$1 = new Function("id", "return import(id)");
|
|
5
5
|
async function loadPlugins(doc, pluginsDir) {
|
|
@@ -89,8 +89,8 @@ async function resolveNodeAssetsPath(document, assetsPath) {
|
|
|
89
89
|
}
|
|
90
90
|
async function loadSharpBackend() {
|
|
91
91
|
try {
|
|
92
|
-
const
|
|
93
|
-
return
|
|
92
|
+
const loaded = await importNative("sharp");
|
|
93
|
+
return loaded.default ?? loaded;
|
|
94
94
|
} catch {
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
@@ -111,7 +111,7 @@ async function publishNode(options) {
|
|
|
111
111
|
const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
|
|
112
112
|
const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
|
|
113
113
|
const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
|
|
114
|
-
if (!encoder)
|
|
114
|
+
if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
|
|
115
115
|
await document.transform(require_publish.publish({
|
|
116
116
|
...publishOptions,
|
|
117
117
|
basePath: assetsPath,
|
package/dist/node.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as AtlasRasterBackend } from "./atlas-CHsu2Y8i.cjs";
|
|
2
|
-
import { O as PublishOptions, m as LoadedPlugin } from "./codegen-
|
|
2
|
+
import { O as PublishOptions, m as LoadedPlugin } from "./codegen-B8ZM1F4j.cjs";
|
|
3
3
|
import { Document } from "@openfairygui/core";
|
|
4
4
|
|
|
5
5
|
//#region src/adapters/node/publish.d.ts
|
package/dist/node.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as AtlasRasterBackend } from "./atlas-CDn6TirX.js";
|
|
2
|
-
import { O as PublishOptions, m as LoadedPlugin } from "./codegen-
|
|
2
|
+
import { O as PublishOptions, m as LoadedPlugin } from "./codegen-CfbDHuFt.js";
|
|
3
3
|
import { Document } from "@openfairygui/core";
|
|
4
4
|
|
|
5
5
|
//#region src/adapters/node/publish.d.ts
|
package/dist/node.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as resolveProjectBasePath, t as publish, u as formatPluginError } from "./publish-
|
|
1
|
+
import { l as resolveProjectBasePath, t as publish, u as formatPluginError } from "./publish-DsPK0SJ1.js";
|
|
2
2
|
//#region src/adapters/node/plugins.ts
|
|
3
3
|
const importNative$1 = new Function("id", "return import(id)");
|
|
4
4
|
async function loadPlugins(doc, pluginsDir) {
|
|
@@ -88,8 +88,8 @@ async function resolveNodeAssetsPath(document, assetsPath) {
|
|
|
88
88
|
}
|
|
89
89
|
async function loadSharpBackend() {
|
|
90
90
|
try {
|
|
91
|
-
const
|
|
92
|
-
return
|
|
91
|
+
const loaded = await importNative("sharp");
|
|
92
|
+
return loaded.default ?? loaded;
|
|
93
93
|
} catch {
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
@@ -110,7 +110,7 @@ async function publishNode(options) {
|
|
|
110
110
|
const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
|
|
111
111
|
const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
|
|
112
112
|
const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
|
|
113
|
-
if (!encoder)
|
|
113
|
+
if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
|
|
114
114
|
await document.transform(publish({
|
|
115
115
|
...publishOptions,
|
|
116
116
|
basePath: assetsPath,
|
|
@@ -751,7 +751,8 @@ const ATLAS_DEFAULTS = {
|
|
|
751
751
|
preserveInputOrderOnTie: false,
|
|
752
752
|
directSingleImageOutput: false,
|
|
753
753
|
extractAlpha: false,
|
|
754
|
-
separatedAtlasForBranch: false
|
|
754
|
+
separatedAtlasForBranch: false,
|
|
755
|
+
strictOutput: false
|
|
755
756
|
};
|
|
756
757
|
function getPublishedItemId(resource) {
|
|
757
758
|
return (resource.getExtras() ?? {})._publishedId ?? resource.getId();
|
|
@@ -908,8 +909,9 @@ function atlas(_options = {}) {
|
|
|
908
909
|
const packageFilter = options.packages ? new Set(options.packages) : null;
|
|
909
910
|
for (const pkg of root.listPackages()) {
|
|
910
911
|
if (packageFilter && !packageFilter.has(pkg.getName())) continue;
|
|
911
|
-
const
|
|
912
|
-
const
|
|
912
|
+
const publishedResourceIds = pkg.getExtras()?.publishedResourceIds;
|
|
913
|
+
const selectedPublishIds = new Set(publishedResourceIds);
|
|
914
|
+
const allResources = publishedResourceIds !== void 0 && (options.strictOutput || selectedPublishIds.size > 0) ? pkg.listResources().filter((resource) => selectedPublishIds.has(resource.getId())) : pkg.listResources();
|
|
913
915
|
const skeletonDependencyImageIds = getSelectedSkeletonDependencyImageIds(allResources);
|
|
914
916
|
const orderedResources = await resolveEditorCompatibleResourceOrder(pkg, allResources, options);
|
|
915
917
|
const orderedAllResources = sortResourcesByOrder(allResources, new Map(orderedResources.map((resource, index) => [resource.getId(), index])), new Map(allResources.map((resource, index) => [resource.getId(), index])));
|
|
@@ -998,6 +1000,7 @@ function atlas(_options = {}) {
|
|
|
998
1000
|
await _collectFontTexture(doc, res, pkg, options);
|
|
999
1001
|
}
|
|
1000
1002
|
if (inputs.length === 0) continue;
|
|
1003
|
+
if (options.strictOutput && (!encoder || !options.basePath || !options.outputPath)) throw new Error(`atlas: Package "${pkg.getName()}" requires encoder, basePath, and outputPath for complete raster output.`);
|
|
1001
1004
|
let totalPageCount = 0;
|
|
1002
1005
|
let usedDirectOutput = false;
|
|
1003
1006
|
const { autoInputs, fixedPageGroups, standaloneGroups, reservedPageIndexes } = groupStandaloneInputs(doc, inputs, options);
|
|
@@ -1095,7 +1098,7 @@ function reserveAutoPageStart(branchPageOffsets, branchOrdinal, reservedPageInde
|
|
|
1095
1098
|
async function emitPagedAtlasGroup(doc, pkg, allResources, inputs, context) {
|
|
1096
1099
|
if (inputs.length === 0) return 0;
|
|
1097
1100
|
const pages = packAtlasPages(inputs, context.options, context.forceSinglePage === true);
|
|
1098
|
-
|
|
1101
|
+
assertPackedInputCoverage(pages, inputs.length, `package "${pkg.getName()}"`);
|
|
1099
1102
|
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
1100
1103
|
const page = pages[pageOffset];
|
|
1101
1104
|
const pageIndex = context.pageStart + pageOffset;
|
|
@@ -1121,7 +1124,7 @@ async function emitStandaloneAtlasGroup(doc, pkg, group, context) {
|
|
|
1121
1124
|
multipleOfFour: true,
|
|
1122
1125
|
square: false
|
|
1123
1126
|
} : void 0);
|
|
1124
|
-
|
|
1127
|
+
assertPackedInputCoverage(pages, group.inputs.length, `standalone texture in package "${pkg.getName()}"`);
|
|
1125
1128
|
for (let pageOffset = 0; pageOffset < pages.length; pageOffset += 1) {
|
|
1126
1129
|
const page = pages[pageOffset];
|
|
1127
1130
|
const baseFileName = resolveStandaloneAtlasOutputFileName(pkg, group.resource, group.branchName);
|
|
@@ -1164,6 +1167,12 @@ function packAtlasPages(inputs, options, forceSinglePage, sizeOverrides) {
|
|
|
1164
1167
|
preserveInputOrderOnTie: options.preserveInputOrderOnTie
|
|
1165
1168
|
}).pack(inputs.map((input, index) => inputToCompatRect(input, index))) ?? [];
|
|
1166
1169
|
}
|
|
1170
|
+
function assertPackedInputCoverage(pages, inputCount, label) {
|
|
1171
|
+
const packedIndexes = /* @__PURE__ */ new Set();
|
|
1172
|
+
for (const page of pages) for (const outputRect of page.outputRects) packedIndexes.add(outputRect.index);
|
|
1173
|
+
const hasEveryInput = Array.from({ length: inputCount }, (_, index) => packedIndexes.has(index)).every(Boolean);
|
|
1174
|
+
if (packedIndexes.size !== inputCount || !hasEveryInput) throw new Error(`atlas: Could not pack every input for ${label}.`);
|
|
1175
|
+
}
|
|
1167
1176
|
function attachSpritesToAtlas(doc, allResources, inputs, outputRects, atlasNode) {
|
|
1168
1177
|
for (const packedRect of outputRects) {
|
|
1169
1178
|
const input = inputs[packedRect.index];
|
|
@@ -1223,7 +1232,9 @@ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, op
|
|
|
1223
1232
|
} else if (input.rasterizedBuffer) imageBuffer = input.rasterizedBuffer;
|
|
1224
1233
|
else {
|
|
1225
1234
|
if (!isImageResource$1(input.resource)) {
|
|
1226
|
-
|
|
1235
|
+
const message = `atlas: Non-image input "${input.id}" is missing inline buffer.`;
|
|
1236
|
+
if (options.strictOutput) throw new Error(message);
|
|
1237
|
+
logger.warn(`${message} Skipping compositing.`);
|
|
1227
1238
|
continue;
|
|
1228
1239
|
}
|
|
1229
1240
|
imageBuffer = await encoder(_resolveImagePath(input.resource, pkg, options.basePath)).toBuffer();
|
|
@@ -1235,7 +1246,9 @@ async function writeAtlasPageImage(pkg, inputs, page, atlasFileName, encoder, op
|
|
|
1235
1246
|
top: packedRect.y
|
|
1236
1247
|
});
|
|
1237
1248
|
} catch {
|
|
1238
|
-
|
|
1249
|
+
const message = `atlas: Could not read image "${input.id}" for compositing.`;
|
|
1250
|
+
if (options.strictOutput) throw new Error(message);
|
|
1251
|
+
logger.warn(message);
|
|
1239
1252
|
}
|
|
1240
1253
|
}
|
|
1241
1254
|
const outputFile = `${options.outputPath}/${atlasFileName}`;
|
|
@@ -1352,7 +1365,9 @@ async function emitDirectImageOutput(doc, pkg, input, encoder, options, logger,
|
|
|
1352
1365
|
}]).png().toFile(outputFile);
|
|
1353
1366
|
}
|
|
1354
1367
|
} catch {
|
|
1355
|
-
|
|
1368
|
+
const message = `atlas: Could not write direct-output atlas "${atlasFileName}".`;
|
|
1369
|
+
if (options.strictOutput) throw new Error(message);
|
|
1370
|
+
logger.warn(message);
|
|
1356
1371
|
}
|
|
1357
1372
|
}
|
|
1358
1373
|
function getInputBranchName(input) {
|
|
@@ -1578,6 +1593,7 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
|
|
|
1578
1593
|
sourceHasAlpha = true;
|
|
1579
1594
|
}
|
|
1580
1595
|
} catch {
|
|
1596
|
+
if (options.strictOutput) throw new Error(`atlas: Could not read image "${filePath}".`);
|
|
1581
1597
|
if (origW === 0 || origH === 0) {
|
|
1582
1598
|
logger.warn(`atlas: Could not read image "${filePath}", skipping.`);
|
|
1583
1599
|
return;
|
|
@@ -1616,7 +1632,11 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
|
|
|
1616
1632
|
}
|
|
1617
1633
|
/** Collect MovieClip frame textures from a .jta file into the inputs array. */
|
|
1618
1634
|
async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, options, logger) {
|
|
1619
|
-
if (!options.basePath || !options.readFileRaw)
|
|
1635
|
+
if (!options.basePath || !options.readFileRaw) {
|
|
1636
|
+
if (options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires basePath and readFileRaw for complete raster output.`);
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
if (!encoder && options.strictOutput) throw new Error(`atlas: MovieClip "${resource.getId()}" requires an encoder for complete raster output.`);
|
|
1620
1640
|
const mcId = resource.getId();
|
|
1621
1641
|
const mcName = resource.getName() + ".jta";
|
|
1622
1642
|
const mcPath = resource.getPath() ?? "/";
|
|
@@ -1639,7 +1659,7 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
1639
1659
|
const exportFrameIndex = firstFrameIndexByTextureIndex.get(textureIndex);
|
|
1640
1660
|
if (exportFrameIndex === void 0) continue;
|
|
1641
1661
|
const itemId = `${mcId}_${exportFrameIndex}`;
|
|
1642
|
-
const input = await _createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder);
|
|
1662
|
+
const input = await _createMovieClipFrameInput(jta.frames[textureIndex], itemId, resource, encoder, options.strictOutput);
|
|
1643
1663
|
if (!input) continue;
|
|
1644
1664
|
inputs.push(input);
|
|
1645
1665
|
spriteIdByTextureIndex.set(textureIndex, itemId);
|
|
@@ -1653,7 +1673,7 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
1653
1673
|
}
|
|
1654
1674
|
} else for (let frameIndex = 0; frameIndex < jta.frames.length; frameIndex += 1) {
|
|
1655
1675
|
const itemId = `${mcId}_${frameIndex}`;
|
|
1656
|
-
const input = await _createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder);
|
|
1676
|
+
const input = await _createMovieClipFrameInput(jta.frames[frameIndex], itemId, resource, encoder, options.strictOutput);
|
|
1657
1677
|
if (!input) continue;
|
|
1658
1678
|
inputs.push(input);
|
|
1659
1679
|
const frame = doc.createMovieFrame(itemId);
|
|
@@ -1665,10 +1685,12 @@ async function _collectMovieClipFrames(doc, resource, pkg, inputs, encoder, opti
|
|
|
1665
1685
|
resource.setHeight(jta.meta?.height ?? 0);
|
|
1666
1686
|
}
|
|
1667
1687
|
} catch {
|
|
1668
|
-
|
|
1688
|
+
const message = `atlas: Could not parse MovieClip "${filePath}".`;
|
|
1689
|
+
if (options.strictOutput) throw new Error(message);
|
|
1690
|
+
logger.warn(`${message} Skipping frames.`);
|
|
1669
1691
|
}
|
|
1670
1692
|
}
|
|
1671
|
-
async function _createMovieClipFrameInput(buffer, itemId, resource, encoder) {
|
|
1693
|
+
async function _createMovieClipFrameInput(buffer, itemId, resource, encoder, strictOutput) {
|
|
1672
1694
|
if (!encoder || buffer.length === 0) return null;
|
|
1673
1695
|
try {
|
|
1674
1696
|
const meta = await encoder(buffer).metadata();
|
|
@@ -1688,6 +1710,7 @@ async function _createMovieClipFrameInput(buffer, itemId, resource, encoder) {
|
|
|
1688
1710
|
sourceKind: "movieclip-frame"
|
|
1689
1711
|
};
|
|
1690
1712
|
} catch {
|
|
1713
|
+
if (strictOutput) throw new Error(`atlas: Could not decode MovieClip frame "${itemId}".`);
|
|
1691
1714
|
return null;
|
|
1692
1715
|
}
|
|
1693
1716
|
}
|
|
@@ -3000,13 +3023,13 @@ function getPublishedSkeletonDependencyImageIds(pkg, publishedResourceIds) {
|
|
|
3000
3023
|
}
|
|
3001
3024
|
return imageIds;
|
|
3002
3025
|
}
|
|
3003
|
-
async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw
|
|
3026
|
+
async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw) {
|
|
3004
3027
|
const publishedResourceIds = getAnnotatedPublishedResourceIds(pkg);
|
|
3005
3028
|
if (publishedResourceIds.size === 0) return;
|
|
3006
3029
|
if (!basePath || !readFileRaw) {
|
|
3007
3030
|
if (pkg.listResources().some((resource) => {
|
|
3008
3031
|
return isSoundResource(resource) && publishedResourceIds.has(resource.getId());
|
|
3009
|
-
}))
|
|
3032
|
+
})) throw new Error(`publish: Sound resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
|
|
3010
3033
|
return;
|
|
3011
3034
|
}
|
|
3012
3035
|
for (const resource of pkg.listResources()) {
|
|
@@ -3019,18 +3042,18 @@ async function exportPackageSounds(pkg, outputDir, basePath, fs, readFileRaw, lo
|
|
|
3019
3042
|
const data = await readFileRaw(sourcePath);
|
|
3020
3043
|
await fs.writeFileRaw(targetPath, data);
|
|
3021
3044
|
} catch {
|
|
3022
|
-
|
|
3045
|
+
throw new Error(`publish: Could not export sound "${resource.getId()}" from package "${pkg.getName()}".`);
|
|
3023
3046
|
}
|
|
3024
3047
|
}
|
|
3025
3048
|
}
|
|
3026
|
-
async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw
|
|
3049
|
+
async function exportPackageExternalResources(pkg, outputDir, basePath, fs, readFileRaw) {
|
|
3027
3050
|
const exportedResourceIds = getAnnotatedExportedResourceIds(pkg);
|
|
3028
3051
|
const skeletonDependencyImageIds = getPublishedSkeletonDependencyImageIds(pkg, exportedResourceIds);
|
|
3029
3052
|
if (exportedResourceIds.size === 0) return;
|
|
3030
3053
|
if (!basePath || !readFileRaw) {
|
|
3031
3054
|
if (pkg.listResources().some((resource) => {
|
|
3032
3055
|
return (isMiscResource(resource) || isSkeletonResource(resource)) && exportedResourceIds.has(resource.getId()) || skeletonDependencyImageIds.has(resource.getId());
|
|
3033
|
-
}))
|
|
3056
|
+
})) throw new Error(`publish: External resources in package "${pkg.getName()}" require basePath and readFileRaw for output.`);
|
|
3034
3057
|
return;
|
|
3035
3058
|
}
|
|
3036
3059
|
for (const resource of pkg.listResources()) {
|
|
@@ -3052,7 +3075,7 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
|
|
|
3052
3075
|
const data = await readFileRaw(sourcePath);
|
|
3053
3076
|
await fs.writeFileRaw(targetPath, data);
|
|
3054
3077
|
} catch {
|
|
3055
|
-
|
|
3078
|
+
throw new Error(`publish: Could not export external resource "${resource.getId()}" from package "${pkg.getName()}".`);
|
|
3056
3079
|
}
|
|
3057
3080
|
}
|
|
3058
3081
|
}
|
|
@@ -3068,18 +3091,17 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
|
|
|
3068
3091
|
* `publishNode()` or `publishBrowser()` through their dedicated entries.
|
|
3069
3092
|
*
|
|
3070
3093
|
* ```ts
|
|
3071
|
-
* import
|
|
3072
|
-
*
|
|
3073
|
-
* const doc = await
|
|
3094
|
+
* import { NodeIO } from '@openfairygui/core/node';
|
|
3095
|
+
* import { publishNode } from '@openfairygui/functions/node';
|
|
3096
|
+
* const doc = await new NodeIO().readProject('./project.fairy');
|
|
3074
3097
|
*
|
|
3075
|
-
* await
|
|
3098
|
+
* await publishNode({
|
|
3099
|
+
* document: doc,
|
|
3076
3100
|
* output: './release/',
|
|
3077
3101
|
* compressed: true,
|
|
3078
|
-
*
|
|
3079
|
-
* basePath: './assets/',
|
|
3102
|
+
* assetsPath: './assets/',
|
|
3080
3103
|
* fileExtension: 'bytes',
|
|
3081
|
-
*
|
|
3082
|
-
* }));
|
|
3104
|
+
* });
|
|
3083
3105
|
* ```
|
|
3084
3106
|
*/
|
|
3085
3107
|
function publish(options) {
|
|
@@ -3147,6 +3169,12 @@ function publish(options) {
|
|
|
3147
3169
|
}
|
|
3148
3170
|
});
|
|
3149
3171
|
const publishPackage = async (plan, writerFs, packageIndex) => {
|
|
3172
|
+
if (options.fs && !plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
|
|
3173
|
+
if (options.fs) {
|
|
3174
|
+
await options.fs.mkdir(plan.outputDir);
|
|
3175
|
+
await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
|
|
3176
|
+
await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw);
|
|
3177
|
+
}
|
|
3150
3178
|
const atlasRuntimeOptions = resolvePublishAtlasRuntimeOptions(plan.fileExtension);
|
|
3151
3179
|
await atlas({
|
|
3152
3180
|
...plan.atlas,
|
|
@@ -3157,20 +3185,17 @@ function publish(options) {
|
|
|
3157
3185
|
outputPath: options.fs ? plan.outputDir : void 0,
|
|
3158
3186
|
mkdir: options.fs ? options.fs.mkdir : void 0,
|
|
3159
3187
|
readFileRaw: options.atlas?.readFileRaw ?? options.fs?.readFileRaw,
|
|
3188
|
+
strictOutput: options.fs !== void 0,
|
|
3160
3189
|
packages: [plan.pkg.getName()],
|
|
3161
3190
|
...atlasRuntimeOptions
|
|
3162
3191
|
})(doc);
|
|
3163
3192
|
if (!options.fs) return;
|
|
3164
|
-
if (!plan.outputDir) throw new Error("publish: no output directory resolved. Provide --output, or configure global publish.path / package publishPath.");
|
|
3165
|
-
await options.fs.mkdir(plan.outputDir);
|
|
3166
3193
|
const filePath = options.fs.join(plan.outputDir, plan.fileName);
|
|
3167
3194
|
const bwOptions = {
|
|
3168
3195
|
compressed: plan.compressed,
|
|
3169
3196
|
packageIndex
|
|
3170
3197
|
};
|
|
3171
3198
|
await new _openfairygui_core.BinaryWriter(writerFs).write(doc, filePath, bwOptions);
|
|
3172
|
-
await exportPackageSounds(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
|
|
3173
|
-
await exportPackageExternalResources(plan.pkg, plan.outputDir, options.basePath, options.fs, options.atlas?.readFileRaw ?? options.fs.readFileRaw, logger);
|
|
3174
3199
|
logger.info(`publish: Written ${plan.fileName}`);
|
|
3175
3200
|
};
|
|
3176
3201
|
const root = doc.getRoot();
|
|
@@ -3203,7 +3228,9 @@ function publish(options) {
|
|
|
3203
3228
|
}
|
|
3204
3229
|
const plans = allPackages.map((pkg) => resolvePackagePublishPlan(pkg, resolved, projectBasePath));
|
|
3205
3230
|
if (!options.fs) {
|
|
3206
|
-
|
|
3231
|
+
const outputPlan = plans.find((plan) => !!plan.outputDir);
|
|
3232
|
+
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.`);
|
|
3233
|
+
logger.info(`publish: Layout computed for ${allPackages.length} package(s); no output directory was requested.`);
|
|
3207
3234
|
const noopWriterFs = toBinaryWriterFileSystem(createNoopPublishFs());
|
|
3208
3235
|
for (const plan of plans) await publishPackage(plan, noopWriterFs, allDocPackages.indexOf(plan.pkg));
|
|
3209
3236
|
await runPublishPluginHook(plugins, "onPublishEnd", doc, options);
|