@openfairygui/cli 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +328 -54
- package/package.json +7 -4
package/dist/cli.mjs
CHANGED
|
@@ -745,10 +745,10 @@ var Property = class extends GraphNode {
|
|
|
745
745
|
return this.set("name", name);
|
|
746
746
|
}
|
|
747
747
|
getExtras() {
|
|
748
|
-
return this.get("extras");
|
|
748
|
+
return structuredClone(this.get("extras"));
|
|
749
749
|
}
|
|
750
750
|
setExtras(extras) {
|
|
751
|
-
return this.set("extras", extras);
|
|
751
|
+
return this.set("extras", structuredClone(extras));
|
|
752
752
|
}
|
|
753
753
|
clone() {
|
|
754
754
|
const PropertyClass = this.constructor;
|
|
@@ -829,7 +829,7 @@ var ExtensibleProperty = class extends Property {
|
|
|
829
829
|
};
|
|
830
830
|
//#endregion
|
|
831
831
|
//#region ../core/src/constants.ts
|
|
832
|
-
const VERSION = `v0.3.
|
|
832
|
+
const VERSION = `v0.3.1`;
|
|
833
833
|
/** Binary package file magic number: "FGUI" as uint32. */
|
|
834
834
|
const FGUI_MAGIC = 1179080009;
|
|
835
835
|
/** Null string index in the binary string table. */
|
|
@@ -1219,10 +1219,10 @@ var Root = class extends ExtensibleProperty {
|
|
|
1219
1219
|
return this.setBranches([...this.listBranches(), branch]);
|
|
1220
1220
|
}
|
|
1221
1221
|
getSettings() {
|
|
1222
|
-
return this.get("settings");
|
|
1222
|
+
return structuredClone(this.get("settings"));
|
|
1223
1223
|
}
|
|
1224
1224
|
setSettings(settings) {
|
|
1225
|
-
return this.set("settings", settings);
|
|
1225
|
+
return this.set("settings", structuredClone(settings));
|
|
1226
1226
|
}
|
|
1227
1227
|
/****** Extensions ******/
|
|
1228
1228
|
listExtensionsUsed() {
|
|
@@ -2263,9 +2263,24 @@ var DragonBonesResource = class extends SkeletonResourceBase {
|
|
|
2263
2263
|
* @category Properties
|
|
2264
2264
|
*/
|
|
2265
2265
|
var Component = class extends ExtensibleProperty {
|
|
2266
|
+
_binaryDirty = true;
|
|
2266
2267
|
init() {
|
|
2267
2268
|
this.propertyType = PropertyType.COMPONENT;
|
|
2268
2269
|
}
|
|
2270
|
+
/** @internal */
|
|
2271
|
+
_markBinaryClean() {
|
|
2272
|
+
this._binaryDirty = false;
|
|
2273
|
+
return this;
|
|
2274
|
+
}
|
|
2275
|
+
/** @internal */
|
|
2276
|
+
_markBinaryDirty() {
|
|
2277
|
+
this._binaryDirty = true;
|
|
2278
|
+
return this;
|
|
2279
|
+
}
|
|
2280
|
+
/** @internal */
|
|
2281
|
+
_isBinaryDirty() {
|
|
2282
|
+
return this._binaryDirty;
|
|
2283
|
+
}
|
|
2269
2284
|
getDefaults() {
|
|
2270
2285
|
return Object.assign(super.getDefaults(), {
|
|
2271
2286
|
id: "",
|
|
@@ -15784,7 +15799,6 @@ const { Deflate, deflate, deflateRaw, gzip } = deflate_1$1;
|
|
|
15784
15799
|
const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1;
|
|
15785
15800
|
var deflateRaw_1 = deflateRaw;
|
|
15786
15801
|
var Inflate_1 = Inflate;
|
|
15787
|
-
var inflateRaw_1 = inflateRaw;
|
|
15788
15802
|
//#endregion
|
|
15789
15803
|
//#region ../core/src/utils/image-info.ts
|
|
15790
15804
|
var import_jpeg_js = require_jpeg_js();
|
|
@@ -16180,6 +16194,99 @@ function probeRasterImageDimensions(data) {
|
|
|
16180
16194
|
return readPngInfo(data, false) ?? readJpegInfo(data, false);
|
|
16181
16195
|
}
|
|
16182
16196
|
//#endregion
|
|
16197
|
+
//#region ../core/src/utils/svg-validation.ts
|
|
16198
|
+
const MAX_SVG_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
16199
|
+
const MAX_SVG_NODES = 5e4;
|
|
16200
|
+
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
|
|
16201
|
+
const XLINK_NAMESPACE = "http://www.w3.org/1999/xlink";
|
|
16202
|
+
const UNSAFE_SVG_ELEMENTS = new Set([
|
|
16203
|
+
"a",
|
|
16204
|
+
"animate",
|
|
16205
|
+
"animatecolor",
|
|
16206
|
+
"animatemotion",
|
|
16207
|
+
"animatetransform",
|
|
16208
|
+
"audio",
|
|
16209
|
+
"canvas",
|
|
16210
|
+
"discard",
|
|
16211
|
+
"embed",
|
|
16212
|
+
"feimage",
|
|
16213
|
+
"foreignobject",
|
|
16214
|
+
"iframe",
|
|
16215
|
+
"image",
|
|
16216
|
+
"object",
|
|
16217
|
+
"script",
|
|
16218
|
+
"set",
|
|
16219
|
+
"style",
|
|
16220
|
+
"video"
|
|
16221
|
+
]);
|
|
16222
|
+
function invalidSvg(message) {
|
|
16223
|
+
throw new Error(`Invalid or unsafe SVG source (${message}).`);
|
|
16224
|
+
}
|
|
16225
|
+
function validateAttribute(name, value) {
|
|
16226
|
+
const normalizedName = name.toLowerCase();
|
|
16227
|
+
if (normalizedName === "xmlns") {
|
|
16228
|
+
if (value !== SVG_NAMESPACE) invalidSvg("the default namespace must be SVG");
|
|
16229
|
+
return;
|
|
16230
|
+
}
|
|
16231
|
+
if (normalizedName === "xmlns:xlink") {
|
|
16232
|
+
if (value !== XLINK_NAMESPACE) invalidSvg("the xlink namespace is invalid");
|
|
16233
|
+
return;
|
|
16234
|
+
}
|
|
16235
|
+
if (normalizedName.includes(":") && normalizedName !== "xlink:href" && normalizedName !== "xml:space") invalidSvg(`qualified attribute "${name}" is not allowed`);
|
|
16236
|
+
const localName = normalizedName.split(":").at(-1);
|
|
16237
|
+
const text = String(value);
|
|
16238
|
+
if (localName.startsWith("on")) invalidSvg(`event attribute "${name}" is not allowed`);
|
|
16239
|
+
if (localName === "style" || localName === "src") invalidSvg(`attribute "${name}" is not allowed`);
|
|
16240
|
+
if (localName === "href" && !/^#[A-Za-z_][\w:.-]*$/u.test(text)) invalidSvg(`external reference in "${name}" is not allowed`);
|
|
16241
|
+
if (/(?:^|[\s("'=])(?:https?:|file:|javascript:|data:|\/\/)/iu.test(text)) invalidSvg(`external URL in "${name}" is not allowed`);
|
|
16242
|
+
for (const match of text.matchAll(/url\s*\(([^)]*)\)/giu)) {
|
|
16243
|
+
const reference = (match[1] ?? "").trim().replace(/^(['"])(.*)\1$/u, "$2");
|
|
16244
|
+
if (!/^#[A-Za-z_][\w:.-]*$/u.test(reference)) invalidSvg(`external url() in "${name}" is not allowed`);
|
|
16245
|
+
}
|
|
16246
|
+
}
|
|
16247
|
+
/** Validate SVG bytes before handing them to a host image decoder. */
|
|
16248
|
+
function validateSafeSvgSource(bytes) {
|
|
16249
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_SVG_SOURCE_BYTES) invalidSvg("source size is unsupported");
|
|
16250
|
+
let source;
|
|
16251
|
+
try {
|
|
16252
|
+
source = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
16253
|
+
} catch {
|
|
16254
|
+
invalidSvg("source is not valid UTF-8");
|
|
16255
|
+
}
|
|
16256
|
+
if (/<!\s*(?:doctype|entity)\b|<\?xml-stylesheet\b/iu.test(source)) invalidSvg("DTD, entities, and stylesheets are not allowed");
|
|
16257
|
+
if (XMLValidator.validate(source, { allowBooleanAttributes: false }) !== true) invalidSvg("source is not well-formed XML");
|
|
16258
|
+
const roots = new XMLParser({
|
|
16259
|
+
preserveOrder: true,
|
|
16260
|
+
ignoreAttributes: false,
|
|
16261
|
+
attributeNamePrefix: "",
|
|
16262
|
+
parseAttributeValue: false,
|
|
16263
|
+
parseTagValue: false,
|
|
16264
|
+
processEntities: false,
|
|
16265
|
+
trimValues: false
|
|
16266
|
+
}).parse(source).flatMap((entry) => Object.keys(entry).filter((name) => name !== ":@" && !name.startsWith("#") && !name.startsWith("?")).map((name) => ({
|
|
16267
|
+
entry,
|
|
16268
|
+
name
|
|
16269
|
+
})));
|
|
16270
|
+
if (roots.length !== 1 || roots[0].name !== "svg") invalidSvg("a single unqualified <svg> root is required");
|
|
16271
|
+
if (roots[0].entry[":@"]?.xmlns !== SVG_NAMESPACE) invalidSvg("the SVG namespace is required");
|
|
16272
|
+
const pending = [roots[0].entry];
|
|
16273
|
+
let nodeCount = 0;
|
|
16274
|
+
while (pending.length > 0) {
|
|
16275
|
+
const current = pending.pop();
|
|
16276
|
+
for (const [name, value] of Object.entries(current)) {
|
|
16277
|
+
if (name === ":@" || name.startsWith("#") || name.startsWith("?")) continue;
|
|
16278
|
+
if (++nodeCount > MAX_SVG_NODES) invalidSvg("node count exceeds the supported limit");
|
|
16279
|
+
if (name.includes(":")) invalidSvg(`qualified element <${name}> is not allowed`);
|
|
16280
|
+
if (UNSAFE_SVG_ELEMENTS.has(name.toLowerCase())) invalidSvg(`element <${name}> is not allowed`);
|
|
16281
|
+
for (const [attributeName, attributeValue] of Object.entries(current[":@"] ?? {})) validateAttribute(attributeName, attributeValue);
|
|
16282
|
+
if (Array.isArray(value)) {
|
|
16283
|
+
for (const child of value) if (child && typeof child === "object" && !Array.isArray(child)) pending.push(child);
|
|
16284
|
+
}
|
|
16285
|
+
}
|
|
16286
|
+
}
|
|
16287
|
+
return source;
|
|
16288
|
+
}
|
|
16289
|
+
//#endregion
|
|
16183
16290
|
//#region ../core/src/document.ts
|
|
16184
16291
|
/**
|
|
16185
16292
|
* Wraps a FairyGUI project and its resources for easier modification.
|
|
@@ -16206,12 +16313,32 @@ var Document = class Document {
|
|
|
16206
16313
|
_root = new Root(this._graph);
|
|
16207
16314
|
_logger = Logger.DEFAULT_INSTANCE;
|
|
16208
16315
|
_projectDir = "";
|
|
16316
|
+
_hasBinaryComponents = false;
|
|
16209
16317
|
static _GRAPH_DOCUMENTS = /* @__PURE__ */ new WeakMap();
|
|
16210
16318
|
static fromGraph(graph) {
|
|
16211
16319
|
return Document._GRAPH_DOCUMENTS.get(graph) || null;
|
|
16212
16320
|
}
|
|
16213
16321
|
constructor() {
|
|
16214
16322
|
Document._GRAPH_DOCUMENTS.set(this._graph, this);
|
|
16323
|
+
this._graph.addEventListener("node:change", (event) => {
|
|
16324
|
+
if (!this._hasBinaryComponents) return;
|
|
16325
|
+
const pending = [event.target];
|
|
16326
|
+
const visited = /* @__PURE__ */ new Set();
|
|
16327
|
+
while (pending.length > 0) {
|
|
16328
|
+
const current = pending.pop();
|
|
16329
|
+
if (visited.has(current)) continue;
|
|
16330
|
+
visited.add(current);
|
|
16331
|
+
if (current instanceof Component) {
|
|
16332
|
+
current._markBinaryDirty();
|
|
16333
|
+
continue;
|
|
16334
|
+
}
|
|
16335
|
+
pending.push(...current.listParents());
|
|
16336
|
+
}
|
|
16337
|
+
});
|
|
16338
|
+
}
|
|
16339
|
+
/** @internal */
|
|
16340
|
+
_trackBinaryComponent() {
|
|
16341
|
+
this._hasBinaryComponents = true;
|
|
16215
16342
|
}
|
|
16216
16343
|
getRoot() {
|
|
16217
16344
|
return this._root;
|
|
@@ -17212,16 +17339,8 @@ function sourceExtension(resource) {
|
|
|
17212
17339
|
}
|
|
17213
17340
|
function validSvg(bytes) {
|
|
17214
17341
|
try {
|
|
17215
|
-
|
|
17216
|
-
|
|
17217
|
-
if (XMLValidator.validate(source, { allowBooleanAttributes: false }) !== true) return false;
|
|
17218
|
-
if (!/<svg(?:\s|>)/i.test(source)) return false;
|
|
17219
|
-
if (/<!\s*(?:doctype|entity)\b|<\?xml-stylesheet\b/iu.test(source)) return false;
|
|
17220
|
-
if (/<(?:a|animate|animatecolor|animatemotion|animatetransform|audio|canvas|discard|embed|feimage|foreignobject|iframe|image|object|script|set|style|video)(?:\s|>)/iu.test(source)) return false;
|
|
17221
|
-
if (/\s(?:on[\w:-]*|style|src)\s*=/iu.test(source)) return false;
|
|
17222
|
-
if (/\s(?:href|xlink:href)\s*=\s*['"](?!#[A-Za-z_][\w:.-]*['"])/iu.test(source)) return false;
|
|
17223
|
-
const urlSource = source.replace(/\sxmlns\s*=\s*(['"])http:\/\/www\.w3\.org\/2000\/svg\1/giu, "");
|
|
17224
|
-
return !/(?:https?:|file:|javascript:|data:|\/\/)|url\s*\(\s*(?!['"]?#[A-Za-z_])/iu.test(urlSource);
|
|
17342
|
+
validateSafeSvgSource(bytes);
|
|
17343
|
+
return true;
|
|
17225
17344
|
} catch {
|
|
17226
17345
|
return false;
|
|
17227
17346
|
}
|
|
@@ -23309,7 +23428,11 @@ var ProjectWriter = class {
|
|
|
23309
23428
|
if (settings[key] === void 0 && await fs.exists(filePath)) staleOptionalSettings.push(filePath);
|
|
23310
23429
|
}
|
|
23311
23430
|
if (staleOptionalSettings.length > 0 && !fs.unlink) throw new Error("Project settings cleanup requires a FileSystem.unlink() implementation.");
|
|
23312
|
-
const fairyXml = `<?xml version="1.0" encoding="utf-8"?>\n<projectDescription
|
|
23431
|
+
const fairyXml = `<?xml version="1.0" encoding="utf-8"?>\n<projectDescription${renderXmlAttrs({
|
|
23432
|
+
id: root.getProjectId(),
|
|
23433
|
+
type: this._projectTypeName(root.getProjectType()),
|
|
23434
|
+
version: root.getVersion() || "3.0"
|
|
23435
|
+
})}/>\n`;
|
|
23313
23436
|
await fs.writeFile(projectPath, fairyXml);
|
|
23314
23437
|
await fs.mkdir(settingsPath);
|
|
23315
23438
|
for (const [fileName, key] of Object.entries({
|
|
@@ -24858,6 +24981,41 @@ function decodeComponentDefinition(resource, rawData, extensionTypeCode, doc) {
|
|
|
24858
24981
|
}
|
|
24859
24982
|
//#endregion
|
|
24860
24983
|
//#region ../core/src/io/binary-reader.ts
|
|
24984
|
+
const DEFAULT_BINARY_READ_LIMITS = {
|
|
24985
|
+
maxCompressedBytes: 64 * 1024 * 1024,
|
|
24986
|
+
maxDecompressedBytes: 256 * 1024 * 1024,
|
|
24987
|
+
maxCompressionRatio: 200
|
|
24988
|
+
};
|
|
24989
|
+
function readLimits(options) {
|
|
24990
|
+
const limits = {
|
|
24991
|
+
...DEFAULT_BINARY_READ_LIMITS,
|
|
24992
|
+
...options.limits
|
|
24993
|
+
};
|
|
24994
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isFinite(value) || value <= 0) throw new RangeError(`${name} must be a positive finite number.`);
|
|
24995
|
+
return limits;
|
|
24996
|
+
}
|
|
24997
|
+
function inflateRawWithLimits(input, limits) {
|
|
24998
|
+
if (input.byteLength > limits.maxCompressedBytes) throw new Error(`FairyGUI binary compressed data exceeds ${limits.maxCompressedBytes} bytes.`);
|
|
24999
|
+
const maxOutputBytes = Math.min(limits.maxDecompressedBytes, Math.floor(input.byteLength * limits.maxCompressionRatio));
|
|
25000
|
+
const chunks = [];
|
|
25001
|
+
let outputLength = 0;
|
|
25002
|
+
const inflater = new Inflate_1({ raw: true });
|
|
25003
|
+
inflater.onData = (chunk) => {
|
|
25004
|
+
if (!(chunk instanceof Uint8Array)) throw new Error("FairyGUI binary inflate returned non-binary data.");
|
|
25005
|
+
outputLength += chunk.byteLength;
|
|
25006
|
+
if (outputLength > maxOutputBytes) throw new Error(`FairyGUI binary decompressed data exceeds the configured ${maxOutputBytes} byte budget.`);
|
|
25007
|
+
chunks.push(chunk);
|
|
25008
|
+
};
|
|
25009
|
+
inflater.push(input, true);
|
|
25010
|
+
if (inflater.err !== 0) throw new Error(`Invalid compressed FairyGUI binary data: ${inflater.msg}`);
|
|
25011
|
+
const output = new Uint8Array(outputLength);
|
|
25012
|
+
let offset = 0;
|
|
25013
|
+
for (const chunk of chunks) {
|
|
25014
|
+
output.set(chunk, offset);
|
|
25015
|
+
offset += chunk.byteLength;
|
|
25016
|
+
}
|
|
25017
|
+
return output;
|
|
25018
|
+
}
|
|
24861
25019
|
/**
|
|
24862
25020
|
* Binary item type codes as used in the .fui format.
|
|
24863
25021
|
* @internal
|
|
@@ -24958,7 +25116,7 @@ function decodeFontGlyphs(doc, resource, buf) {
|
|
|
24958
25116
|
for (let index = 0; index < glyphCount; index += 1) {
|
|
24959
25117
|
const chunkSize = buf.getInt16();
|
|
24960
25118
|
const nextPos = buf.pos + chunkSize;
|
|
24961
|
-
const charId = buf.
|
|
25119
|
+
const charId = buf.getUint16();
|
|
24962
25120
|
const glyph = doc.createFontGlyph(`${resource.getId()}_${charId || index}`);
|
|
24963
25121
|
glyph.setCharId(charId).setChar(decodeChar(charId)).setImg(buf.readS() ?? "").setX(buf.getInt32()).setY(buf.getInt32()).setXOffset(buf.getInt32()).setYOffset(buf.getInt32()).setWidth(buf.getInt32()).setHeight(buf.getInt32()).setAdvance(buf.getInt32()).setChannel(buf.getUint8());
|
|
24964
25122
|
resource.addGlyph(glyph);
|
|
@@ -24977,8 +25135,10 @@ function decodeFontGlyphs(doc, resource, buf) {
|
|
|
24977
25135
|
*/
|
|
24978
25136
|
var BinaryReader = class {
|
|
24979
25137
|
_fs;
|
|
24980
|
-
|
|
25138
|
+
_limits;
|
|
25139
|
+
constructor(fs, options = {}) {
|
|
24981
25140
|
this._fs = fs;
|
|
25141
|
+
this._limits = readLimits(options);
|
|
24982
25142
|
}
|
|
24983
25143
|
async read(filePath) {
|
|
24984
25144
|
const doc = new Document();
|
|
@@ -25004,9 +25164,12 @@ var BinaryReader = class {
|
|
|
25004
25164
|
outer.skip(20);
|
|
25005
25165
|
let buf;
|
|
25006
25166
|
if (compressed) {
|
|
25007
|
-
const decompressed =
|
|
25167
|
+
const decompressed = inflateRawWithLimits(new Uint8Array(outer.buffer, outer.byteOffset + outer.pos, outer.byteLength - outer.pos), this._limits);
|
|
25008
25168
|
buf = new ByteBuffer(decompressed.buffer, 0, decompressed.byteLength);
|
|
25009
|
-
} else
|
|
25169
|
+
} else {
|
|
25170
|
+
if (outer.byteLength - outer.pos > this._limits.maxDecompressedBytes) throw new Error(`FairyGUI binary data exceeds ${this._limits.maxDecompressedBytes} bytes.`);
|
|
25171
|
+
buf = outer;
|
|
25172
|
+
}
|
|
25010
25173
|
buf.version = outer.version;
|
|
25011
25174
|
const indexTablePos = buf.pos;
|
|
25012
25175
|
const ver2 = buf.version >= 2;
|
|
@@ -25135,6 +25298,8 @@ var BinaryReader = class {
|
|
|
25135
25298
|
...getComponentExtras(res),
|
|
25136
25299
|
_rawBinary: toRawBinarySlice(rawData)
|
|
25137
25300
|
});
|
|
25301
|
+
res._markBinaryClean();
|
|
25302
|
+
doc._trackBinaryComponent();
|
|
25138
25303
|
pkg.addResource(res);
|
|
25139
25304
|
createdResource = res;
|
|
25140
25305
|
break;
|
|
@@ -25286,7 +25451,7 @@ var BinaryReader = class {
|
|
|
25286
25451
|
*
|
|
25287
25452
|
* @internal
|
|
25288
25453
|
*/
|
|
25289
|
-
var WriteBuffer = class {
|
|
25454
|
+
var WriteBuffer = class WriteBuffer {
|
|
25290
25455
|
_buf;
|
|
25291
25456
|
_view;
|
|
25292
25457
|
_pos = 0;
|
|
@@ -25296,6 +25461,9 @@ var WriteBuffer = class {
|
|
|
25296
25461
|
_strings;
|
|
25297
25462
|
/** Raw custom strings written to block 5, keyed by string table index. */
|
|
25298
25463
|
_customStrings;
|
|
25464
|
+
static _assertInteger(value, min, max, type) {
|
|
25465
|
+
if (!Number.isSafeInteger(value) || value < min || value > max) throw new RangeError(`${type} value is out of range: ${value}`);
|
|
25466
|
+
}
|
|
25299
25467
|
constructor(initialSize = 4096, parent) {
|
|
25300
25468
|
this._buf = new ArrayBuffer(initialSize);
|
|
25301
25469
|
this._view = new DataView(this._buf);
|
|
@@ -25330,34 +25498,41 @@ var WriteBuffer = class {
|
|
|
25330
25498
|
this._view = new DataView(this._buf);
|
|
25331
25499
|
}
|
|
25332
25500
|
writeUint8(v) {
|
|
25501
|
+
WriteBuffer._assertInteger(v, 0, 255, "uint8");
|
|
25333
25502
|
this._ensure(1);
|
|
25334
25503
|
this._view.setUint8(this._pos++, v);
|
|
25335
25504
|
}
|
|
25336
25505
|
writeInt8(v) {
|
|
25506
|
+
WriteBuffer._assertInteger(v, -128, 127, "int8");
|
|
25337
25507
|
this._ensure(1);
|
|
25338
25508
|
this._view.setInt8(this._pos++, v);
|
|
25339
25509
|
}
|
|
25340
25510
|
writeUint16(v) {
|
|
25511
|
+
WriteBuffer._assertInteger(v, 0, 65535, "uint16");
|
|
25341
25512
|
this._ensure(2);
|
|
25342
25513
|
this._view.setUint16(this._pos, v, false);
|
|
25343
25514
|
this._pos += 2;
|
|
25344
25515
|
}
|
|
25345
25516
|
writeInt16(v) {
|
|
25517
|
+
WriteBuffer._assertInteger(v, -32768, 32767, "int16");
|
|
25346
25518
|
this._ensure(2);
|
|
25347
25519
|
this._view.setInt16(this._pos, v, false);
|
|
25348
25520
|
this._pos += 2;
|
|
25349
25521
|
}
|
|
25350
25522
|
writeUint32(v) {
|
|
25523
|
+
WriteBuffer._assertInteger(v, 0, 4294967295, "uint32");
|
|
25351
25524
|
this._ensure(4);
|
|
25352
25525
|
this._view.setUint32(this._pos, v, false);
|
|
25353
25526
|
this._pos += 4;
|
|
25354
25527
|
}
|
|
25355
25528
|
writeInt32(v) {
|
|
25529
|
+
WriteBuffer._assertInteger(v, -2147483648, 2147483647, "int32");
|
|
25356
25530
|
this._ensure(4);
|
|
25357
25531
|
this._view.setInt32(this._pos, v, false);
|
|
25358
25532
|
this._pos += 4;
|
|
25359
25533
|
}
|
|
25360
25534
|
writeFloat32(v) {
|
|
25535
|
+
if (!Number.isFinite(v)) throw new RangeError(`float32 value must be finite: ${v}`);
|
|
25361
25536
|
this._ensure(4);
|
|
25362
25537
|
this._view.setFloat32(this._pos, v, false);
|
|
25363
25538
|
this._pos += 4;
|
|
@@ -25368,6 +25543,7 @@ var WriteBuffer = class {
|
|
|
25368
25543
|
/** Write a uint16-prefixed UTF-8 string. */
|
|
25369
25544
|
writeUTFString(s) {
|
|
25370
25545
|
const encoded = new TextEncoder().encode(s);
|
|
25546
|
+
if (encoded.byteLength > 65535) throw new RangeError(`UTF string exceeds uint16 byte length: ${encoded.byteLength}`);
|
|
25371
25547
|
this.writeUint16(encoded.byteLength);
|
|
25372
25548
|
this._ensure(encoded.byteLength);
|
|
25373
25549
|
new Uint8Array(this._buf, this._pos, encoded.byteLength).set(encoded);
|
|
@@ -25394,6 +25570,7 @@ var WriteBuffer = class {
|
|
|
25394
25570
|
const existing = this._stringMap.get(s);
|
|
25395
25571
|
if (existing !== void 0) return existing;
|
|
25396
25572
|
const index = this._strings.length;
|
|
25573
|
+
if (index >= 65533) throw new RangeError(`String table exceeds protocol index limit: ${index + 1}`);
|
|
25397
25574
|
this._strings.push(s);
|
|
25398
25575
|
this._stringMap.set(s, index);
|
|
25399
25576
|
return index;
|
|
@@ -25430,6 +25607,7 @@ var WriteBuffer = class {
|
|
|
25430
25607
|
if (!noCache) this.writeUint16(this.addString(s));
|
|
25431
25608
|
else {
|
|
25432
25609
|
const index = this._strings.length;
|
|
25610
|
+
if (index >= 65533) throw new RangeError(`String table exceeds protocol index limit: ${index + 1}`);
|
|
25433
25611
|
this._strings.push(s);
|
|
25434
25612
|
this.writeUint16(index);
|
|
25435
25613
|
}
|
|
@@ -27236,7 +27414,7 @@ var BinaryWriter = class {
|
|
|
27236
27414
|
const compExtras = res.getExtras();
|
|
27237
27415
|
const extType = res.getExtensionType?.() ?? compExtras.extensionType;
|
|
27238
27416
|
data.writeUint8(extType ? extTypeMap[extType] ?? 0 : 0);
|
|
27239
|
-
if (compExtras?._rawBinary) data.writeBuffer(toUint8Array(compExtras._rawBinary));
|
|
27417
|
+
if (compExtras?._rawBinary && !res._isBinaryDirty()) data.writeBuffer(toUint8Array(compExtras._rawBinary));
|
|
27240
27418
|
else {
|
|
27241
27419
|
const encoded = encodeComponent(res, doc, pkg, version, data);
|
|
27242
27420
|
data.writeBuffer(encoded);
|
|
@@ -27525,7 +27703,7 @@ function _encodeFontGlyphs(fntData, parentBuf) {
|
|
|
27525
27703
|
for (const glyph of fntData.glyphs) {
|
|
27526
27704
|
const glyphStart = buf.pos;
|
|
27527
27705
|
buf.writeInt16(0);
|
|
27528
|
-
buf.
|
|
27706
|
+
buf.writeUint16(glyph.charId);
|
|
27529
27707
|
buf.writeS(glyph.img);
|
|
27530
27708
|
buf.writeInt32(glyph.x);
|
|
27531
27709
|
buf.writeInt32(glyph.y);
|
|
@@ -27659,8 +27837,8 @@ var PlatformIO = class {
|
|
|
27659
27837
|
async writeProject(doc, projectPath, options) {
|
|
27660
27838
|
return new ProjectWriter(this.createFileSystem()).write(doc, projectPath, options);
|
|
27661
27839
|
}
|
|
27662
|
-
async readBinary(filePath) {
|
|
27663
|
-
return new BinaryReader(this.createFileSystem()).read(filePath);
|
|
27840
|
+
async readBinary(filePath, options) {
|
|
27841
|
+
return new BinaryReader(this.createFileSystem(), options).read(filePath);
|
|
27664
27842
|
}
|
|
27665
27843
|
async writeBinary(doc, filePath, options) {
|
|
27666
27844
|
return new BinaryWriter(this.createFileSystem()).write(doc, filePath, options);
|
|
@@ -30107,6 +30285,9 @@ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
|
|
|
30107
30285
|
function formatPluginError(error) {
|
|
30108
30286
|
return error instanceof Error ? error.message : String(error);
|
|
30109
30287
|
}
|
|
30288
|
+
function shouldAbortPluginFailure(plugin) {
|
|
30289
|
+
return plugin.failureMode !== "warn";
|
|
30290
|
+
}
|
|
30110
30291
|
//#endregion
|
|
30111
30292
|
//#region ../functions/src/path-utils.ts
|
|
30112
30293
|
function trimTrailingSlashes(value) {
|
|
@@ -30192,7 +30373,9 @@ async function publishCodeGeneration(doc, options) {
|
|
|
30192
30373
|
handled = true;
|
|
30193
30374
|
logger.info(`publish: Generated code using plugin "${plugin.name}"`);
|
|
30194
30375
|
} catch (error) {
|
|
30195
|
-
|
|
30376
|
+
const message = `publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`;
|
|
30377
|
+
if (shouldAbortPluginFailure(plugin)) throw new Error(message);
|
|
30378
|
+
logger.warn(message);
|
|
30196
30379
|
}
|
|
30197
30380
|
}
|
|
30198
30381
|
if (handled) return;
|
|
@@ -31574,7 +31757,9 @@ async function runPublishPluginHook(plugins, hook, doc, options) {
|
|
|
31574
31757
|
try {
|
|
31575
31758
|
await fn(doc, options);
|
|
31576
31759
|
} catch (error) {
|
|
31577
|
-
|
|
31760
|
+
const message = `publish: Plugin "${plugin.name}" ${hook} failed: ${formatPluginError(error)}`;
|
|
31761
|
+
if (shouldAbortPluginFailure(plugin)) throw new Error(message);
|
|
31762
|
+
logger.warn(message);
|
|
31578
31763
|
}
|
|
31579
31764
|
}
|
|
31580
31765
|
}
|
|
@@ -31866,15 +32051,9 @@ var NodeIO = class extends PlatformIO {
|
|
|
31866
32051
|
},
|
|
31867
32052
|
async readdir(dirPath) {
|
|
31868
32053
|
const entries = await fs$1.readdir(dirPath, { withFileTypes: true });
|
|
31869
|
-
|
|
31870
|
-
|
|
31871
|
-
|
|
31872
|
-
try {
|
|
31873
|
-
return (await fs$1.stat(path$1.join(dirPath, entry.name))).isDirectory() ? entry.name : null;
|
|
31874
|
-
} catch {
|
|
31875
|
-
return null;
|
|
31876
|
-
}
|
|
31877
|
-
}))).filter((entry) => entry !== null);
|
|
32054
|
+
const symlink = entries.find((entry) => entry.isSymbolicLink());
|
|
32055
|
+
if (symlink) throw new Error(`Symbolic links are not supported in project directories: ${path$1.join(dirPath, symlink.name)}`);
|
|
32056
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
31878
32057
|
},
|
|
31879
32058
|
async exists(filePath) {
|
|
31880
32059
|
try {
|
|
@@ -31959,16 +32138,33 @@ async function loadPlugins(doc, pluginsDir) {
|
|
|
31959
32138
|
for (const entry of entries) {
|
|
31960
32139
|
if (!entry.isDirectory()) continue;
|
|
31961
32140
|
const pluginDir = path.join(pluginsDir, entry.name);
|
|
32141
|
+
let manifest;
|
|
32142
|
+
try {
|
|
32143
|
+
manifest = await readPluginManifest(fs, path, pluginDir);
|
|
32144
|
+
} catch (error) {
|
|
32145
|
+
doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
|
|
32146
|
+
continue;
|
|
32147
|
+
}
|
|
32148
|
+
if (!manifest) continue;
|
|
32149
|
+
if (!manifest.main) {
|
|
32150
|
+
const error = /* @__PURE__ */ new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
|
|
32151
|
+
if (manifest.required) throw error;
|
|
32152
|
+
doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${error.message}`);
|
|
32153
|
+
continue;
|
|
32154
|
+
}
|
|
31962
32155
|
try {
|
|
31963
|
-
const manifest = await readPluginManifest(fs, path, pluginDir);
|
|
31964
|
-
if (!manifest) continue;
|
|
31965
32156
|
const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
|
|
31966
32157
|
plugins.push({
|
|
31967
32158
|
name: manifest.name,
|
|
31968
|
-
plugin
|
|
32159
|
+
plugin,
|
|
32160
|
+
failureMode: manifest.required ? "abort" : manifest.failureMode
|
|
31969
32161
|
});
|
|
31970
32162
|
} catch (error) {
|
|
31971
|
-
|
|
32163
|
+
if (!manifest.required && manifest.failureMode === "warn") {
|
|
32164
|
+
doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${formatPluginError(error)}`);
|
|
32165
|
+
continue;
|
|
32166
|
+
}
|
|
32167
|
+
throw new Error(`publish: Failed to load plugin "${manifest.name}": ${formatPluginError(error)}`);
|
|
31972
32168
|
}
|
|
31973
32169
|
}
|
|
31974
32170
|
return plugins;
|
|
@@ -31978,7 +32174,6 @@ async function readPluginManifest(fs, path, pluginDir) {
|
|
|
31978
32174
|
const content = await fs.readFile(manifestPath, "utf-8");
|
|
31979
32175
|
const manifest = JSON.parse(content);
|
|
31980
32176
|
if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
|
|
31981
|
-
if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
|
|
31982
32177
|
return manifest;
|
|
31983
32178
|
}
|
|
31984
32179
|
function resolvePluginMain(path, pluginDir, manifest) {
|
|
@@ -32043,6 +32238,77 @@ async function loadNodePublishPlugins(document, assetsPath) {
|
|
|
32043
32238
|
if (!projectDir) return [];
|
|
32044
32239
|
return loadPlugins(document, (await importNative$2("node:path")).join(projectDir, "plugins"));
|
|
32045
32240
|
}
|
|
32241
|
+
async function publishToStagedOutput(output, run) {
|
|
32242
|
+
const [fs, path, { randomUUID }] = await Promise.all([
|
|
32243
|
+
importNative$2("node:fs/promises"),
|
|
32244
|
+
importNative$2("node:path"),
|
|
32245
|
+
importNative$2("node:crypto")
|
|
32246
|
+
]);
|
|
32247
|
+
const target = path.resolve(output);
|
|
32248
|
+
const parent = path.dirname(target);
|
|
32249
|
+
const name = path.basename(target);
|
|
32250
|
+
const staging = path.join(parent, `.${name}.publish-${randomUUID()}`);
|
|
32251
|
+
const backup = path.join(parent, `.${name}.publish-backup-${randomUUID()}`);
|
|
32252
|
+
await fs.mkdir(parent, { recursive: true });
|
|
32253
|
+
let existed = false;
|
|
32254
|
+
try {
|
|
32255
|
+
const stat = await fs.lstat(target);
|
|
32256
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`publishNode: output must be a regular directory: ${target}`);
|
|
32257
|
+
await assertNoSymlinks(fs, path, target);
|
|
32258
|
+
existed = true;
|
|
32259
|
+
} catch (error) {
|
|
32260
|
+
if (error.code !== "ENOENT") throw error;
|
|
32261
|
+
}
|
|
32262
|
+
try {
|
|
32263
|
+
if (existed) await fs.cp(target, staging, {
|
|
32264
|
+
recursive: true,
|
|
32265
|
+
errorOnExist: true,
|
|
32266
|
+
force: false
|
|
32267
|
+
});
|
|
32268
|
+
else await fs.mkdir(staging);
|
|
32269
|
+
} catch (error) {
|
|
32270
|
+
await fs.rm(staging, {
|
|
32271
|
+
recursive: true,
|
|
32272
|
+
force: true
|
|
32273
|
+
});
|
|
32274
|
+
throw error;
|
|
32275
|
+
}
|
|
32276
|
+
try {
|
|
32277
|
+
await run(staging);
|
|
32278
|
+
} catch (error) {
|
|
32279
|
+
await fs.rm(staging, {
|
|
32280
|
+
recursive: true,
|
|
32281
|
+
force: true
|
|
32282
|
+
});
|
|
32283
|
+
throw error;
|
|
32284
|
+
}
|
|
32285
|
+
if (!existed) {
|
|
32286
|
+
await fs.rename(staging, target);
|
|
32287
|
+
return;
|
|
32288
|
+
}
|
|
32289
|
+
await fs.rename(target, backup);
|
|
32290
|
+
try {
|
|
32291
|
+
await fs.rename(staging, target);
|
|
32292
|
+
} catch (error) {
|
|
32293
|
+
await fs.rename(backup, target);
|
|
32294
|
+
await fs.rm(staging, {
|
|
32295
|
+
recursive: true,
|
|
32296
|
+
force: true
|
|
32297
|
+
});
|
|
32298
|
+
throw error;
|
|
32299
|
+
}
|
|
32300
|
+
await fs.rm(backup, {
|
|
32301
|
+
recursive: true,
|
|
32302
|
+
force: true
|
|
32303
|
+
}).catch(() => void 0);
|
|
32304
|
+
}
|
|
32305
|
+
async function assertNoSymlinks(fs, path, directory) {
|
|
32306
|
+
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
|
|
32307
|
+
const entryPath = path.join(directory, entry.name);
|
|
32308
|
+
if (entry.isSymbolicLink()) throw new Error(`publishNode: symbolic links are not supported in output directories: ${entryPath}`);
|
|
32309
|
+
if (entry.isDirectory()) await assertNoSymlinks(fs, path, entryPath);
|
|
32310
|
+
}
|
|
32311
|
+
}
|
|
32046
32312
|
/**
|
|
32047
32313
|
* Publish a FairyGUI project through the standard Node host adapter.
|
|
32048
32314
|
*
|
|
@@ -32055,17 +32321,25 @@ async function publishNode(options) {
|
|
|
32055
32321
|
const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
|
|
32056
32322
|
const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
|
|
32057
32323
|
if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
|
|
32063
|
-
|
|
32064
|
-
|
|
32065
|
-
|
|
32066
|
-
|
|
32067
|
-
|
|
32068
|
-
|
|
32324
|
+
const run = async (output) => {
|
|
32325
|
+
await document.transform(publish({
|
|
32326
|
+
...publishOptions,
|
|
32327
|
+
output,
|
|
32328
|
+
basePath: assetsPath,
|
|
32329
|
+
encoder,
|
|
32330
|
+
atlas: {
|
|
32331
|
+
...atlas,
|
|
32332
|
+
readFileRaw: fileSystem.readFileRaw
|
|
32333
|
+
},
|
|
32334
|
+
fs: fileSystem,
|
|
32335
|
+
plugins
|
|
32336
|
+
}));
|
|
32337
|
+
};
|
|
32338
|
+
if (publishOptions.output) {
|
|
32339
|
+
await publishToStagedOutput(publishOptions.output, run);
|
|
32340
|
+
return;
|
|
32341
|
+
}
|
|
32342
|
+
await run(void 0);
|
|
32069
32343
|
}
|
|
32070
32344
|
//#endregion
|
|
32071
32345
|
//#region ../functions/src/adapters/node/restore.ts
|
|
@@ -32364,7 +32638,7 @@ function registerValidateCommand(program) {
|
|
|
32364
32638
|
//#region src/utils/package-version.ts
|
|
32365
32639
|
const require$1 = createRequire(import.meta.url);
|
|
32366
32640
|
function getInjectedPackageVersion() {
|
|
32367
|
-
const version = "0.3.
|
|
32641
|
+
const version = "0.3.1";
|
|
32368
32642
|
return typeof version === "string" && true ? version : null;
|
|
32369
32643
|
}
|
|
32370
32644
|
function readPackageVersion() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfairygui/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "FairyGUI Headless Authoring SDK — command-line interface.",
|
|
5
5
|
"author": "OpenFairyGUI Contributors",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/OpenFairyGUI/OpenFairyGUI/issues"
|
|
15
15
|
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
16
19
|
"type": "module",
|
|
17
20
|
"bin": {
|
|
18
21
|
"ofgui": "bin/cli.cjs",
|
|
@@ -33,13 +36,13 @@
|
|
|
33
36
|
"restore"
|
|
34
37
|
],
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@openfairygui/
|
|
37
|
-
"@openfairygui/
|
|
39
|
+
"@openfairygui/functions": "0.3.1",
|
|
40
|
+
"@openfairygui/core": "0.3.1"
|
|
38
41
|
},
|
|
39
42
|
"dependencies": {
|
|
40
43
|
"commander": "^14.0.2",
|
|
41
44
|
"jiti": "^2.7.0",
|
|
42
|
-
"@openfairygui/backend": "0.3.
|
|
45
|
+
"@openfairygui/backend": "0.3.1"
|
|
43
46
|
},
|
|
44
47
|
"optionalDependencies": {
|
|
45
48
|
"sharp": ">=0.33.0"
|