@openfairygui/functions 0.3.0-alpha.4 → 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.
Files changed (37) hide show
  1. package/dist/{atlas-C6tbl7nn.d.ts → atlas-BtWa1DwO.d.ts} +6 -0
  2. package/dist/{atlas-CHsu2Y8i.d.cts → atlas-CSqHsG0X.d.cts} +6 -0
  3. package/dist/index.cjs +2 -2
  4. package/dist/index.d.cts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +2 -2
  7. package/dist/node.cjs +128 -30
  8. package/dist/node.d.cts +2 -2
  9. package/dist/node.d.ts +2 -2
  10. package/dist/node.js +128 -30
  11. package/dist/{publish-CyBj2o7n.js → publish-Cy09yjfn.js} +122 -54
  12. package/dist/{publish-C7qHwEiP.cjs → publish-DhMv_lEP.cjs} +122 -54
  13. package/dist/{restore-BeWaJNjR.d.cts → restore-CbscKkNw.d.ts} +9 -4
  14. package/dist/{restore-DVo1hXpN.js → restore-DB_L_xq4.js} +1 -1
  15. package/dist/{restore-Dh0-Nvms.d.ts → restore-k3oX26Fs.d.cts} +9 -4
  16. package/dist/{restore-LXbDQqmT.cjs → restore-n61oZ4nw.cjs} +1 -1
  17. package/dist/web.cjs +47 -64
  18. package/dist/web.d.cts +2 -2
  19. package/dist/web.d.ts +2 -2
  20. package/dist/web.js +49 -66
  21. package/package.json +5 -2
  22. package/src/adapters/node/plugins.ts +29 -13
  23. package/src/adapters/node/publish.ts +74 -4
  24. package/src/adapters/node/validate.ts +1 -0
  25. package/src/adapters/web/publish.ts +2 -0
  26. package/src/adapters/web/raster.ts +55 -72
  27. package/src/atlas/packing.ts +22 -13
  28. package/src/atlas.ts +22 -3
  29. package/src/codegen.ts +24 -11
  30. package/src/plugins/types.ts +7 -0
  31. package/src/publish/contracts.ts +3 -0
  32. package/src/publish/external-resources.ts +12 -6
  33. package/src/publish/options.ts +21 -7
  34. package/src/publish/package-context.ts +35 -11
  35. package/src/publish/resource-references.ts +21 -6
  36. package/src/publish.ts +26 -4
  37. package/src/utils.ts +2 -2
