@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/README.md
CHANGED
|
@@ -22,7 +22,7 @@ const report = inspect(doc);
|
|
|
22
22
|
await publishNode({ document: doc, output: './release' });
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`publishNode` owns the standard Node filesystem, Sharp raster backend, and project plugin discovery. The root `publish()` export remains the lower-level capability-injected core for custom hosts.
|
|
25
|
+
`publishNode` owns the standard Node filesystem, Sharp raster backend, and project plugin discovery. It fails when a complete runtime artifact cannot be generated. The root `publish()` export remains the lower-level capability-injected core for custom hosts; it is layout-only only when no output directory is requested.
|
|
26
26
|
|
|
27
27
|
## Browser LayaBox publish
|
|
28
28
|
|
|
@@ -50,9 +50,9 @@ Publish plugins are documented in the repository guide:
|
|
|
50
50
|
|
|
51
51
|
- https://github.com/OpenFairyGUI/OpenFairyGUI/blob/main/docs/publish-plugins.md
|
|
52
52
|
|
|
53
|
-
##
|
|
53
|
+
## UAM authoring seam
|
|
54
54
|
|
|
55
|
-
`@openfairygui/functions` also exposes a thin stateless wrapper over the
|
|
55
|
+
`@openfairygui/functions` also exposes a thin stateless wrapper over the UAM
|
|
56
56
|
transaction contract from `@openfairygui/core`.
|
|
57
57
|
|
|
58
58
|
This seam:
|
|
@@ -63,6 +63,14 @@ This seam:
|
|
|
63
63
|
- does not define a second selector / operation grammar
|
|
64
64
|
- does not wrap `publish` or `restore`
|
|
65
65
|
|
|
66
|
+
The transaction surface includes resource rename/move, byte-backed binary resource
|
|
67
|
+
add/replace/remove, and add/update/remove for `display`, `display2`, `look`, `xy`,
|
|
68
|
+
`size`, `color`, `animation`, `text`, `icon`, and `fontSize` gears. Resource
|
|
69
|
+
rename/move/replace/remove requires `sourceBytes`; opt in with
|
|
70
|
+
`ProjectReader.read(path, { hydrateResourceBytes: true })` before lifting a project to
|
|
71
|
+
UAM. Source bytes are written back with the project, and stale source files are removed
|
|
72
|
+
only after all replacement content succeeds.
|
|
73
|
+
|
|
66
74
|
```ts
|
|
67
75
|
import {
|
|
68
76
|
type UamProject,
|
|
@@ -70,7 +78,7 @@ import {
|
|
|
70
78
|
} from '@openfairygui/core';
|
|
71
79
|
import { applyUamTransactionApp } from '@openfairygui/functions';
|
|
72
80
|
|
|
73
|
-
const project: UamProject = /*
|
|
81
|
+
const project: UamProject = /* project read and lifted with hydrateResourceBytes */;
|
|
74
82
|
const operations: UamTransactionOperation[] = [
|
|
75
83
|
{
|
|
76
84
|
kind: 'renameResource',
|
|
@@ -19,7 +19,8 @@ interface PublishOptions {
|
|
|
19
19
|
fileExtension?: string;
|
|
20
20
|
/**
|
|
21
21
|
* Raster backend for atlas image compositing.
|
|
22
|
-
*
|
|
22
|
+
* Required when a filesystem-backed publish has packable resources.
|
|
23
|
+
* Without a filesystem, publish remains an explicit layout-only transform.
|
|
23
24
|
*/
|
|
24
25
|
encoder?: AtlasRasterBackend;
|
|
25
26
|
/**
|
|
@@ -37,8 +38,9 @@ interface PublishOptions {
|
|
|
37
38
|
packages?: string[];
|
|
38
39
|
/**
|
|
39
40
|
* FileSystem abstraction for writing output files.
|
|
40
|
-
* Required for actual file output.
|
|
41
|
-
* is
|
|
41
|
+
* Required for actual file output. Calling publish with a resolved output
|
|
42
|
+
* directory but no filesystem is rejected; omit output entirely for a
|
|
43
|
+
* layout-only transform.
|
|
42
44
|
*/
|
|
43
45
|
fs?: PublishFileSystem;
|
|
44
46
|
/**
|
|
@@ -89,18 +91,17 @@ declare function resolvePublishOptions(doc: Document, overrides?: ResolvePublish
|
|
|
89
91
|
* `publishNode()` or `publishBrowser()` through their dedicated entries.
|
|
90
92
|
*
|
|
91
93
|
* ```ts
|
|
92
|
-
* import
|
|
93
|
-
*
|
|
94
|
-
* const doc = await
|
|
94
|
+
* import { NodeIO } from '@openfairygui/core/node';
|
|
95
|
+
* import { publishNode } from '@openfairygui/functions/node';
|
|
96
|
+
* const doc = await new NodeIO().readProject('./project.fairy');
|
|
95
97
|
*
|
|
96
|
-
* await
|
|
98
|
+
* await publishNode({
|
|
99
|
+
* document: doc,
|
|
97
100
|
* output: './release/',
|
|
98
101
|
* compressed: true,
|
|
99
|
-
*
|
|
100
|
-
* basePath: './assets/',
|
|
102
|
+
* assetsPath: './assets/',
|
|
101
103
|
* fileExtension: 'bytes',
|
|
102
|
-
*
|
|
103
|
-
* }));
|
|
104
|
+
* });
|
|
104
105
|
* ```
|
|
105
106
|
*/
|
|
106
107
|
declare function publish(options: PublishOptions): Transform;
|
|
@@ -19,7 +19,8 @@ interface PublishOptions {
|
|
|
19
19
|
fileExtension?: string;
|
|
20
20
|
/**
|
|
21
21
|
* Raster backend for atlas image compositing.
|
|
22
|
-
*
|
|
22
|
+
* Required when a filesystem-backed publish has packable resources.
|
|
23
|
+
* Without a filesystem, publish remains an explicit layout-only transform.
|
|
23
24
|
*/
|
|
24
25
|
encoder?: AtlasRasterBackend;
|
|
25
26
|
/**
|
|
@@ -37,8 +38,9 @@ interface PublishOptions {
|
|
|
37
38
|
packages?: string[];
|
|
38
39
|
/**
|
|
39
40
|
* FileSystem abstraction for writing output files.
|
|
40
|
-
* Required for actual file output.
|
|
41
|
-
* is
|
|
41
|
+
* Required for actual file output. Calling publish with a resolved output
|
|
42
|
+
* directory but no filesystem is rejected; omit output entirely for a
|
|
43
|
+
* layout-only transform.
|
|
42
44
|
*/
|
|
43
45
|
fs?: PublishFileSystem;
|
|
44
46
|
/**
|
|
@@ -89,18 +91,17 @@ declare function resolvePublishOptions(doc: Document, overrides?: ResolvePublish
|
|
|
89
91
|
* `publishNode()` or `publishBrowser()` through their dedicated entries.
|
|
90
92
|
*
|
|
91
93
|
* ```ts
|
|
92
|
-
* import
|
|
93
|
-
*
|
|
94
|
-
* const doc = await
|
|
94
|
+
* import { NodeIO } from '@openfairygui/core/node';
|
|
95
|
+
* import { publishNode } from '@openfairygui/functions/node';
|
|
96
|
+
* const doc = await new NodeIO().readProject('./project.fairy');
|
|
95
97
|
*
|
|
96
|
-
* await
|
|
98
|
+
* await publishNode({
|
|
99
|
+
* document: doc,
|
|
97
100
|
* output: './release/',
|
|
98
101
|
* compressed: true,
|
|
99
|
-
*
|
|
100
|
-
* basePath: './assets/',
|
|
102
|
+
* assetsPath: './assets/',
|
|
101
103
|
* fileExtension: 'bytes',
|
|
102
|
-
*
|
|
103
|
-
* }));
|
|
104
|
+
* });
|
|
104
105
|
* ```
|
|
105
106
|
*/
|
|
106
107
|
declare function publish(options: PublishOptions): Transform;
|
package/dist/index.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
|
const require_uam_transaction = require("./uam-transaction.cjs");
|
|
4
4
|
let _openfairygui_core = require("@openfairygui/core");
|
|
5
5
|
//#region src/inspect.ts
|
|
@@ -393,10 +393,16 @@ const TRANSPARENT_PNG_1X1 = Uint8Array.from([
|
|
|
393
393
|
96,
|
|
394
394
|
130
|
|
395
395
|
]);
|
|
396
|
+
function assertSafeRestoreSegment(value, label) {
|
|
397
|
+
if (!value || value === "." || value === ".." || value.includes("\0") || /[\\/:]/u.test(value)) throw new Error(`restore: Invalid ${label} "${value}".`);
|
|
398
|
+
}
|
|
396
399
|
function normalizeVirtualPath(path) {
|
|
397
|
-
const
|
|
398
|
-
if (!
|
|
399
|
-
|
|
400
|
+
const raw = (path ?? "").trim();
|
|
401
|
+
if (!raw || raw === "/") return "";
|
|
402
|
+
if (raw.includes("\0") || raw.startsWith("\\") || raw.startsWith("//") || /^[a-z]:/iu.test(raw)) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
403
|
+
const segments = raw.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
404
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment.includes(":"))) throw new Error(`restore: Invalid resource path "${raw}".`);
|
|
405
|
+
return segments.join("/");
|
|
400
406
|
}
|
|
401
407
|
function resourceFileName(resource) {
|
|
402
408
|
return resource.getFileName?.() || resource.getFile?.() || resource.getName?.() || "";
|
|
@@ -580,44 +586,40 @@ function normalizeComparablePath(value) {
|
|
|
580
586
|
const joined = segments.join("/");
|
|
581
587
|
return (drivePrefix ? `${drivePrefix}/${joined}`.replace(/\/$/, "") : hasRoot ? `/${joined}`.replace(/\/$/, "") : joined || ".").toLowerCase();
|
|
582
588
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
589
|
+
function isPathWithin(root, candidate) {
|
|
590
|
+
const normalizedRoot = normalizeComparablePath(root);
|
|
591
|
+
return normalizeComparablePath(candidate).startsWith(`${normalizedRoot}/`);
|
|
585
592
|
}
|
|
586
593
|
function basename(filePath) {
|
|
587
594
|
return trimTrailingSlashes(filePath).match(/([^/\\]+)$/)?.[1] ?? "";
|
|
588
595
|
}
|
|
589
|
-
function
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
return
|
|
596
|
+
function normalizeRestoreOutputDir(output) {
|
|
597
|
+
const normalized = trimTrailingSlashes(output);
|
|
598
|
+
const name = basename(normalized);
|
|
599
|
+
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.");
|
|
600
|
+
return normalized;
|
|
594
601
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
throw new Error(`Restore output path is not a directory: ${outputDir}`);
|
|
607
|
-
}
|
|
608
|
-
if (!await fs.exists(outputProjectPath)) return;
|
|
609
|
-
if (!force) throw new Error(`Restore output file already exists: ${outputProjectPath}. Use --force to overwrite it.`);
|
|
610
|
-
if (!fs.rm) throw new Error("Restore output file already exists and the provided fs does not support rm(...).");
|
|
611
|
-
await fs.rm(outputProjectPath, {
|
|
612
|
-
recursive: true,
|
|
613
|
-
force: true
|
|
614
|
-
});
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
if (!await fs.exists(outputDir)) {
|
|
618
|
-
await fs.mkdir(outputDir);
|
|
619
|
-
return;
|
|
602
|
+
function resolveOutputProjectPath(outputDir, fs) {
|
|
603
|
+
return fs.join(outputDir, `${basename(outputDir)}.fairy`);
|
|
604
|
+
}
|
|
605
|
+
async function resolvePathForContainment(filePath, fs) {
|
|
606
|
+
const missingSegments = [];
|
|
607
|
+
let existingPath = filePath;
|
|
608
|
+
while (!await fs.exists(existingPath)) {
|
|
609
|
+
const parentPath = fs.dirname(existingPath);
|
|
610
|
+
if (!parentPath || parentPath === existingPath) return Promise.resolve(fs.resolvePath(filePath));
|
|
611
|
+
missingSegments.unshift(basename(existingPath));
|
|
612
|
+
existingPath = parentPath;
|
|
620
613
|
}
|
|
614
|
+
const resolvedExistingPath = await Promise.resolve(fs.resolvePath(existingPath));
|
|
615
|
+
return missingSegments.reduce((resolvedPath, segment) => fs.join(resolvedPath, segment), resolvedExistingPath);
|
|
616
|
+
}
|
|
617
|
+
async function assertRestoreOutputDir(inputDir, outputDir, fs, force) {
|
|
618
|
+
const [resolvedInputDir, resolvedOutputDir] = await Promise.all([resolvePathForContainment(inputDir, fs), resolvePathForContainment(outputDir, fs)]);
|
|
619
|
+
const normalizedInputDir = normalizeComparablePath(resolvedInputDir);
|
|
620
|
+
const normalizedOutputDir = normalizeComparablePath(resolvedOutputDir);
|
|
621
|
+
if (normalizedInputDir === normalizedOutputDir || isPathWithin(normalizedInputDir, normalizedOutputDir) || isPathWithin(normalizedOutputDir, normalizedInputDir)) throw new Error("Restore output directory must be independent from the published input directory.");
|
|
622
|
+
if (!await fs.exists(outputDir)) return;
|
|
621
623
|
let entries;
|
|
622
624
|
try {
|
|
623
625
|
entries = await fs.readdir(outputDir);
|
|
@@ -626,23 +628,63 @@ async function prepareRestoreOutputDir(inputDir, outputDir, outputProjectPath, f
|
|
|
626
628
|
}
|
|
627
629
|
if (entries.length === 0) return;
|
|
628
630
|
if (!force) throw new Error(`Restore output directory is not empty: ${outputDir}. Use --force to overwrite it.`);
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
631
|
+
}
|
|
632
|
+
async function createRestoreStagingDir(outputDir, fs) {
|
|
633
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
634
|
+
await fs.mkdir(parentDir);
|
|
635
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
636
|
+
const stagingDir = fs.join(parentDir, `.${basename(outputDir)}.restore-${(0, _openfairygui_core.generateId)()}`);
|
|
637
|
+
if (await fs.exists(stagingDir)) continue;
|
|
638
|
+
await fs.mkdir(stagingDir);
|
|
639
|
+
return stagingDir;
|
|
640
|
+
}
|
|
641
|
+
throw new Error(`restore: Could not allocate a staging directory beside ${outputDir}.`);
|
|
642
|
+
}
|
|
643
|
+
async function commitRestoreOutput(stagingDir, outputDir, fs) {
|
|
644
|
+
if (!await fs.exists(outputDir)) {
|
|
645
|
+
await fs.rename(stagingDir, outputDir);
|
|
646
|
+
return null;
|
|
647
|
+
}
|
|
648
|
+
const parentDir = fs.dirname(outputDir) || ".";
|
|
649
|
+
let backupDir = "";
|
|
650
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
651
|
+
const candidate = fs.join(parentDir, `.${basename(outputDir)}.restore-backup-${(0, _openfairygui_core.generateId)()}`);
|
|
652
|
+
if (!await fs.exists(candidate)) {
|
|
653
|
+
backupDir = candidate;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (!backupDir) throw new Error(`restore: Could not allocate a backup directory beside ${outputDir}.`);
|
|
658
|
+
await fs.rename(outputDir, backupDir);
|
|
659
|
+
try {
|
|
660
|
+
await fs.rename(stagingDir, outputDir);
|
|
661
|
+
} catch (error) {
|
|
662
|
+
await fs.rename(backupDir, outputDir);
|
|
663
|
+
throw error;
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
await fs.rm(backupDir, {
|
|
667
|
+
recursive: true,
|
|
668
|
+
force: true
|
|
669
|
+
});
|
|
670
|
+
return null;
|
|
671
|
+
} catch {
|
|
672
|
+
return `restore: Previous output retained at ${backupDir}; remove it after checking the restored project.`;
|
|
673
|
+
}
|
|
635
674
|
}
|
|
636
675
|
async function restore(options) {
|
|
637
676
|
const sourceDir = trimTrailingSlashes(options.inputDir);
|
|
638
|
-
const
|
|
639
|
-
const outputProjectPath = resolveOutputProjectPath(
|
|
640
|
-
await
|
|
677
|
+
const outputDir = normalizeRestoreOutputDir(options.output);
|
|
678
|
+
const outputProjectPath = resolveOutputProjectPath(outputDir, options.fs);
|
|
679
|
+
await assertRestoreOutputDir(sourceDir, outputDir, options.fs, options.force === true);
|
|
641
680
|
const packageFilter = options.packages?.length ? new Set(options.packages) : null;
|
|
642
|
-
const
|
|
681
|
+
const binaryNames = (await options.fs.readdir(sourceDir)).filter((name) => isPublishedBinaryFile(name)).filter((name) => !packageFilter || packageFilter.has(inferPackageName(name)));
|
|
682
|
+
for (const binaryName of binaryNames) assertSafeRestoreSegment(binaryName, "published binary file name");
|
|
683
|
+
const candidateBinaryPaths = binaryNames.map((name) => options.fs.join(sourceDir, name)).sort((left, right) => left.localeCompare(right));
|
|
643
684
|
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));
|
|
644
685
|
if (binaryPaths.length === 0) throw new Error(`No FairyGUI published binary files found in ${sourceDir}.`);
|
|
645
|
-
|
|
686
|
+
const restorer = new RestoreWorkflow(options.fs);
|
|
687
|
+
const document = await restorer.prepare({
|
|
646
688
|
binaryPaths,
|
|
647
689
|
sourceDir,
|
|
648
690
|
outputProjectPath,
|
|
@@ -650,18 +692,45 @@ async function restore(options) {
|
|
|
650
692
|
cropImage: options.cropImage,
|
|
651
693
|
extractImage: options.extractImage
|
|
652
694
|
});
|
|
695
|
+
const stagingDir = await createRestoreStagingDir(outputDir, options.fs);
|
|
696
|
+
const stagingProjectPath = options.fs.join(stagingDir, basename(outputProjectPath));
|
|
697
|
+
const warnings = [];
|
|
698
|
+
try {
|
|
699
|
+
await restorer.write(document, {
|
|
700
|
+
binaryPaths,
|
|
701
|
+
sourceDir,
|
|
702
|
+
outputProjectPath: stagingProjectPath,
|
|
703
|
+
projectType: options.projectType,
|
|
704
|
+
cropImage: options.cropImage,
|
|
705
|
+
extractImage: options.extractImage
|
|
706
|
+
}, warnings);
|
|
707
|
+
const cleanupWarning = await commitRestoreOutput(stagingDir, outputDir, options.fs);
|
|
708
|
+
if (cleanupWarning) warnings.push(cleanupWarning);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
await options.fs.rm(stagingDir, {
|
|
711
|
+
recursive: true,
|
|
712
|
+
force: true
|
|
713
|
+
}).catch(() => void 0);
|
|
714
|
+
throw error;
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
document,
|
|
718
|
+
projectPath: outputProjectPath,
|
|
719
|
+
warnings
|
|
720
|
+
};
|
|
653
721
|
}
|
|
654
722
|
var RestoreWorkflow = class {
|
|
655
723
|
_fs;
|
|
656
724
|
constructor(fs) {
|
|
657
725
|
this._fs = fs;
|
|
658
726
|
}
|
|
659
|
-
async
|
|
660
|
-
const warnings = [];
|
|
727
|
+
async prepare(options) {
|
|
661
728
|
const doc = await new _openfairygui_core.BinaryReader(this._fs).readMany(options.binaryPaths);
|
|
729
|
+
this._assertDocumentPaths(doc);
|
|
662
730
|
this._initializeProjectDefaults(doc, options.projectType);
|
|
663
731
|
this._initializeImageFileNames(doc);
|
|
664
732
|
this._initializeLooseResourceFileNames(doc);
|
|
733
|
+
this._assertDocumentPaths(doc);
|
|
665
734
|
await this._synthesizeLooseSkeletonResources(doc, options.sourceDir);
|
|
666
735
|
this._initializeRestoredResourceRelations(doc);
|
|
667
736
|
this._initializePublishedFontTextureIds(doc);
|
|
@@ -670,13 +739,27 @@ var RestoreWorkflow = class {
|
|
|
670
739
|
this._initializePublishedTextFontResources(doc);
|
|
671
740
|
this._initializeDisplayObjectFileNames(doc);
|
|
672
741
|
this._initializePublishedFontDefaults(doc);
|
|
742
|
+
this._assertDocumentPaths(doc);
|
|
743
|
+
return doc;
|
|
744
|
+
}
|
|
745
|
+
async write(doc, options, warnings) {
|
|
673
746
|
await new _openfairygui_core.ProjectWriter(this._fs).write(doc, options.outputProjectPath);
|
|
674
747
|
await this._restoreAssets(doc, options, warnings);
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
748
|
+
}
|
|
749
|
+
_assertDocumentPaths(doc) {
|
|
750
|
+
for (const pkg of doc.getRoot().listPackages()) {
|
|
751
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
752
|
+
assertSafeRestoreSegment(pkg.getPublishName() || pkg.getName(), "package publish name");
|
|
753
|
+
for (const resource of pkg.listResources()) {
|
|
754
|
+
normalizeVirtualPath(resource.getPath?.());
|
|
755
|
+
const branch = resource.getBranch?.() ?? "";
|
|
756
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
757
|
+
const fileName = resourceFileName(resource);
|
|
758
|
+
if (fileName) assertSafeRestoreSegment(fileName, "resource file name");
|
|
759
|
+
const publishedFileName = resourcePublishedFileName(resource);
|
|
760
|
+
if (publishedFileName) assertSafeRestoreSegment(publishedFileName, "published resource file name");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
680
763
|
}
|
|
681
764
|
_initializeProjectDefaults(doc, projectType) {
|
|
682
765
|
doc.getRoot().setProjectId((0, _openfairygui_core.generateId)()).setProjectType(projectType ?? _openfairygui_core.ProjectType.Unity).setVersion("3.0").setSettings({
|
|
@@ -1110,27 +1193,40 @@ var RestoreWorkflow = class {
|
|
|
1110
1193
|
}
|
|
1111
1194
|
_sourceFileCandidates(pkg, fileName, outputFileName = fileName) {
|
|
1112
1195
|
const publishName = pkg.getPublishName() || pkg.getName();
|
|
1113
|
-
|
|
1196
|
+
assertSafeRestoreSegment(publishName, "package publish name");
|
|
1197
|
+
assertSafeRestoreSegment(fileName, "published source file name");
|
|
1198
|
+
assertSafeRestoreSegment(outputFileName, "published source file name");
|
|
1199
|
+
const candidates = Array.from(new Set([
|
|
1114
1200
|
`${publishName}_${fileName}`,
|
|
1115
1201
|
fileName,
|
|
1116
1202
|
`${publishName}_${outputFileName}`,
|
|
1117
1203
|
outputFileName
|
|
1118
1204
|
]));
|
|
1205
|
+
for (const candidate of candidates) assertSafeRestoreSegment(candidate, "published source file name");
|
|
1206
|
+
return candidates;
|
|
1119
1207
|
}
|
|
1120
1208
|
async _resolveLooseSourceFile(pkg, sourceDir, outputFileName) {
|
|
1121
1209
|
const candidates = outputFileName.endsWith(".atlas") ? this._sourceFileCandidates(pkg, `${outputFileName}.txt`, outputFileName) : outputFileName.endsWith(".skel") ? this._sourceFileCandidates(pkg, `${outputFileName}.bytes`, outputFileName) : this._sourceFileCandidates(pkg, outputFileName);
|
|
1122
1210
|
return this._resolveSourceFile(sourceDir, candidates);
|
|
1123
1211
|
}
|
|
1124
1212
|
async _resolveSourceFile(sourceDir, candidates) {
|
|
1213
|
+
const resolvedSourceDir = await Promise.resolve(this._fs.resolvePath(sourceDir));
|
|
1125
1214
|
for (const candidate of candidates) {
|
|
1215
|
+
assertSafeRestoreSegment(candidate, "published source file name");
|
|
1126
1216
|
const sourcePath = this._fs.join(sourceDir, candidate);
|
|
1127
|
-
if (await this._fs.isFile(sourcePath))
|
|
1217
|
+
if (!await this._fs.isFile(sourcePath)) continue;
|
|
1218
|
+
const resolvedSourcePath = await Promise.resolve(this._fs.resolvePath(sourcePath));
|
|
1219
|
+
if (!isPathWithin(resolvedSourceDir, resolvedSourcePath)) throw new Error(`restore: Published source file resolves outside the input directory: ${candidate}.`);
|
|
1220
|
+
return resolvedSourcePath;
|
|
1128
1221
|
}
|
|
1129
1222
|
return null;
|
|
1130
1223
|
}
|
|
1131
1224
|
_resourceOutputPath(outputProjectPath, pkg, resource, fileName) {
|
|
1132
1225
|
const basePath = this._fs.dirname(outputProjectPath);
|
|
1133
1226
|
const branch = resource.getBranch?.() ?? "";
|
|
1227
|
+
assertSafeRestoreSegment(pkg.getName(), "package name");
|
|
1228
|
+
if (branch) assertSafeRestoreSegment(branch, "branch name");
|
|
1229
|
+
assertSafeRestoreSegment(fileName, "resource file name");
|
|
1134
1230
|
const assetsDir = branch ? `assets_${branch}` : "assets";
|
|
1135
1231
|
const virtualPath = normalizeVirtualPath(resource.getPath?.());
|
|
1136
1232
|
const pkgDir = this._fs.join(basePath, assetsDir, pkg.getName());
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as AtlasRasterInput, c as AtlasRasterResolvedBuffer, d as PublishSourceFileSystem, i as AtlasRasterCompositeInput, l as PublishFileSystem, n as atlas, o as AtlasRasterMetadata, r as AtlasRasterBackend, s as AtlasRasterPipeline, t as AtlasOptions, u as PublishOutputFileSystem } from "./atlas-CHsu2Y8i.cjs";
|
|
2
|
-
import { A as ResolvedPublishAtlasOptions, C as HasOptionalFont, D as RootProjectSettings, E as PublishDependency, M as publish, N as resolvePublishOptions, O as PublishOptions, S as ExtrasMap, T as HasOptionalUrl, _ as PluginManifest, a as PublishCodeGenerationOptions, b as CliCodeGenerationSettings, c as decodeText, d as resolvePackageCodegenPlan, f as CodeWriter, g as Plugin, h as MaybePromise, i as CodegenReferencedComponent, j as ResolvedPublishOptions, k as ResolvePublishOptionsOverrides, l as encodeText, m as LoadedPlugin, n as CodegenClass, o as ResolvedPackageCodegenPlan, p as ICodeWriterConfig, r as CodegenMember, s as buildCodegenClasses, t as AUTO_GENERATED_CODE_MARK, u as publishCodeGeneration, v as PluginModule, w as HasOptionalSrc, x as CliPublishSettings, y as CliAtlasSettings } from "./codegen-
|
|
2
|
+
import { A as ResolvedPublishAtlasOptions, C as HasOptionalFont, D as RootProjectSettings, E as PublishDependency, M as publish, N as resolvePublishOptions, O as PublishOptions, S as ExtrasMap, T as HasOptionalUrl, _ as PluginManifest, a as PublishCodeGenerationOptions, b as CliCodeGenerationSettings, c as decodeText, d as resolvePackageCodegenPlan, f as CodeWriter, g as Plugin, h as MaybePromise, i as CodegenReferencedComponent, j as ResolvedPublishOptions, k as ResolvePublishOptionsOverrides, l as encodeText, m as LoadedPlugin, n as CodegenClass, o as ResolvedPackageCodegenPlan, p as ICodeWriterConfig, r as CodegenMember, s as buildCodegenClasses, t as AUTO_GENERATED_CODE_MARK, u as publishCodeGeneration, v as PluginModule, w as HasOptionalSrc, x as CliPublishSettings, y as CliAtlasSettings } from "./codegen-B8ZM1F4j.cjs";
|
|
3
3
|
import { ApplyUamTransactionAppDiagnostic, ApplyUamTransactionAppError, ApplyUamTransactionAppInput, ApplyUamTransactionAppResult, applyUamTransactionApp } from "./uam-transaction.cjs";
|
|
4
4
|
import { Document, FileSystem, Transform } from "@openfairygui/core";
|
|
5
5
|
|
|
@@ -194,10 +194,11 @@ interface RestoreFileSystem extends Pick<FileSystem, 'readFile' | 'readFileRaw'
|
|
|
194
194
|
readdir(path: string): Promise<string[]>;
|
|
195
195
|
isFile(path: string): Promise<boolean>;
|
|
196
196
|
resolvePath(path: string): string | Promise<string>;
|
|
197
|
-
rm
|
|
197
|
+
rm(path: string, options?: {
|
|
198
198
|
recursive?: boolean;
|
|
199
199
|
force?: boolean;
|
|
200
|
-
})
|
|
200
|
+
}): Promise<void>;
|
|
201
|
+
rename(from: string, to: string): Promise<void>;
|
|
201
202
|
}
|
|
202
203
|
interface RestoreOptions {
|
|
203
204
|
inputDir: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as AtlasRasterInput, c as AtlasRasterResolvedBuffer, d as PublishSourceFileSystem, i as AtlasRasterCompositeInput, l as PublishFileSystem, n as atlas, o as AtlasRasterMetadata, r as AtlasRasterBackend, s as AtlasRasterPipeline, t as AtlasOptions, u as PublishOutputFileSystem } from "./atlas-CDn6TirX.js";
|
|
2
|
-
import { A as ResolvedPublishAtlasOptions, C as HasOptionalFont, D as RootProjectSettings, E as PublishDependency, M as publish, N as resolvePublishOptions, O as PublishOptions, S as ExtrasMap, T as HasOptionalUrl, _ as PluginManifest, a as PublishCodeGenerationOptions, b as CliCodeGenerationSettings, c as decodeText, d as resolvePackageCodegenPlan, f as CodeWriter, g as Plugin, h as MaybePromise, i as CodegenReferencedComponent, j as ResolvedPublishOptions, k as ResolvePublishOptionsOverrides, l as encodeText, m as LoadedPlugin, n as CodegenClass, o as ResolvedPackageCodegenPlan, p as ICodeWriterConfig, r as CodegenMember, s as buildCodegenClasses, t as AUTO_GENERATED_CODE_MARK, u as publishCodeGeneration, v as PluginModule, w as HasOptionalSrc, x as CliPublishSettings, y as CliAtlasSettings } from "./codegen-
|
|
2
|
+
import { A as ResolvedPublishAtlasOptions, C as HasOptionalFont, D as RootProjectSettings, E as PublishDependency, M as publish, N as resolvePublishOptions, O as PublishOptions, S as ExtrasMap, T as HasOptionalUrl, _ as PluginManifest, a as PublishCodeGenerationOptions, b as CliCodeGenerationSettings, c as decodeText, d as resolvePackageCodegenPlan, f as CodeWriter, g as Plugin, h as MaybePromise, i as CodegenReferencedComponent, j as ResolvedPublishOptions, k as ResolvePublishOptionsOverrides, l as encodeText, m as LoadedPlugin, n as CodegenClass, o as ResolvedPackageCodegenPlan, p as ICodeWriterConfig, r as CodegenMember, s as buildCodegenClasses, t as AUTO_GENERATED_CODE_MARK, u as publishCodeGeneration, v as PluginModule, w as HasOptionalSrc, x as CliPublishSettings, y as CliAtlasSettings } from "./codegen-CfbDHuFt.js";
|
|
3
3
|
import { ApplyUamTransactionAppDiagnostic, ApplyUamTransactionAppError, ApplyUamTransactionAppInput, ApplyUamTransactionAppResult, applyUamTransactionApp } from "./uam-transaction.js";
|
|
4
4
|
import { Document, FileSystem, Transform } from "@openfairygui/core";
|
|
5
5
|
|
|
@@ -194,10 +194,11 @@ interface RestoreFileSystem extends Pick<FileSystem, 'readFile' | 'readFileRaw'
|
|
|
194
194
|
readdir(path: string): Promise<string[]>;
|
|
195
195
|
isFile(path: string): Promise<boolean>;
|
|
196
196
|
resolvePath(path: string): string | Promise<string>;
|
|
197
|
-
rm
|
|
197
|
+
rm(path: string, options?: {
|
|
198
198
|
recursive?: boolean;
|
|
199
199
|
force?: boolean;
|
|
200
|
-
})
|
|
200
|
+
}): Promise<void>;
|
|
201
|
+
rename(from: string, to: string): Promise<void>;
|
|
201
202
|
}
|
|
202
203
|
interface RestoreOptions {
|
|
203
204
|
inputDir: string;
|