@openfairygui/functions 0.2.0 → 0.3.0-alpha.2

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/node.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { r as AtlasRasterBackend } from "./atlas-CHsu2Y8i.cjs";
2
2
  import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-BeWaJNjR.cjs";
3
- import { Document } from "@openfairygui/core";
3
+ import { Document, ProjectValidationReport } from "@openfairygui/core";
4
4
 
5
5
  //#region src/adapters/node/publish.d.ts
6
6
  interface PublishNodeOptions extends Omit<PublishOptions, 'atlas' | 'basePath' | 'encoder' | 'fs' | 'plugins'> {
@@ -33,4 +33,7 @@ interface RestoreNodeOptions extends Omit<RestoreOptions, 'fs' | 'cropImage' | '
33
33
  /** Restore trusted local published artifacts through the standard Node host adapter. */
34
34
  declare function restoreNode(options: RestoreNodeOptions): Promise<RestoreResult>;
35
35
  //#endregion
36
- export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode };
36
+ //#region src/adapters/node/validate.d.ts
37
+ declare function validateProjectNode(projectPath: string): Promise<ProjectValidationReport>;
38
+ //#endregion
39
+ export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode, validateProjectNode };
package/dist/node.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { r as AtlasRasterBackend } from "./atlas-C6tbl7nn.js";
2
2
  import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-Dh0-Nvms.js";
3
- import { Document } from "@openfairygui/core";
3
+ import { Document, ProjectValidationReport } from "@openfairygui/core";
4
4
 
5
5
  //#region src/adapters/node/publish.d.ts
6
6
  interface PublishNodeOptions extends Omit<PublishOptions, 'atlas' | 'basePath' | 'encoder' | 'fs' | 'plugins'> {
@@ -33,4 +33,7 @@ interface RestoreNodeOptions extends Omit<RestoreOptions, 'fs' | 'cropImage' | '
33
33
  /** Restore trusted local published artifacts through the standard Node host adapter. */
34
34
  declare function restoreNode(options: RestoreNodeOptions): Promise<RestoreResult>;
35
35
  //#endregion
36
- export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode };
36
+ //#region src/adapters/node/validate.d.ts
37
+ declare function validateProjectNode(projectPath: string): Promise<ProjectValidationReport>;
38
+ //#endregion
39
+ export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode, validateProjectNode };
package/dist/node.js CHANGED
@@ -1,11 +1,13 @@
1
- import { m as formatPluginError, t as publish, u as resolveProjectBasePath } from "./publish-DXoaC1Nl.js";
2
- import { t as restore } from "./restore-BQp01WY3.js";
1
+ import { _ as validateProject, m as formatPluginError, t as publish, u as resolveProjectBasePath } from "./publish-CyBj2o7n.js";
2
+ import { t as restore } from "./restore-DVo1hXpN.js";
3
+ import { createProjectValidationReport, liftDocumentToUamProject } from "@openfairygui/core";
4
+ import { NodeIO } from "@openfairygui/core/node";
3
5
  //#region src/adapters/node/plugins.ts