@@ -61,6 +61,9 @@ type AtlasRasterInput = string | Uint8Array | {
61
61
  */
62
62
  interface AtlasRasterPipeline {
63
63
  ensureAlpha(): AtlasRasterPipeline;
64
+ removeAlpha(): AtlasRasterPipeline;
65
+ extractChannel(channel: 'alpha'): AtlasRasterPipeline;
66
+ joinChannel(images: Uint8Array[]): AtlasRasterPipeline;
64
67
  resize(options: {
65
68
  width: number;
66
69
  height: number;
@@ -114,6 +117,9 @@ interface AtlasOptions {
114
117
  padding?: number;
115
118
  /** Constrain atlas dimensions to powers of two. Default: false. */
116
119
  powerOfTwo?: boolean;
120
+ /** Highest fixed atlas page index accepted from resource textureSetMode. Default: 10. */
121
+ maxAtlasIndex?: number;
122
+ multipleOfFour?: boolean;
117
123
  /** Force square atlas (width === height). Default: false. */
118
124
  square?: boolean;
119
125
  /** Allow spilling into multiple atlas pages. Default: true. */
@@ -61,6 +61,9 @@ type AtlasRasterInput = string | Uint8Array | {
61
61
  */
62
62
  interface AtlasRasterPipeline {
63
63
  ensureAlpha(): AtlasRasterPipeline;
64
+ removeAlpha(): AtlasRasterPipeline;
65
+ extractChannel(channel: 'alpha'): AtlasRasterPipeline;
66
+ joinChannel(images: Uint8Array[]): AtlasRasterPipeline;
64
67
  resize(options: {
65
68
  width: number;
66
69
  height: number;
@@ -114,6 +117,9 @@ interface AtlasOptions {
114
117
  padding?: number;
115
118
  /** Constrain atlas dimensions to powers of two. Default: false. */
116
119
  powerOfTwo?: boolean;
120
+ /** Highest fixed atlas page index accepted from resource textureSetMode. Default: 10. */
121
+ maxAtlasIndex?: number;
122
+ multipleOfFour?: boolean;
117
123
  /** Force square atlas (width === height). Default: false. */
118
124
  square?: boolean;
119
125
  /** Allow spilling into multiple atlas pages. Default: true. */
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_publish = require("./publish-C7qHwEiP.cjs");
3
- const require_restore = require("./restore-LXbDQqmT.cjs");
2
+ const require_publish = require("./publish-DhMv_lEP.cjs");
3
+ const require_restore = require("./restore-n61oZ4nw.cjs");
4
4
  const require_uam_transaction = require("./uam-transaction.cjs");
5
5
  //#region src/inspect.ts
6
6
  function mapResource(resource) {
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
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 ExtrasMap, B as ResolvedPublishOptions, C as MaybePromise, D as CliAtlasSettings, E as PluginModule, F as RootProjectSettings, I as publish, L as PublishOptions, M as HasOptionalSrc, N as HasOptionalUrl, O as CliCodeGenerationSettings, P as PublishDependency, R as ResolvePublishOptionsOverrides, S as LoadedPlugin, T as PluginManifest, V as resolvePublishOptions, _ as encodeText, a as RestoreImageExtractor, b as CodeWriter, c as restore, d as CodegenMember, f as CodegenReferencedComponent, g as decodeText, h as buildCodegenClasses, i as RestoreImageExtractInput, j as HasOptionalFont, k as CliPublishSettings, l as AUTO_GENERATED_CODE_MARK, m as ResolvedPackageCodegenPlan, n as RestoreImageCropInput, o as RestoreOptions, p as PublishCodeGenerationOptions, r as RestoreImageCropper, s as RestoreResult, t as RestoreFileSystem, u as CodegenClass, v as publishCodeGeneration, w as Plugin, x as ICodeWriterConfig, y as resolvePackageCodegenPlan, z as ResolvedPublishAtlasOptions } from "./restore-BeWaJNjR.cjs";
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-CSqHsG0X.cjs";
2
+ import { A as ExtrasMap, B as ResolvedPublishOptions, C as MaybePromise, D as CliAtlasSettings, E as PluginModule, F as RootProjectSettings, I as publish, L as PublishOptions, M as HasOptionalSrc, N as HasOptionalUrl, O as CliCodeGenerationSettings, P as PublishDependency, R as ResolvePublishOptionsOverrides, S as LoadedPlugin, T as PluginManifest, V as resolvePublishOptions, _ as encodeText, a as RestoreImageExtractor, b as CodeWriter, c as restore, d as CodegenMember, f as CodegenReferencedComponent, g as decodeText, h as buildCodegenClasses, i as RestoreImageExtractInput, j as HasOptionalFont, k as CliPublishSettings, l as AUTO_GENERATED_CODE_MARK, m as ResolvedPackageCodegenPlan, n as RestoreImageCropInput, o as RestoreOptions, p as PublishCodeGenerationOptions, r as RestoreImageCropper, s as RestoreResult, t as RestoreFileSystem, u as CodegenClass, v as publishCodeGeneration, w as Plugin, x as ICodeWriterConfig, y as resolvePackageCodegenPlan, z as ResolvedPublishAtlasOptions } from "./restore-k3oX26Fs.cjs";
3
3
  import { ApplyUamTransactionAppDiagnostic, ApplyUamTransactionAppError, ApplyUamTransactionAppInput, ApplyUamTransactionAppResult, applyUamTransactionApp, applyUamTransactionAppAsync } from "./uam-transaction.cjs";
4
4
  import { Document, ProjectDiagnostic, ProjectValidationReport, Transform, UamProject } from "@openfairygui/core";
5
5
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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-C6tbl7nn.js";
2
- import { A as ExtrasMap, B as ResolvedPublishOptions, C as MaybePromise, D as CliAtlasSettings, E as PluginModule, F as RootProjectSettings, I as publish, L as PublishOptions, M as HasOptionalSrc, N as HasOptionalUrl, O as CliCodeGenerationSettings, P as PublishDependency, R as ResolvePublishOptionsOverrides, S as LoadedPlugin, T as PluginManifest, V as resolvePublishOptions, _ as encodeText, a as RestoreImageExtractor, b as CodeWriter, c as restore, d as CodegenMember, f as CodegenReferencedComponent, g as decodeText, h as buildCodegenClasses, i as RestoreImageExtractInput, j as HasOptionalFont, k as CliPublishSettings, l as AUTO_GENERATED_CODE_MARK, m as ResolvedPackageCodegenPlan, n as RestoreImageCropInput, o as RestoreOptions, p as PublishCodeGenerationOptions, r as RestoreImageCropper, s as RestoreResult, t as RestoreFileSystem, u as CodegenClass, v as publishCodeGeneration, w as Plugin, x as ICodeWriterConfig, y as resolvePackageCodegenPlan, z as ResolvedPublishAtlasOptions } from "./restore-Dh0-Nvms.js";
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-BtWa1DwO.js";
2
+ import { A as ExtrasMap, B as ResolvedPublishOptions, C as MaybePromise, D as CliAtlasSettings, E as PluginModule, F as RootProjectSettings, I as publish, L as PublishOptions, M as HasOptionalSrc, N as HasOptionalUrl, O as CliCodeGenerationSettings, P as PublishDependency, R as ResolvePublishOptionsOverrides, S as LoadedPlugin, T as PluginManifest, V as resolvePublishOptions, _ as encodeText, a as RestoreImageExtractor, b as CodeWriter, c as restore, d as CodegenMember, f as CodegenReferencedComponent, g as decodeText, h as buildCodegenClasses, i as RestoreImageExtractInput, j as HasOptionalFont, k as CliPublishSettings, l as AUTO_GENERATED_CODE_MARK, m as ResolvedPackageCodegenPlan, n as RestoreImageCropInput, o as RestoreOptions, p as PublishCodeGenerationOptions, r as RestoreImageCropper, s as RestoreResult, t as RestoreFileSystem, u as CodegenClass, v as publishCodeGeneration, w as Plugin, x as ICodeWriterConfig, y as resolvePackageCodegenPlan, z as ResolvedPublishAtlasOptions } from "./restore-CbscKkNw.js";
3
3
  import { ApplyUamTransactionAppDiagnostic, ApplyUamTransactionAppError, ApplyUamTransactionAppInput, ApplyUamTransactionAppResult, applyUamTransactionApp, applyUamTransactionAppAsync } from "./uam-transaction.js";
4
4
  import { Document, ProjectDiagnostic, ProjectValidationReport, Transform, UamProject } from "@openfairygui/core";
5
5
 
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as validateProject, a as decodeText, g as createTransform, h as atlas, i as buildCodegenClasses, l as resolvePackageCodegenPlan, n as resolvePublishOptions, o as encodeText, r as AUTO_GENERATED_CODE_MARK, s as publishCodeGeneration, t as publish } from "./publish-CyBj2o7n.js";
2
- import { t as restore } from "./restore-DVo1hXpN.js";
1
+ import { _ as validateProject, a as decodeText, g as createTransform, h as atlas, i as buildCodegenClasses, l as resolvePackageCodegenPlan, n as resolvePublishOptions, o as encodeText, r as AUTO_GENERATED_CODE_MARK, s as publishCodeGeneration, t as publish } from "./publish-Cy09yjfn.js";
2
+ import { t as restore } from "./restore-DB_L_xq4.js";
3
3
  import { applyUamTransactionApp, applyUamTransactionAppAsync } from "./uam-transaction.js";
4
4
  //#region src/inspect.ts
5
5
  function mapResource(resource) {
package/dist/node.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_publish = require("./publish-C7qHwEiP.cjs");
3
- const require_restore = require("./restore-LXbDQqmT.cjs");
2
+ const require_publish = require("./publish-DhMv_lEP.cjs");
3
+ const require_restore = require("./restore-n61oZ4nw.cjs");
4
4
  let _openfairygui_core = require("@openfairygui/core");
5
5
  let _openfairygui_core_node = require("@openfairygui/core/node");
6
6
  //#region src/adapters/node/plugins.ts
@@ -19,16 +19,33 @@ async function loadPlugins(doc, pluginsDir) {
19
19
  for (const entry of entries) {
20
20
  if (!entry.isDirectory()) continue;
21
21
  const pluginDir = path.join(pluginsDir, entry.name);
22
+ let manifest;
23
+ try {
24
+ manifest = await readPluginManifest(fs, path, pluginDir);
25
+ } catch (error) {
26
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${require_publish.formatPluginError(error)}`);
27
+ continue;
28
+ }
29
+ if (!manifest) continue;
30
+ if (!manifest.main) {
31
+ const error = /* @__PURE__ */ new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
32
+ if (manifest.required) throw error;
33
+ doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${error.message}`);
34
+ continue;
35
+ }
22
36
  try {
23
- const manifest = await readPluginManifest(fs, path, pluginDir);
24
- if (!manifest) continue;
25
37
  const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
26
38
  plugins.push({
27
39
  name: manifest.name,
28
- plugin
40
+ plugin,
41
+ failureMode: manifest.required ? "abort" : manifest.failureMode
29
42
  });
30
43
  } catch (error) {
31
- doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${require_publish.formatPluginError(error)}`);
44
+ if (!manifest.required && manifest.failureMode === "warn") {
45
+ doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${require_publish.formatPluginError(error)}`);
46
+ continue;
47
+ }
48
+ throw new Error(`publish: Failed to load plugin "${manifest.name}": ${require_publish.formatPluginError(error)}`);
32
49
  }
33
50
  }
34
51
  return plugins;
@@ -38,7 +55,6 @@ async function readPluginManifest(fs, path, pluginDir) {
38
55
  const content = await fs.readFile(manifestPath, "utf-8");
39
56
  const manifest = JSON.parse(content);
40
57
  if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
41
- if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
42
58
  return manifest;
43
59
  }
44
60
  function resolvePluginMain(path, pluginDir, manifest) {
@@ -103,6 +119,77 @@ async function loadNodePublishPlugins(document, assetsPath) {
103
119
  if (!projectDir) return [];
104
120
  return loadPlugins(document, (await importNative$2("node:path")).join(projectDir, "plugins"));
105
121
  }
122
+ async function publishToStagedOutput(output, run) {
123
+ const [fs, path, { randomUUID }] = await Promise.all([
124
+ importNative$2("node:fs/promises"),
125
+ importNative$2("node:path"),
126
+ importNative$2("node:crypto")
127
+ ]);
128
+ const target = path.resolve(output);
129
+ const parent = path.dirname(target);
130
+ const name = path.basename(target);
131
+ const staging = path.join(parent, `.${name}.publish-${randomUUID()}`);
132
+ const backup = path.join(parent, `.${name}.publish-backup-${randomUUID()}`);
133
+ await fs.mkdir(parent, { recursive: true });
134
+ let existed = false;
135
+ try {
136
+ const stat = await fs.lstat(target);
137
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`publishNode: output must be a regular directory: ${target}`);
138
+ await assertNoSymlinks(fs, path, target);
139
+ existed = true;
140
+ } catch (error) {
141
+ if (error.code !== "ENOENT") throw error;
142
+ }
143
+ try {
144
+ if (existed) await fs.cp(target, staging, {
145
+ recursive: true,
146
+ errorOnExist: true,
147
+ force: false
148
+ });
149
+ else await fs.mkdir(staging);
150
+ } catch (error) {
151
+ await fs.rm(staging, {
152
+ recursive: true,
153
+ force: true
154
+ });
155
+ throw error;
156
+ }
157
+ try {
158
+ await run(staging);
159
+ } catch (error) {
160
+ await fs.rm(staging, {
161
+ recursive: true,
162
+ force: true
163
+ });
164
+ throw error;
165
+ }
166
+ if (!existed) {
167
+ await fs.rename(staging, target);
168
+ return;
169
+ }
170
+ await fs.rename(target, backup);
171
+ try {
172
+ await fs.rename(staging, target);
173
+ } catch (error) {
174
+ await fs.rename(backup, target);
175
+ await fs.rm(staging, {
176
+ recursive: true,
177
+ force: true
178
+ });
179
+ throw error;
180
+ }
181
+ await fs.rm(backup, {
182
+ recursive: true,
183
+ force: true
184
+ }).catch(() => void 0);
185
+ }
186
+ async function assertNoSymlinks(fs, path, directory) {
187
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
188
+ const entryPath = path.join(directory, entry.name);
189
+ if (entry.isSymbolicLink()) throw new Error(`publishNode: symbolic links are not supported in output directories: ${entryPath}`);
190
+ if (entry.isDirectory()) await assertNoSymlinks(fs, path, entryPath);
191
+ }
192
+ }
106
193
  /**
107
194
  * Publish a FairyGUI project through the standard Node host adapter.
108
195
  *
@@ -115,17 +202,25 @@ async function publishNode(options) {
115
202
  const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
116
203
  const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
117
204
  if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
118
- await document.transform(require_publish.publish({
119
- ...publishOptions,
120
- basePath: assetsPath,
121
- encoder,
122
- atlas: {
123
- ...atlas,
124
- readFileRaw: fileSystem.readFileRaw
125
- },
126
- fs: fileSystem,
127
- plugins
128
- }));
205
+ const run = async (output) => {
206
+ await document.transform(require_publish.publish({
207
+ ...publishOptions,
208
+ output,
209
+ basePath: assetsPath,
210
+ encoder,
211
+ atlas: {
212
+ ...atlas,
213
+ readFileRaw: fileSystem.readFileRaw
214
+ },
215
+ fs: fileSystem,
216
+ plugins
217
+ }));
218
+ };
219
+ if (publishOptions.output) {
220
+ await publishToStagedOutput(publishOptions.output, run);
221
+ return;
222
+ }
223
+ await run(void 0);
129
224
  }
130
225
  //#endregion
131
226
  //#region src/adapters/node/restore.ts
@@ -302,18 +397,21 @@ async function validateProjectNode(projectPath) {
302
397
  }
303
398
  const decodedPaths = new Set(images.map(({ packageIndex, resourceIndex }) => `packages[${packageIndex}].resources[${resourceIndex}]`));
304
399
  diagnostics = diagnostics.filter((diagnostic) => diagnostic.code !== "decode_capability_unavailable" || !decodedPaths.has(diagnostic.path));
305
- for (const { pkg, packageIndex, resource, resourceIndex } of images) try {
306
- await sharp(resource.sourceBytes).raw().toBuffer();
307
- } catch (error) {
308
- diagnostics.push({
309
- severity: "error",
310
- code: "corrupt_source",
311
- path: `packages[${packageIndex}].resources[${resourceIndex}]`,
312
- message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
313
- packageId: pkg.id,
314
- resourceId: resource.id,
315
- sourcePath: sourceName(resource)
316
- });
400
+ for (const { pkg, packageIndex, resource, resourceIndex } of images) {
401
+ if (resource.kind !== "image") continue;
402
+ try {
403
+ await sharp(resource.sourceBytes).raw().toBuffer();
404
+ } catch (error) {
405
+ diagnostics.push({
406
+ severity: "error",
407
+ code: "corrupt_source",
408
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
409
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
410
+ packageId: pkg.id,
411
+ resourceId: resource.id,
412
+ sourcePath: sourceName(resource)
413
+ });
414
+ }
317
415
  }
318
416
  return (0, _openfairygui_core.createProjectValidationReport)(diagnostics, read.complete && !diagnostics.some((diagnostic) => diagnostic.code === "decode_capability_unavailable"));
319
417
  }
package/dist/node.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { r as AtlasRasterBackend } from "./atlas-CHsu2Y8i.cjs";
2
- import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-BeWaJNjR.cjs";
1
+ import { r as AtlasRasterBackend } from "./atlas-CSqHsG0X.cjs";
2
+ import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-k3oX26Fs.cjs";
3
3
  import { Document, ProjectValidationReport } from "@openfairygui/core";
4
4
 
5
5
  //#region src/adapters/node/publish.d.ts
package/dist/node.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { r as AtlasRasterBackend } from "./atlas-C6tbl7nn.js";
2
- import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-Dh0-Nvms.js";
1
+ import { r as AtlasRasterBackend } from "./atlas-BtWa1DwO.js";
2
+ import { L as PublishOptions, S as LoadedPlugin, o as RestoreOptions, s as RestoreResult } from "./restore-CbscKkNw.js";
3
3
  import { Document, ProjectValidationReport } from "@openfairygui/core";
4
4
 
5
5
  //#region src/adapters/node/publish.d.ts
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
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";
1
+ import { _ as validateProject, m as formatPluginError, t as publish, u as resolveProjectBasePath } from "./publish-Cy09yjfn.js";
2
+ import { t as restore } from "./restore-DB_L_xq4.js";
3
3
  import { createProjectValidationReport, liftDocumentToUamProject } from "@openfairygui/core";
4
4
  import { NodeIO } from "@openfairygui/core/node";
5
5
  //#region src/adapters/node/plugins.ts
@@ -18,16 +18,33 @@ async function loadPlugins(doc, pluginsDir) {
18
18
  for (const entry of entries) {
19
19
  if (!entry.isDirectory()) continue;
20
20
  const pluginDir = path.join(pluginsDir, entry.name);
21
+ let manifest;
22
+ try {
23
+ manifest = await readPluginManifest(fs, path, pluginDir);
24
+ } catch (error) {
25
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
26
+ continue;
27
+ }
28
+ if (!manifest) continue;
29
+ if (!manifest.main) {
30
+ const error = /* @__PURE__ */ new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
31
+ if (manifest.required) throw error;
32
+ doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${error.message}`);
33
+ continue;
34
+ }
21
35
  try {
22
- const manifest = await readPluginManifest(fs, path, pluginDir);
23
- if (!manifest) continue;
24
36
  const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
25
37
  plugins.push({
26
38
  name: manifest.name,
27
- plugin
39
+ plugin,
40
+ failureMode: manifest.required ? "abort" : manifest.failureMode
28
41
  });
29
42
  } catch (error) {
30
- doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
43
+ if (!manifest.required && manifest.failureMode === "warn") {
44
+ doc.getLogger().warn(`publish: Plugin "${manifest.name}" was skipped: ${formatPluginError(error)}`);
45
+ continue;
46
+ }
47
+ throw new Error(`publish: Failed to load plugin "${manifest.name}": ${formatPluginError(error)}`);
31
48
  }
32
49
  }
33
50
  return plugins;
@@ -37,7 +54,6 @@ async function readPluginManifest(fs, path, pluginDir) {
37
54
  const content = await fs.readFile(manifestPath, "utf-8");
38
55
  const manifest = JSON.parse(content);
39
56
  if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
40
- if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
41
57
  return manifest;
42
58
  }
43
59
  function resolvePluginMain(path, pluginDir, manifest) {
@@ -102,6 +118,77 @@ async function loadNodePublishPlugins(document, assetsPath) {
102
118
  if (!projectDir) return [];
103
119
  return loadPlugins(document, (await importNative$2("node:path")).join(projectDir, "plugins"));
104
120
  }
121
+ async function publishToStagedOutput(output, run) {
122
+ const [fs, path, { randomUUID }] = await Promise.all([
123
+ importNative$2("node:fs/promises"),
124
+ importNative$2("node:path"),
125
+ importNative$2("node:crypto")
126
+ ]);
127
+ const target = path.resolve(output);
128
+ const parent = path.dirname(target);
129
+ const name = path.basename(target);
130
+ const staging = path.join(parent, `.${name}.publish-${randomUUID()}`);
131
+ const backup = path.join(parent, `.${name}.publish-backup-${randomUUID()}`);
132
+ await fs.mkdir(parent, { recursive: true });
133
+ let existed = false;
134
+ try {
135
+ const stat = await fs.lstat(target);
136
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(`publishNode: output must be a regular directory: ${target}`);
137
+ await assertNoSymlinks(fs, path, target);
138
+ existed = true;
139
+ } catch (error) {
140
+ if (error.code !== "ENOENT") throw error;
141
+ }
142
+ try {
143
+ if (existed) await fs.cp(target, staging, {
144
+ recursive: true,
145
+ errorOnExist: true,
146
+ force: false
147
+ });
148
+ else await fs.mkdir(staging);
149
+ } catch (error) {
150
+ await fs.rm(staging, {
151
+ recursive: true,
152
+ force: true
153
+ });
154
+ throw error;
155
+ }
156
+ try {
157
+ await run(staging);
158
+ } catch (error) {
159
+ await fs.rm(staging, {
160
+ recursive: true,
161
+ force: true
162
+ });
163
+ throw error;
164
+ }
165
+ if (!existed) {
166
+ await fs.rename(staging, target);
167
+ return;
168
+ }
169
+ await fs.rename(target, backup);
170
+ try {
171
+ await fs.rename(staging, target);
172
+ } catch (error) {
173
+ await fs.rename(backup, target);
174
+ await fs.rm(staging, {
175
+ recursive: true,
176
+ force: true
177
+ });
178
+ throw error;
179
+ }
180
+ await fs.rm(backup, {
181
+ recursive: true,
182
+ force: true
183
+ }).catch(() => void 0);
184
+ }
185
+ async function assertNoSymlinks(fs, path, directory) {
186
+ for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
187
+ const entryPath = path.join(directory, entry.name);
188
+ if (entry.isSymbolicLink()) throw new Error(`publishNode: symbolic links are not supported in output directories: ${entryPath}`);
189
+ if (entry.isDirectory()) await assertNoSymlinks(fs, path, entryPath);
190
+ }
191
+ }
105
192
  /**
106
193
  * Publish a FairyGUI project through the standard Node host adapter.
107
194
  *
@@ -114,17 +201,25 @@ async function publishNode(options) {
114
201
  const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
115
202
  const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
116
203
  if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
117
- await document.transform(publish({
118
- ...publishOptions,
119
- basePath: assetsPath,
120
- encoder,
121
- atlas: {
122
- ...atlas,
123
- readFileRaw: fileSystem.readFileRaw
124
- },
125
- fs: fileSystem,
126
- plugins
127
- }));
204
+ const run = async (output) => {
205
+ await document.transform(publish({
206
+ ...publishOptions,
207
+ output,
208
+ basePath: assetsPath,
209
+ encoder,
210
+ atlas: {
211
+ ...atlas,
212
+ readFileRaw: fileSystem.readFileRaw
213
+ },
214
+ fs: fileSystem,
215
+ plugins
216
+ }));
217
+ };
218
+ if (publishOptions.output) {
219
+ await publishToStagedOutput(publishOptions.output, run);
220
+ return;
221
+ }
222
+ await run(void 0);
128
223
  }
129
224
  //#endregion
130
225
  //#region src/adapters/node/restore.ts
@@ -301,18 +396,21 @@ async function validateProjectNode(projectPath) {
301
396
  }
302
397
  const decodedPaths = new Set(images.map(({ packageIndex, resourceIndex }) => `packages[${packageIndex}].resources[${resourceIndex}]`));
303
398
  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
- });
399
+ for (const { pkg, packageIndex, resource, resourceIndex } of images) {
400
+ if (resource.kind !== "image") continue;
401
+ try {
402
+ await sharp(resource.sourceBytes).raw().toBuffer();
403
+ } catch (error) {
404
+ diagnostics.push({
405
+ severity: "error",
406
+ code: "corrupt_source",
407
+ path: `packages[${packageIndex}].resources[${resourceIndex}]`,
408
+ message: `Image source cannot be decoded: ${error instanceof Error ? error.message : String(error)}`,
409
+ packageId: pkg.id,
410
+ resourceId: resource.id,
411
+ sourcePath: sourceName(resource)
412
+ });
413
+ }
316
414
  }
317
415
  return createProjectValidationReport(diagnostics, read.complete && !diagnostics.some((diagnostic) => diagnostic.code === "decode_capability_unavailable"));
318
416
  }