4
- const importNative$2 = new Function("id", "return import(id)");
6
+ const importNative$3 = new Function("id", "return import(id)");
5
7
  async function loadPlugins(doc, pluginsDir) {
6
8
  if (!pluginsDir) return [];
7
- const fs = await importNative$2("node:fs/promises");
8
- const path = await importNative$2("node:path");
9
+ const fs = await importNative$3("node:fs/promises");
10
+ const path = await importNative$3("node:path");
9
11
  let entries;
10
12
  try {
11
13
  entries = await fs.readdir(pluginsDir, { withFileTypes: true });
@@ -45,7 +47,7 @@ function resolvePluginMain(path, pluginDir, manifest) {
45
47
  return mainPath;
46
48
  }
47
49
  async function loadPlugin(mainPath) {
48
- const { createJiti } = await importNative$2("jiti");
50
+ const { createJiti } = await importNative$3("jiti");
49
51
  const mod = await createJiti(import.meta.url).import(mainPath);
50
52
  const defaultExport = mod.default;
51
53
  return isObject(defaultExport) ? defaultExport : mod;
@@ -55,9 +57,9 @@ function isObject(value) {
55
57
  }
56
58
  //#endregion
57
59
  //#region src/adapters/node/publish.ts
58
- const importNative$1 = new Function("id", "return import(id)");
60
+ const importNative$2 = new Function("id", "return import(id)");
59
61
  async function createNodePublishFileSystem() {
60
- const [fs, path] = await Promise.all([importNative$1("node:fs/promises"), importNative$1("node:path")]);
62
+ const [fs, path] = await Promise.all([importNative$2("node:fs/promises"), importNative$2("node:path")]);
61
63
  return {
62
64
  async readFileRaw(filePath) {
63
65
  const data = await fs.readFile(filePath);
@@ -85,11 +87,11 @@ async function resolveNodeAssetsPath(document, assetsPath) {
85
87
  if (assetsPath) return assetsPath;
86
88
  const projectDir = document.getProjectDir?.() ?? "";
87
89
  if (!projectDir) return void 0;
88
- return (await importNative$1("node:path")).join(projectDir, "assets");
90
+ return (await importNative$2("node:path")).join(projectDir, "assets");
89
91
  }
90
92
  async function loadSharpBackend() {
91
93
  try {
92
- const loaded = await importNative$1("sharp");
94
+ const loaded = await importNative$2("sharp");
93
95
  return loaded.default ?? loaded;
94
96
  } catch {
95
97
  return;
@@ -98,7 +100,7 @@ async function loadSharpBackend() {
98
100
  async function loadNodePublishPlugins(document, assetsPath) {
99
101
  const projectDir = document.getProjectDir?.() || (assetsPath ? resolveProjectBasePath(assetsPath) : "");
100
102
  if (!projectDir) return [];
101
- return loadPlugins(document, (await importNative$1("node:path")).join(projectDir, "plugins"));
103
+ return loadPlugins(document, (await importNative$2("node:path")).join(projectDir, "plugins"));
102
104
  }
103
105
  /**
104
106
  * Publish a FairyGUI project through the standard Node host adapter.
@@ -126,9 +128,9 @@ async function publishNode(options) {
126
128
  }
127
129
  //#endregion
128
130
  //#region src/adapters/node/restore.ts
129
- const importNative = new Function("id", "return import(id)");
131
+ const importNative$1 = new Function("id", "return import(id)");
130
132
  async function createNodeRestoreFileSystem() {
131
- const [fs, path] = await Promise.all([importNative("node:fs/promises"), importNative("node:path")]);
133
+ const [fs, path] = await Promise.all([importNative$1("node:fs/promises"), importNative$1("node:path")]);
132
134
  return {
133
135
  async readFile(filePath) {
134
136
  return fs.readFile(filePath, "utf-8");
@@ -193,7 +195,7 @@ async function createNodeRestoreFileSystem() {
193
195
  async function createRestoreImageProcessors() {
194
196
  let sharp;
195
197
  try {
196
- const loaded = await importNative("sharp");
198
+ const loaded = await importNative$1("sharp");
197
199
  sharp = loaded.default ?? loaded;
198
200
  } catch {
199
201
  throw new Error("restoreNode: Sharp is required to crop atlas images. Install sharp before restoring.");
@@ -231,8 +233,8 @@ async function createRestoreImageProcessors() {
231
233
  if (input.expectedWidth > 0 && input.expectedHeight > 0 && (info.width !== input.expectedWidth || info.height !== input.expectedHeight)) throw new Error(`restore: Cropped image size mismatch for ${targetPath}: expected ${input.expectedWidth}x${input.expectedHeight}, got ${info.width}x${info.height}`);
232
234
  return data;
233
235
  }
234
- const fs = await importNative("node:fs/promises");
235
- const path = await importNative("node:path");
236
+ const fs = await importNative$1("node:fs/promises");
237
+ const path = await importNative$1("node:path");
236
238
  return {
237
239
  extractImage,
238
240
  cropImage: async (input) => {
@@ -251,4 +253,68 @@ async function restoreNode(options) {
251
253
  });
252
254
  }
253
255
  //#endregion
254
- export { publishNode, restoreNode };
256
+ //#region src/adapters/node/validate.ts
257
+ const importNative = new Function("id", "return import(id)");
258
+ function sourceName(resource) {
259
+ return resource.sourcePath ?? resource.fileName ?? resource.file ?? resource.name;
260
+ }
261
+ async function validateProjectNode(projectPath) {
262
+ const read = await new NodeIO().readProjectDetailed(projectPath, { hydrateResourceBytes: true });
263
+ if (!read.document) return createProjectValidationReport(read.diagnostics, false);
264
+ let project;
265
+ try {
266
+ project = liftDocumentToUamProject(read.document);
267
+ } catch (error) {
268
+ return createProjectValidationReport([...read.diagnostics, {
269
+ severity: "error",
270
+ code: "invalid_uam",
271
+ path: "project",
272
+ message: `Project cannot be represented as UAM: ${error instanceof Error ? error.message : String(error)}`,
273
+ sourcePath: projectPath
274
+ }], false);
275
+ }
276
+ const base = validateProject(project, {
277
+ readDiagnostics: read.diagnostics,
278
+ complete: read.complete,
279
+ validateSources: true
280
+ });
281
+ let diagnostics = base.diagnostics;
282
+ const knownCorruptPaths = new Set(base.diagnostics.filter((diagnostic) => diagnostic.code === "corrupt_source").map((diagnostic) => diagnostic.path));
283
+ const images = project.packages.flatMap((pkg, packageIndex) => pkg.resources.map((resource, resourceIndex) => ({
284
+ pkg,
285
+ packageIndex,
286
+ resource,
287
+ resourceIndex
288
+ })).filter(({ resource, packageIndex, resourceIndex }) => resource.kind === "image" && resource.sourceBytes instanceof Uint8Array && !knownCorruptPaths.has(`packages[${packageIndex}].resources[${resourceIndex}]`)));
289
+ if (images.length === 0) return base;
290
+ let sharp;
291
+ try {
292
+ const loaded = await importNative("sharp");
293
+ sharp = loaded.default ?? loaded;
294
+ } catch {
295
+ return createProjectValidationReport([...base.diagnostics, {
296
+ severity: "warning",
297
+ code: "decode_capability_unavailable",
298
+ path: "project",
299
+ message: "Sharp is unavailable, so Node image decoding could not be completed."
300
+ }], false);
301
+ }
302
+ const decodedPaths = new Set(images.map(({ packageIndex, resourceIndex }) => `packages[${packageIndex}].resources[${resourceIndex}]`));
303
+ diagnostics = diagnostics.filter((diagnostic) => diagnostic.code !== "decode_capability_unavailable" || !decodedPaths.has(diagnostic.path));
304
+ for (const { pkg, packageIndex, resource, resourceIndex } of images) try {
305
+ await sharp(resource.sourceBytes).raw().toBuffer();
306
+ } catch (error) {
307
+ diagnostics.push({
308
+ severity: "error",
309
+ code: "corrupt_source",
310
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
311
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
312
+ packageId: pkg.id,
313
+ resourceId: resource.id,
314
+ sourcePath: sourceName(resource)
315
+ });
316
+ }
317
+ return createProjectValidationReport(diagnostics, read.complete && !diagnostics.some((diagnostic) => diagnostic.code === "decode_capability_unavailable"));
318
+ }
319
+ //#endregion
320
+ export { publishNode, restoreNode, validateProjectNode };
@@ -1,4 +1,21 @@
1
1
  let _openfairygui_core = require("@openfairygui/core");
2
+ //#region src/validate.ts
3
+ /** Validate one authoritative UAM snapshot without mutating it. */
4
+ function validateProject(project, options = {}) {
5
+ const diagnostics = [
6
+ ...options.readDiagnostics ?? [],
7
+ ...(0, _openfairygui_core.validateUamProject)(project),
8
+ ...(0, _openfairygui_core.validateUamReferences)(project)
9
+ ];
10
+ let complete = options.complete ?? true;
11
+ if (options.validateSources) {
12
+ const source = (0, _openfairygui_core.validateUamSourceBytes)(project);
13
+ diagnostics.push(...source.diagnostics);
14
+ complete = complete && source.complete;
15
+ }
16
+ return (0, _openfairygui_core.createProjectValidationReport)(diagnostics, complete);
17
+ }
18
+ //#endregion
2
19
  //#region src/utils.ts
3
20
  /**
4
21
  * Wraps a transform function, assigning it a name for the transform stack.
@@ -3267,3 +3284,9 @@ Object.defineProperty(exports, "trimTrailingSlashes", {
3267
3284
  return trimTrailingSlashes;
3268
3285
  }
3269
3286
  });
3287
+ Object.defineProperty(exports, "validateProject", {
3288
+ enumerable: true,
3289
+ get: function() {
3290
+ return validateProject;
3291
+ }
3292
+ });
@@ -1,4 +1,21 @@
1
- import { BinaryWriter, GearType, ProjectType, TransitionActionType, deriveMovieClipModel, parseJta, probeRasterImage } from "@openfairygui/core";
1
+ import { BinaryWriter, GearType, ProjectType, TransitionActionType, createProjectValidationReport, deriveMovieClipModel, parseJta, probeRasterImage, validateUamProject, validateUamReferences, validateUamSourceBytes } from "@openfairygui/core";
2
+ //#region src/validate.ts
3
+ /** Validate one authoritative UAM snapshot without mutating it. */
4
+ function validateProject(project, options = {}) {
5
+ const diagnostics = [
6
+ ...options.readDiagnostics ?? [],
7
+ ...validateUamProject(project),
8
+ ...validateUamReferences(project)
9
+ ];
10
+ let complete = options.complete ?? true;
11
+ if (options.validateSources) {
12
+ const source = validateUamSourceBytes(project);
13
+ diagnostics.push(...source.diagnostics);
14
+ complete = complete && source.complete;
15
+ }
16
+ return createProjectValidationReport(diagnostics, complete);
17
+ }
18
+ //#endregion
2
19
  //#region src/utils.ts
3
20
  /**
4
21
  * Wraps a transform function, assigning it a name for the transform stack.
@@ -3171,4 +3188,4 @@ function _computeDependencies(doc, pkg, pkgMap) {
3171
3188
  }
3172
3189
  }
3173
3190
  //#endregion
3174
- export { decodeText as a, resolveCodeGenerationSettings as c, basename as d, normalizeComparablePath as f, createTransform as g, atlas as h, buildCodegenClasses as i, resolvePackageCodegenPlan as l, formatPluginError as m, resolvePublishOptions as n, encodeText as o, trimTrailingSlashes as p, AUTO_GENERATED_CODE_MARK as r, publishCodeGeneration as s, publish as t, resolveProjectBasePath as u };
3191
+ export { validateProject as _, decodeText as a, resolveCodeGenerationSettings as c, basename as d, normalizeComparablePath as f, createTransform as g, atlas as h, buildCodegenClasses as i, resolvePackageCodegenPlan as l, formatPluginError as m, resolvePublishOptions as n, encodeText as o, trimTrailingSlashes as p, AUTO_GENERATED_CODE_MARK as r, publishCodeGeneration as s, publish as t, resolveProjectBasePath as u };
@@ -1,4 +1,4 @@
1
- import { d as basename, f as normalizeComparablePath, p as trimTrailingSlashes } from "./publish-DXoaC1Nl.js";
1
+ import { d as basename, f as normalizeComparablePath, p as trimTrailingSlashes } from "./publish-CyBj2o7n.js";
2
2
  import { BinaryReader, ProjectType, ProjectWriter, generateId } from "@openfairygui/core";
3
3
  //#region src/restore-internals/output-transaction.ts
4
4
  function isPathWithin(root, candidate) {
@@ -1,4 +1,4 @@
1
- const require_publish = require("./publish-CykUJfVa.cjs");
1
+ const require_publish = require("./publish-C7qHwEiP.cjs");
2
2
  let _openfairygui_core = require("@openfairygui/core");
3
3
  //#region src/restore-internals/output-transaction.ts
4
4
  function isPathWithin(root, candidate) {
@@ -13,7 +13,7 @@ function compactDiagnostic(diagnostic) {
13
13
  return Object.fromEntries(Object.entries(diagnostic).filter(([, value]) => value !== void 0));
14
14
  }
15
15
  function isTransactionSupportIssue(issue) {
16
- return "code" in issue;
16
+ return !("severity" in issue);
17
17
  }
18
18
  function mapTransactionDiagnostics(error) {
19
19
  if (error.issues?.length) return error.issues.map((issue) => {
@@ -12,7 +12,7 @@ function compactDiagnostic(diagnostic) {
12
12
  return Object.fromEntries(Object.entries(diagnostic).filter(([, value]) => value !== void 0));
13
13
  }
14
14
  function isTransactionSupportIssue(issue) {
15
- return "code" in issue;
15
+ return !("severity" in issue);
16
16
  }
17
17
  function mapTransactionDiagnostics(error) {
18
18
  if (error.issues?.length) return error.issues.map((issue) => {
package/dist/web.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_publish = require("./publish-CykUJfVa.cjs");
2
+ const require_publish = require("./publish-C7qHwEiP.cjs");
3
3
  let _openfairygui_core = require("@openfairygui/core");
4
4
  let fast_xml_parser = require("fast-xml-parser");
5
5
  //#region src/adapters/web/raster.ts
@@ -186,6 +186,9 @@ async function decodeRaster(bytes, mimeType) {
186
186
  bitmap.close();
187
187
  }
188
188
  }
189
+ async function validateBrowserImageSource(bytes, path) {
190
+ await decodeRaster(bytes, imageMimeType(path));
191
+ }
189
192
  async function decodeSvgWithDom(blob) {
190
193
  if (typeof globalThis.Image !== "function" || typeof globalThis.URL?.createObjectURL !== "function" || typeof globalThis.URL?.revokeObjectURL !== "function") throw new Error("publishBrowser: createImageBitmap rejected SVG and DOM image decoding is unavailable.");
191
194
  const url = globalThis.URL.createObjectURL(blob);
@@ -437,4 +440,46 @@ async function publishBrowser(options) {
437
440
  }
438
441
  }
439
442
  //#endregion
443
+ //#region src/adapters/web/validate.ts
444
+ function sourceName(resource) {
445
+ return resource.sourcePath ?? resource.fileName ?? resource.name;
446
+ }
447
+ async function validateProjectWeb(project) {
448
+ const base = require_publish.validateProject(project, { validateSources: true });
449
+ if (!project.packages.some((pkg) => pkg.resources.some((resource) => resource.kind === "image" && resource.sourceBytes instanceof Uint8Array))) return base;
450
+ try {
451
+ assertBrowserImageSupport();
452
+ } catch (error) {
453
+ return (0, _openfairygui_core.createProjectValidationReport)([...base.diagnostics, {
454
+ severity: "warning",
455
+ code: "decode_capability_unavailable",
456
+ path: "project",
457
+ message: error instanceof Error ? error.message : String(error)
458
+ }], false);
459
+ }
460
+ const decodedPaths = /* @__PURE__ */ new Set();
461
+ const diagnostics = [...base.diagnostics];
462
+ for (const [packageIndex, pkg] of project.packages.entries()) for (const [resourceIndex, resource] of pkg.resources.entries()) {
463
+ if (resource.kind !== "image" || !(resource.sourceBytes instanceof Uint8Array)) continue;
464
+ try {
465
+ await validateBrowserImageSource(resource.sourceBytes, sourceName(resource));
466
+ decodedPaths.add(`packages[${packageIndex}].resources[${resourceIndex}]`);
467
+ } catch (error) {
468
+ const capabilityUnavailable = error instanceof Error && /(?:required|unavailable)/iu.test(error.message);
469
+ diagnostics.push({
470
+ severity: capabilityUnavailable ? "warning" : "error",
471
+ code: capabilityUnavailable ? "decode_capability_unavailable" : "corrupt_source",
472
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
473
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
474
+ packageId: pkg.id,
475
+ resourceId: resource.id,
476
+ sourcePath: sourceName(resource)
477
+ });
478
+ }
479
+ }
480
+ const resolved = diagnostics.filter((diagnostic) => diagnostic.code !== "decode_capability_unavailable" || !decodedPaths.has(diagnostic.path));
481
+ return (0, _openfairygui_core.createProjectValidationReport)(resolved, !resolved.some((diagnostic) => diagnostic.code === "decode_capability_unavailable"));
482
+ }
483
+ //#endregion
440
484
  exports.publishBrowser = publishBrowser;
485
+ exports.validateProjectWeb = validateProjectWeb;
package/dist/web.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { d as PublishSourceFileSystem, t as AtlasOptions, u as PublishOutputFileSystem } from "./atlas-CHsu2Y8i.cjs";
2
- import { Document } from "@openfairygui/core";
2
+ import { Document, ProjectValidationReport, UamProject } from "@openfairygui/core";
3
3
 
4
4
  //#region src/adapters/web/publish.d.ts
5
5
  type BrowserPublishProjectType = 'layabox';
@@ -41,4 +41,7 @@ interface BrowserPublishResult {
41
41
  */
42
42
  declare function publishBrowser(options: BrowserPublishOptions): Promise<BrowserPublishResult>;
43
43
  //#endregion
44
- export { type BrowserPublishAtlasOptions, type BrowserPublishDiagnostic, type BrowserPublishOptions, type BrowserPublishOutputFileSystem, type BrowserPublishProjectType, type BrowserPublishResult, type BrowserPublishSourceFileSystem, type BrowserPublishedFile, publishBrowser };
44
+ //#region src/adapters/web/validate.d.ts
45
+ declare function validateProjectWeb(project: UamProject): Promise<ProjectValidationReport>;
46
+ //#endregion
47
+ export { type BrowserPublishAtlasOptions, type BrowserPublishDiagnostic, type BrowserPublishOptions, type BrowserPublishOutputFileSystem, type BrowserPublishProjectType, type BrowserPublishResult, type BrowserPublishSourceFileSystem, type BrowserPublishedFile, publishBrowser, validateProjectWeb };
package/dist/web.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { d as PublishSourceFileSystem, t as AtlasOptions, u as PublishOutputFileSystem } from "./atlas-C6tbl7nn.js";
2
- import { Document } from "@openfairygui/core";
2
+ import { Document, ProjectValidationReport, UamProject } from "@openfairygui/core";
3
3
 
4
4
  //#region src/adapters/web/publish.d.ts
5
5
  type BrowserPublishProjectType = 'layabox';
@@ -41,4 +41,7 @@ interface BrowserPublishResult {
41
41
  */
42
42
  declare function publishBrowser(options: BrowserPublishOptions): Promise<BrowserPublishResult>;
43
43
  //#endregion
44
- export { type BrowserPublishAtlasOptions, type BrowserPublishDiagnostic, type BrowserPublishOptions, type BrowserPublishOutputFileSystem, type BrowserPublishProjectType, type BrowserPublishResult, type BrowserPublishSourceFileSystem, type BrowserPublishedFile, publishBrowser };
44
+ //#region src/adapters/web/validate.d.ts
45
+ declare function validateProjectWeb(project: UamProject): Promise<ProjectValidationReport>;
46
+ //#endregion
47
+ export { type BrowserPublishAtlasOptions, type BrowserPublishDiagnostic, type BrowserPublishOptions, type BrowserPublishOutputFileSystem, type BrowserPublishProjectType, type BrowserPublishResult, type BrowserPublishSourceFileSystem, type BrowserPublishedFile, publishBrowser, validateProjectWeb };
package/dist/web.js CHANGED
@@ -1,5 +1,5 @@
1
- import { c as resolveCodeGenerationSettings, n as resolvePublishOptions, t as publish } from "./publish-DXoaC1Nl.js";
2
- import { ProjectType } from "@openfairygui/core";
1
+ import { _ as validateProject, c as resolveCodeGenerationSettings, n as resolvePublishOptions, t as publish } from "./publish-CyBj2o7n.js";
2
+ import { ProjectType, createProjectValidationReport } from "@openfairygui/core";
3
3
  import { XMLParser, XMLValidator } from "fast-xml-parser";
4
4
  //#region src/adapters/web/raster.ts
5
5
  const MAX_SVG_SOURCE_BYTES = 8 * 1024 * 1024;
@@ -185,6 +185,9 @@ async function decodeRaster(bytes, mimeType) {
185
185
  bitmap.close();
186
186
  }
187
187
  }
188
+ async function validateBrowserImageSource(bytes, path) {
189
+ await decodeRaster(bytes, imageMimeType(path));
190
+ }
188
191
  async function decodeSvgWithDom(blob) {
189
192
  if (typeof globalThis.Image !== "function" || typeof globalThis.URL?.createObjectURL !== "function" || typeof globalThis.URL?.revokeObjectURL !== "function") throw new Error("publishBrowser: createImageBitmap rejected SVG and DOM image decoding is unavailable.");
190
193
  const url = globalThis.URL.createObjectURL(blob);
@@ -436,4 +439,45 @@ async function publishBrowser(options) {
436
439
  }
437
440
  }
438
441
  //#endregion
439
- export { publishBrowser };
442
+ //#region src/adapters/web/validate.ts
443
+ function sourceName(resource) {
444
+ return resource.sourcePath ?? resource.fileName ?? resource.name;
445
+ }
446
+ async function validateProjectWeb(project) {
447
+ const base = validateProject(project, { validateSources: true });
448
+ if (!project.packages.some((pkg) => pkg.resources.some((resource) => resource.kind === "image" && resource.sourceBytes instanceof Uint8Array))) return base;
449
+ try {
450
+ assertBrowserImageSupport();
451
+ } catch (error) {
452
+ return createProjectValidationReport([...base.diagnostics, {
453
+ severity: "warning",
454
+ code: "decode_capability_unavailable",
455
+ path: "project",
456
+ message: error instanceof Error ? error.message : String(error)
457
+ }], false);
458
+ }
459
+ const decodedPaths = /* @__PURE__ */ new Set();
460
+ const diagnostics = [...base.diagnostics];
461
+ for (const [packageIndex, pkg] of project.packages.entries()) for (const [resourceIndex, resource] of pkg.resources.entries()) {
462
+ if (resource.kind !== "image" || !(resource.sourceBytes instanceof Uint8Array)) continue;
463
+ try {
464
+ await validateBrowserImageSource(resource.sourceBytes, sourceName(resource));
465
+ decodedPaths.add(`packages[${packageIndex}].resources[${resourceIndex}]`);
466
+ } catch (error) {
467
+ const capabilityUnavailable = error instanceof Error && /(?:required|unavailable)/iu.test(error.message);
468
+ diagnostics.push({
469
+ severity: capabilityUnavailable ? "warning" : "error",
470
+ code: capabilityUnavailable ? "decode_capability_unavailable" : "corrupt_source",
471
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
472
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
473
+ packageId: pkg.id,
474
+ resourceId: resource.id,
475
+ sourcePath: sourceName(resource)
476
+ });
477
+ }
478
+ }
479
+ const resolved = diagnostics.filter((diagnostic) => diagnostic.code !== "decode_capability_unavailable" || !decodedPaths.has(diagnostic.path));
480
+ return createProjectValidationReport(resolved, !resolved.some((diagnostic) => diagnostic.code === "decode_capability_unavailable"));
481
+ }
482
+ //#endregion
483
+ export { publishBrowser, validateProjectWeb };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/functions",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-alpha.2",
4
4
  "description": "FairyGUI Headless Authoring SDK — composable transform functions.",
5
5
  "author": "OpenFairyGUI Contributors",
6
6
  "license": "MIT",
@@ -75,7 +75,7 @@
75
75
  "dependencies": {
76
76
  "fast-xml-parser": "^5.0.0",
77
77
  "jiti": "^2.6.1",
78
- "@openfairygui/core": "0.2.0"
78
+ "@openfairygui/core": "0.3.0-alpha.2"
79
79
  },
80
80
  "optionalDependencies": {
81
81
  "sharp": ">=0.33.0"
@@ -0,0 +1,93 @@
1
+ import {
2
+ createProjectValidationReport,
3
+ liftDocumentToUamProject,
4
+ type ProjectDiagnostic,
5
+ type ProjectValidationReport,
6
+ type UamProject,
7
+ } from '@openfairygui/core';
8
+ import { NodeIO } from '@openfairygui/core/node';
9
+ import { validateProject } from '../../validate.js';
10
+
11
+ const importNative = new Function('id', 'return import(id)') as <T>(id: string) => Promise<T>;
12
+
13
+ function sourceName(resource: {
14
+ kind: string;
15
+ name: string;
16
+ fileName?: string;
17
+ file?: string;
18
+ sourcePath?: string;
19
+ }): string {
20
+ return resource.sourcePath ?? resource.fileName ?? resource.file ?? resource.name;
21
+ }
22
+
23
+ export async function validateProjectNode(projectPath: string): Promise<ProjectValidationReport> {
24
+ const read = await new NodeIO().readProjectDetailed(projectPath, { hydrateResourceBytes: true });
25
+ if (!read.document) return createProjectValidationReport(read.diagnostics, false);
26
+
27
+ let project: UamProject;
28
+ try {
29
+ project = liftDocumentToUamProject(read.document);
30
+ } catch (error) {
31
+ return createProjectValidationReport([...read.diagnostics, {
32
+ severity: 'error',
33
+ code: 'invalid_uam',
34
+ path: 'project',
35
+ message: `Project cannot be represented as UAM: ${error instanceof Error ? error.message : String(error)}`,
36
+ sourcePath: projectPath,
37
+ }], false);
38
+ }
39
+ const base = validateProject(project, {
40
+ readDiagnostics: read.diagnostics,
41
+ complete: read.complete,
42
+ validateSources: true,
43
+ });
44
+ let diagnostics: ProjectDiagnostic[] = base.diagnostics;
45
+ const knownCorruptPaths = new Set(base.diagnostics
46
+ .filter((diagnostic) => diagnostic.code === 'corrupt_source')
47
+ .map((diagnostic) => diagnostic.path));
48
+ const images = project.packages.flatMap((pkg, packageIndex) => pkg.resources
49
+ .map((resource, resourceIndex) => ({ pkg, packageIndex, resource, resourceIndex }))
50
+ .filter(({ resource, packageIndex, resourceIndex }) => (
51
+ resource.kind === 'image'
52
+ && resource.sourceBytes instanceof Uint8Array
53
+ && !knownCorruptPaths.has(`packages[${packageIndex}].resources[${resourceIndex}]`)
54
+ )));
55
+ if (images.length === 0) return base;
56
+
57
+ let sharp: ((bytes: Uint8Array) => { raw(): { toBuffer(): Promise<unknown> } });
58
+ try {
59
+ const loaded = await importNative<typeof import('sharp')>('sharp');
60
+ sharp = (loaded as unknown as { default?: typeof loaded }).default ?? loaded;
61
+ } catch {
62
+ return createProjectValidationReport([...base.diagnostics, {
63
+ severity: 'warning',
64
+ code: 'decode_capability_unavailable',
65
+ path: 'project',
66
+ message: 'Sharp is unavailable, so Node image decoding could not be completed.',
67
+ }], false);
68
+ }
69
+ const decodedPaths = new Set(images.map(({ packageIndex, resourceIndex }) => `packages[${packageIndex}].resources[${resourceIndex}]`));
70
+ diagnostics = diagnostics.filter((diagnostic) => (
71
+ diagnostic.code !== 'decode_capability_unavailable' || !decodedPaths.has(diagnostic.path)
72
+ ));
73
+
74
+ for (const { pkg, packageIndex, resource, resourceIndex } of images) {
75
+ try {
76
+ await sharp(resource.sourceBytes!).raw().toBuffer();
77
+ } catch (error) {
78
+ diagnostics.push({
79
+ severity: 'error',
80
+ code: 'corrupt_source',
81
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
82
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
83
+ packageId: pkg.id,
84
+ resourceId: resource.id,
85
+ sourcePath: sourceName(resource),
86
+ });
87
+ }
88
+ }
89
+ return createProjectValidationReport(
90
+ diagnostics,
91
+ read.complete && !diagnostics.some((diagnostic) => diagnostic.code === 'decode_capability_unavailable'),
92
+ );
93
+ }
@@ -265,6 +265,10 @@ async function decodeRaster(bytes: Uint8Array, mimeType: string): Promise<Browse
265
265
  }
266
266
  }
267
267
 
268
+ export async function validateBrowserImageSource(bytes: Uint8Array, path: string): Promise<void> {
269
+ await decodeRaster(bytes, imageMimeType(path));
270
+ }
271
+
268
272
  async function decodeSvgWithDom(blob: Blob): Promise<BrowserRaster> {
269
273
  if (typeof globalThis.Image !== 'function'
270
274
  || typeof globalThis.URL?.createObjectURL !== 'function'
@@ -0,0 +1,60 @@
1
+ import {
2
+ createProjectValidationReport,
3
+ type ProjectDiagnostic,
4
+ type ProjectValidationReport,
5
+ type UamProject,
6
+ } from '@openfairygui/core';
7
+ import { validateProject } from '../../validate.js';
8
+ import { assertBrowserImageSupport, validateBrowserImageSource } from './raster.js';
9
+
10
+ function sourceName(resource: {
11
+ name: string;
12
+ fileName?: string;
13
+ sourcePath?: string;
14
+ }): string {
15
+ return resource.sourcePath ?? resource.fileName ?? resource.name;
16
+ }
17
+
18
+ export async function validateProjectWeb(project: UamProject): Promise<ProjectValidationReport> {
19
+ const base = validateProject(project, { validateSources: true });
20
+ const hasHydratedImages = project.packages.some((pkg) => pkg.resources.some(
21
+ (resource) => resource.kind === 'image' && resource.sourceBytes instanceof Uint8Array,
22
+ ));
23
+ if (!hasHydratedImages) return base;
24
+ try {
25
+ assertBrowserImageSupport();
26
+ } catch (error) {
27
+ return createProjectValidationReport([...base.diagnostics, {
28
+ severity: 'warning',
29
+ code: 'decode_capability_unavailable',
30
+ path: 'project',
31
+ message: error instanceof Error ? error.message : String(error),
32
+ }], false);
33
+ }
34
+ const decodedPaths = new Set<string>();
35
+ const diagnostics: ProjectDiagnostic[] = [...base.diagnostics];
36
+ for (const [packageIndex, pkg] of project.packages.entries()) {
37
+ for (const [resourceIndex, resource] of pkg.resources.entries()) {
38
+ if (resource.kind !== 'image' || !(resource.sourceBytes instanceof Uint8Array)) continue;
39
+ try {
40
+ await validateBrowserImageSource(resource.sourceBytes, sourceName(resource));
41
+ decodedPaths.add(`packages[${packageIndex}].resources[${resourceIndex}]`);
42
+ } catch (error) {
43
+ const capabilityUnavailable = error instanceof Error && /(?:required|unavailable)/iu.test(error.message);
44
+ diagnostics.push({
45
+ severity: capabilityUnavailable ? 'warning' : 'error',
46
+ code: capabilityUnavailable ? 'decode_capability_unavailable' : 'corrupt_source',
47
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
48
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
49
+ packageId: pkg.id,
50
+ resourceId: resource.id,
51
+ sourcePath: sourceName(resource),
52
+ });
53
+ }
54
+ }
55
+ }
56
+ const resolved = diagnostics.filter((diagnostic) => (
57
+ diagnostic.code !== 'decode_capability_unavailable' || !decodedPaths.has(diagnostic.path)
58
+ ));
59
+ return createProjectValidationReport(resolved, !resolved.some((diagnostic) => diagnostic.code === 'decode_capability_unavailable'));
60
+ }
package/src/index.ts CHANGED
@@ -1,10 +1,7 @@
1
1
  export { inspect, type InspectReport, type InspectCategoryReport } from './inspect.js';
2
2
  export {
3
- validate,
4
- type ValidateOptions,
5
- type ValidationResult,
6
- type ValidationIssue,
7
- ValidationSeverity,
3
+ validateProject,
4
+ type ValidateProjectOptions,
8
5
  } from './validate.js';
9
6
  export { prune, type PruneOptions } from './prune.js';
10
7
  export { rename, type RenameOptions } from './rename.js';