@openfairygui/functions 0.2.0-alpha.2 → 0.2.0-alpha.21

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 (54) hide show
  1. package/README.md +48 -6
  2. package/dist/atlas-C6tbl7nn.d.ts +193 -0
  3. package/dist/atlas-CHsu2Y8i.d.cts +193 -0
  4. package/dist/index.cjs +16 -3603
  5. package/dist/index.d.cts +5 -294
  6. package/dist/index.d.ts +5 -294
  7. package/dist/index.js +3 -3594
  8. package/dist/node.cjs +256 -0
  9. package/dist/node.d.cts +36 -0
  10. package/dist/node.d.ts +36 -0
  11. package/dist/node.js +254 -0
  12. package/dist/publish-BJ_eelME.js +3267 -0
  13. package/dist/publish-xFWT9Slz.cjs +3338 -0
  14. package/dist/restore-BW2xacB3.cjs +936 -0
  15. package/dist/restore-BeWaJNjR.d.cts +288 -0
  16. package/dist/restore-Clk62n0O.js +931 -0
  17. package/dist/restore-Dh0-Nvms.d.ts +288 -0
  18. package/dist/uam-transaction.cjs +44 -1
  19. package/dist/uam-transaction.d.cts +16 -1
  20. package/dist/uam-transaction.d.ts +16 -1
  21. package/dist/uam-transaction.js +44 -1
  22. package/dist/web.cjs +274 -0
  23. package/dist/web.d.cts +41 -0
  24. package/dist/web.d.ts +41 -0
  25. package/dist/web.js +273 -0
  26. package/package.json +28 -4
  27. package/src/adapters/node/plugins.ts +82 -0
  28. package/src/adapters/node/publish.ts +130 -0
  29. package/src/adapters/node/restore.ts +187 -0
  30. package/src/adapters/web/publish.ts +159 -0
  31. package/src/adapters/web/raster.ts +251 -0
  32. package/src/atlas/font.ts +95 -0
  33. package/src/atlas/inputs.ts +515 -0
  34. package/src/atlas/jta.ts +211 -0
  35. package/src/atlas/packing.ts +767 -0
  36. package/src/atlas.ts +116 -1221
  37. package/src/codegen.ts +106 -67
  38. package/src/index.ts +43 -3
  39. package/src/node.ts +8 -0
  40. package/src/plugins/types.ts +56 -0
  41. package/src/publish/contracts.ts +80 -0
  42. package/src/publish/external-resources.ts +117 -0
  43. package/src/publish/options.ts +180 -0
  44. package/src/publish/package-context.ts +608 -0
  45. package/src/publish/resource-references.ts +210 -0
  46. package/src/publish.ts +290 -968
  47. package/src/restore-internals/font.ts +100 -0
  48. package/src/restore-internals/movie-clip.ts +104 -0
  49. package/src/restore-internals/output-transaction.ts +164 -0
  50. package/src/restore.ts +112 -311
  51. package/src/shared-types.ts +4 -8
  52. package/src/uam-transaction.ts +68 -0
  53. package/src/utils.ts +28 -0
  54. package/src/web.ts +11 -0
package/dist/node.cjs ADDED
@@ -0,0 +1,256 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_publish = require("./publish-xFWT9Slz.cjs");
3
+ const require_restore = require("./restore-BW2xacB3.cjs");
4
+ //#region src/adapters/node/plugins.ts
5
+ const importNative$2 = new Function("id", "return import(id)");
6
+ async function loadPlugins(doc, pluginsDir) {
7
+ if (!pluginsDir) return [];
8
+ const fs = await importNative$2("node:fs/promises");
9
+ const path = await importNative$2("node:path");
10
+ let entries;
11
+ try {
12
+ entries = await fs.readdir(pluginsDir, { withFileTypes: true });
13
+ } catch {
14
+ return [];
15
+ }
16
+ const plugins = [];
17
+ for (const entry of entries) {
18
+ if (!entry.isDirectory()) continue;
19
+ const pluginDir = path.join(pluginsDir, entry.name);
20
+ try {
21
+ const manifest = await readPluginManifest(fs, path, pluginDir);
22
+ if (!manifest) continue;
23
+ const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
24
+ plugins.push({
25
+ name: manifest.name,
26
+ plugin
27
+ });
28
+ } catch (error) {
29
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${require_publish.formatPluginError(error)}`);
30
+ }
31
+ }
32
+ return plugins;
33
+ }
34
+ async function readPluginManifest(fs, path, pluginDir) {
35
+ const manifestPath = path.join(pluginDir, "package.json");
36
+ const content = await fs.readFile(manifestPath, "utf-8");
37
+ const manifest = JSON.parse(content);
38
+ if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
39
+ if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
40
+ return manifest;
41
+ }
42
+ function resolvePluginMain(path, pluginDir, manifest) {
43
+ const mainPath = path.resolve(pluginDir, manifest.main);
44
+ const relative = path.relative(pluginDir, mainPath);
45
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
46
+ return mainPath;
47
+ }
48
+ async function loadPlugin(mainPath) {
49
+ const { createJiti } = await importNative$2("jiti");
50
+ const mod = await createJiti(require("url").pathToFileURL(__filename).href).import(mainPath);
51
+ const defaultExport = mod.default;
52
+ return isObject(defaultExport) ? defaultExport : mod;
53
+ }
54
+ function isObject(value) {
55
+ return value !== null && typeof value === "object";
56
+ }
57
+ //#endregion
58
+ //#region src/adapters/node/publish.ts
59
+ const importNative$1 = new Function("id", "return import(id)");
60
+ async function createNodePublishFileSystem() {
61
+ const [fs, path] = await Promise.all([importNative$1("node:fs/promises"), importNative$1("node:path")]);
62
+ return {
63
+ async readFileRaw(filePath) {
64
+ const data = await fs.readFile(filePath);
65
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
66
+ },
67
+ async writeFileRaw(filePath, data) {
68
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
69
+ await fs.writeFile(filePath, data);
70
+ },
71
+ async mkdir(dirPath) {
72
+ await fs.mkdir(dirPath, { recursive: true });
73
+ },
74
+ async readdir(dirPath) {
75
+ return fs.readdir(dirPath);
76
+ },
77
+ async deleteFile(filePath) {
78
+ await fs.rm(filePath, { force: true });
79
+ },
80
+ join(...paths) {
81
+ return path.join(...paths);
82
+ }
83
+ };
84
+ }
85
+ async function resolveNodeAssetsPath(document, assetsPath) {
86
+ if (assetsPath) return assetsPath;
87
+ const projectDir = document.getProjectDir?.() ?? "";
88
+ if (!projectDir) return void 0;
89
+ return (await importNative$1("node:path")).join(projectDir, "assets");
90
+ }
91
+ async function loadSharpBackend() {
92
+ try {
93
+ const loaded = await importNative$1("sharp");
94
+ return loaded.default ?? loaded;
95
+ } catch {
96
+ return;
97
+ }
98
+ }
99
+ async function loadNodePublishPlugins(document, assetsPath) {
100
+ const projectDir = document.getProjectDir?.() || (assetsPath ? require_publish.resolveProjectBasePath(assetsPath) : "");
101
+ if (!projectDir) return [];
102
+ return loadPlugins(document, (await importNative$1("node:path")).join(projectDir, "plugins"));
103
+ }
104
+ /**
105
+ * Publish a FairyGUI project through the standard Node host adapter.
106
+ *
107
+ * The adapter owns Node filesystem, Sharp, and project plugin discovery.
108
+ * For custom environments, use the lower-level `publish()` core with explicit
109
+ * capabilities instead.
110
+ */
111
+ async function publishNode(options) {
112
+ const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
113
+ const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
114
+ const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
115
+ if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
116
+ await document.transform(require_publish.publish({
117
+ ...publishOptions,
118
+ basePath: assetsPath,
119
+ encoder,
120
+ atlas: {
121
+ ...atlas,
122
+ readFileRaw: fileSystem.readFileRaw
123
+ },
124
+ fs: fileSystem,
125
+ plugins
126
+ }));
127
+ }
128
+ //#endregion
129
+ //#region src/adapters/node/restore.ts
130
+ const importNative = new Function("id", "return import(id)");
131
+ async function createNodeRestoreFileSystem() {
132
+ const [fs, path] = await Promise.all([importNative("node:fs/promises"), importNative("node:path")]);
133
+ return {
134
+ async readFile(filePath) {
135
+ return fs.readFile(filePath, "utf-8");
136
+ },
137
+ async readFileRaw(filePath) {
138
+ const buffer = await fs.readFile(filePath);
139
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
140
+ },
141
+ async writeFile(filePath, content) {
142
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
143
+ await fs.writeFile(filePath, content, "utf-8");
144
+ },
145
+ async writeFileRaw(filePath, data) {
146
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
147
+ await fs.writeFile(filePath, data);
148
+ },
149
+ async mkdir(dirPath) {
150
+ await fs.mkdir(dirPath, { recursive: true });
151
+ },
152
+ async readdir(dirPath) {
153
+ return fs.readdir(dirPath);
154
+ },
155
+ async exists(filePath) {
156
+ try {
157
+ await fs.access(filePath);
158
+ return true;
159
+ } catch {
160
+ return false;
161
+ }
162
+ },
163
+ async isFile(filePath) {
164
+ try {
165
+ return (await fs.stat(filePath)).isFile();
166
+ } catch {
167
+ return false;
168
+ }
169
+ },
170
+ async resolvePath(filePath) {
171
+ try {
172
+ return await fs.realpath(filePath);
173
+ } catch {
174
+ return path.resolve(filePath);
175
+ }
176
+ },
177
+ async rm(targetPath, options) {
178
+ await fs.rm(targetPath, {
179
+ recursive: options?.recursive ?? false,
180
+ force: options?.force ?? false
181
+ });
182
+ },
183
+ async rename(from, to) {
184
+ await fs.rename(from, to);
185
+ },
186
+ join(...paths) {
187
+ return path.join(...paths);
188
+ },
189
+ dirname(filePath) {
190
+ return path.dirname(filePath);
191
+ }
192
+ };
193
+ }
194
+ async function createRestoreImageProcessors() {
195
+ let sharp;
196
+ try {
197
+ const loaded = await importNative("sharp");
198
+ sharp = loaded.default ?? loaded;
199
+ } catch {
200
+ throw new Error("restoreNode: Sharp is required to crop atlas images. Install sharp before restoring.");
201
+ }
202
+ async function extractImage(input) {
203
+ const targetPath = input.outputPath ?? input.sourcePath;
204
+ let image = sharp(input.sourcePath).extract({
205
+ left: input.left,
206
+ top: input.top,
207
+ width: input.width,
208
+ height: input.height
209
+ });
210
+ if (input.rotated) image = image.rotate(90);
211
+ const { data, info } = await image.png().toBuffer({ resolveWithObject: true });
212
+ if (input.expectedWidth > 0 && input.expectedHeight > 0 && (input.offsetX !== 0 || input.offsetY !== 0 || info.width !== input.expectedWidth || info.height !== input.expectedHeight)) {
213
+ if (input.offsetX < 0 || input.offsetY < 0 || input.offsetX + info.width > input.expectedWidth || input.offsetY + info.height > input.expectedHeight) throw new Error(`restore: Cropped image does not fit original canvas for ${targetPath}: crop ${info.width}x${info.height} at ${input.offsetX},${input.offsetY}, canvas ${input.expectedWidth}x${input.expectedHeight}`);
214
+ const composed = await sharp({ create: {
215
+ width: input.expectedWidth,
216
+ height: input.expectedHeight,
217
+ channels: 4,
218
+ background: {
219
+ r: 0,
220
+ g: 0,
221
+ b: 0,
222
+ alpha: 0
223
+ }
224
+ } }).composite([{
225
+ input: data,
226
+ left: input.offsetX,
227
+ top: input.offsetY
228
+ }]).png().toBuffer({ resolveWithObject: true });
229
+ if (composed.info.width !== input.expectedWidth || composed.info.height !== input.expectedHeight) throw new Error(`restore: Cropped image size mismatch for ${targetPath}: expected ${input.expectedWidth}x${input.expectedHeight}, got ${composed.info.width}x${composed.info.height}`);
230
+ return composed.data;
231
+ }
232
+ 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}`);
233
+ return data;
234
+ }
235
+ const fs = await importNative("node:fs/promises");
236
+ const path = await importNative("node:path");
237
+ return {
238
+ extractImage,
239
+ cropImage: async (input) => {
240
+ await fs.mkdir(path.dirname(input.outputPath), { recursive: true });
241
+ await fs.writeFile(input.outputPath, await extractImage(input));
242
+ }
243
+ };
244
+ }
245
+ /** Restore trusted local published artifacts through the standard Node host adapter. */
246
+ async function restoreNode(options) {
247
+ const [fs, imageProcessors] = await Promise.all([createNodeRestoreFileSystem(), createRestoreImageProcessors()]);
248
+ return require_restore.restore({
249
+ ...options,
250
+ fs,
251
+ ...imageProcessors
252
+ });
253
+ }
254
+ //#endregion
255
+ exports.publishNode = publishNode;
256
+ exports.restoreNode = restoreNode;
@@ -0,0 +1,36 @@
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";
3
+ import { Document } from "@openfairygui/core";
4
+
5
+ //#region src/adapters/node/publish.d.ts
6
+ interface PublishNodeOptions extends Omit<PublishOptions, 'atlas' | 'basePath' | 'encoder' | 'fs' | 'plugins'> {
7
+ document: Document;
8
+ /**
9
+ * Assets directory. Defaults to `<document project dir>/assets` when available.
10
+ */
11
+ assetsPath?: string;
12
+ /**
13
+ * Override the standard Sharp raster backend.
14
+ */
15
+ encoder?: AtlasRasterBackend;
16
+ /**
17
+ * Supply already-loaded hooks. Pass an empty array to skip project plugin discovery.
18
+ */
19
+ plugins?: LoadedPlugin[];
20
+ atlas?: Omit<NonNullable<PublishOptions['atlas']>, 'readFileRaw'>;
21
+ }
22
+ /**
23
+ * Publish a FairyGUI project through the standard Node host adapter.
24
+ *
25
+ * The adapter owns Node filesystem, Sharp, and project plugin discovery.
26
+ * For custom environments, use the lower-level `publish()` core with explicit
27
+ * capabilities instead.
28
+ */
29
+ declare function publishNode(options: PublishNodeOptions): Promise<void>;
30
+ //#endregion
31
+ //#region src/adapters/node/restore.d.ts
32
+ interface RestoreNodeOptions extends Omit<RestoreOptions, 'fs' | 'cropImage' | 'extractImage'> {}
33
+ /** Restore trusted local published artifacts through the standard Node host adapter. */
34
+ declare function restoreNode(options: RestoreNodeOptions): Promise<RestoreResult>;
35
+ //#endregion
36
+ export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode };
package/dist/node.d.ts ADDED
@@ -0,0 +1,36 @@
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";
3
+ import { Document } from "@openfairygui/core";
4
+
5
+ //#region src/adapters/node/publish.d.ts
6
+ interface PublishNodeOptions extends Omit<PublishOptions, 'atlas' | 'basePath' | 'encoder' | 'fs' | 'plugins'> {
7
+ document: Document;
8
+ /**
9
+ * Assets directory. Defaults to `<document project dir>/assets` when available.
10
+ */
11
+ assetsPath?: string;
12
+ /**
13
+ * Override the standard Sharp raster backend.
14
+ */
15
+ encoder?: AtlasRasterBackend;
16
+ /**
17
+ * Supply already-loaded hooks. Pass an empty array to skip project plugin discovery.
18
+ */
19
+ plugins?: LoadedPlugin[];
20
+ atlas?: Omit<NonNullable<PublishOptions['atlas']>, 'readFileRaw'>;
21
+ }
22
+ /**
23
+ * Publish a FairyGUI project through the standard Node host adapter.
24
+ *
25
+ * The adapter owns Node filesystem, Sharp, and project plugin discovery.
26
+ * For custom environments, use the lower-level `publish()` core with explicit
27
+ * capabilities instead.
28
+ */
29
+ declare function publishNode(options: PublishNodeOptions): Promise<void>;
30
+ //#endregion
31
+ //#region src/adapters/node/restore.d.ts
32
+ interface RestoreNodeOptions extends Omit<RestoreOptions, 'fs' | 'cropImage' | 'extractImage'> {}
33
+ /** Restore trusted local published artifacts through the standard Node host adapter. */
34
+ declare function restoreNode(options: RestoreNodeOptions): Promise<RestoreResult>;
35
+ //#endregion
36
+ export { type PublishNodeOptions, type RestoreNodeOptions, publishNode, restoreNode };
package/dist/node.js ADDED
@@ -0,0 +1,254 @@
1
+ import { l as resolveProjectBasePath, t as publish, u as formatPluginError } from "./publish-BJ_eelME.js";
2
+ import { t as restore } from "./restore-Clk62n0O.js";
3
+ //#region src/adapters/node/plugins.ts
4
+ const importNative$2 = new Function("id", "return import(id)");
5
+ async function loadPlugins(doc, pluginsDir) {
6
+ if (!pluginsDir) return [];
7
+ const fs = await importNative$2("node:fs/promises");
8
+ const path = await importNative$2("node:path");
9
+ let entries;
10
+ try {
11
+ entries = await fs.readdir(pluginsDir, { withFileTypes: true });
12
+ } catch {
13
+ return [];
14
+ }
15
+ const plugins = [];
16
+ for (const entry of entries) {
17
+ if (!entry.isDirectory()) continue;
18
+ const pluginDir = path.join(pluginsDir, entry.name);
19
+ try {
20
+ const manifest = await readPluginManifest(fs, path, pluginDir);
21
+ if (!manifest) continue;
22
+ const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
23
+ plugins.push({
24
+ name: manifest.name,
25
+ plugin
26
+ });
27
+ } catch (error) {
28
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
29
+ }
30
+ }
31
+ return plugins;
32
+ }
33
+ async function readPluginManifest(fs, path, pluginDir) {
34
+ const manifestPath = path.join(pluginDir, "package.json");
35
+ const content = await fs.readFile(manifestPath, "utf-8");
36
+ const manifest = JSON.parse(content);
37
+ if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
38
+ if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
39
+ return manifest;
40
+ }
41
+ function resolvePluginMain(path, pluginDir, manifest) {
42
+ const mainPath = path.resolve(pluginDir, manifest.main);
43
+ const relative = path.relative(pluginDir, mainPath);
44
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
45
+ return mainPath;
46
+ }
47
+ async function loadPlugin(mainPath) {
48
+ const { createJiti } = await importNative$2("jiti");
49
+ const mod = await createJiti(import.meta.url).import(mainPath);
50
+ const defaultExport = mod.default;
51
+ return isObject(defaultExport) ? defaultExport : mod;
52
+ }
53
+ function isObject(value) {
54
+ return value !== null && typeof value === "object";
55
+ }
56
+ //#endregion
57
+ //#region src/adapters/node/publish.ts
58
+ const importNative$1 = new Function("id", "return import(id)");
59
+ async function createNodePublishFileSystem() {
60
+ const [fs, path] = await Promise.all([importNative$1("node:fs/promises"), importNative$1("node:path")]);
61
+ return {
62
+ async readFileRaw(filePath) {
63
+ const data = await fs.readFile(filePath);
64
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
65
+ },
66
+ async writeFileRaw(filePath, data) {
67
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
68
+ await fs.writeFile(filePath, data);
69
+ },
70
+ async mkdir(dirPath) {
71
+ await fs.mkdir(dirPath, { recursive: true });
72
+ },
73
+ async readdir(dirPath) {
74
+ return fs.readdir(dirPath);
75
+ },
76
+ async deleteFile(filePath) {
77
+ await fs.rm(filePath, { force: true });
78
+ },
79
+ join(...paths) {
80
+ return path.join(...paths);
81
+ }
82
+ };
83
+ }
84
+ async function resolveNodeAssetsPath(document, assetsPath) {
85
+ if (assetsPath) return assetsPath;
86
+ const projectDir = document.getProjectDir?.() ?? "";
87
+ if (!projectDir) return void 0;
88
+ return (await importNative$1("node:path")).join(projectDir, "assets");
89
+ }
90
+ async function loadSharpBackend() {
91
+ try {
92
+ const loaded = await importNative$1("sharp");
93
+ return loaded.default ?? loaded;
94
+ } catch {
95
+ return;
96
+ }
97
+ }
98
+ async function loadNodePublishPlugins(document, assetsPath) {
99
+ const projectDir = document.getProjectDir?.() || (assetsPath ? resolveProjectBasePath(assetsPath) : "");
100
+ if (!projectDir) return [];
101
+ return loadPlugins(document, (await importNative$1("node:path")).join(projectDir, "plugins"));
102
+ }
103
+ /**
104
+ * Publish a FairyGUI project through the standard Node host adapter.
105
+ *
106
+ * The adapter owns Node filesystem, Sharp, and project plugin discovery.
107
+ * For custom environments, use the lower-level `publish()` core with explicit
108
+ * capabilities instead.
109
+ */
110
+ async function publishNode(options) {
111
+ const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
112
+ const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
113
+ const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
114
+ if (!encoder) throw new Error("publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.");
115
+ await document.transform(publish({
116
+ ...publishOptions,
117
+ basePath: assetsPath,
118
+ encoder,
119
+ atlas: {
120
+ ...atlas,
121
+ readFileRaw: fileSystem.readFileRaw
122
+ },
123
+ fs: fileSystem,
124
+ plugins
125
+ }));
126
+ }
127
+ //#endregion
128
+ //#region src/adapters/node/restore.ts
129
+ const importNative = new Function("id", "return import(id)");
130
+ async function createNodeRestoreFileSystem() {
131
+ const [fs, path] = await Promise.all([importNative("node:fs/promises"), importNative("node:path")]);
132
+ return {
133
+ async readFile(filePath) {
134
+ return fs.readFile(filePath, "utf-8");
135
+ },
136
+ async readFileRaw(filePath) {
137
+ const buffer = await fs.readFile(filePath);
138
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
139
+ },
140
+ async writeFile(filePath, content) {
141
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
142
+ await fs.writeFile(filePath, content, "utf-8");
143
+ },
144
+ async writeFileRaw(filePath, data) {
145
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
146
+ await fs.writeFile(filePath, data);
147
+ },
148
+ async mkdir(dirPath) {
149
+ await fs.mkdir(dirPath, { recursive: true });
150
+ },
151
+ async readdir(dirPath) {
152
+ return fs.readdir(dirPath);
153
+ },
154
+ async exists(filePath) {
155
+ try {
156
+ await fs.access(filePath);
157
+ return true;
158
+ } catch {
159
+ return false;
160
+ }
161
+ },
162
+ async isFile(filePath) {
163
+ try {
164
+ return (await fs.stat(filePath)).isFile();
165
+ } catch {
166
+ return false;
167
+ }
168
+ },
169
+ async resolvePath(filePath) {
170
+ try {
171
+ return await fs.realpath(filePath);
172
+ } catch {
173
+ return path.resolve(filePath);
174
+ }
175
+ },
176
+ async rm(targetPath, options) {
177
+ await fs.rm(targetPath, {
178
+ recursive: options?.recursive ?? false,
179
+ force: options?.force ?? false
180
+ });
181
+ },
182
+ async rename(from, to) {
183
+ await fs.rename(from, to);
184
+ },
185
+ join(...paths) {
186
+ return path.join(...paths);
187
+ },
188
+ dirname(filePath) {
189
+ return path.dirname(filePath);
190
+ }
191
+ };
192
+ }
193
+ async function createRestoreImageProcessors() {
194
+ let sharp;
195
+ try {
196
+ const loaded = await importNative("sharp");
197
+ sharp = loaded.default ?? loaded;
198
+ } catch {
199
+ throw new Error("restoreNode: Sharp is required to crop atlas images. Install sharp before restoring.");
200
+ }
201
+ async function extractImage(input) {
202
+ const targetPath = input.outputPath ?? input.sourcePath;
203
+ let image = sharp(input.sourcePath).extract({
204
+ left: input.left,
205
+ top: input.top,
206
+ width: input.width,
207
+ height: input.height
208
+ });
209
+ if (input.rotated) image = image.rotate(90);
210
+ const { data, info } = await image.png().toBuffer({ resolveWithObject: true });
211
+ if (input.expectedWidth > 0 && input.expectedHeight > 0 && (input.offsetX !== 0 || input.offsetY !== 0 || info.width !== input.expectedWidth || info.height !== input.expectedHeight)) {
212
+ if (input.offsetX < 0 || input.offsetY < 0 || input.offsetX + info.width > input.expectedWidth || input.offsetY + info.height > input.expectedHeight) throw new Error(`restore: Cropped image does not fit original canvas for ${targetPath}: crop ${info.width}x${info.height} at ${input.offsetX},${input.offsetY}, canvas ${input.expectedWidth}x${input.expectedHeight}`);
213
+ const composed = await sharp({ create: {
214
+ width: input.expectedWidth,
215
+ height: input.expectedHeight,
216
+ channels: 4,
217
+ background: {
218
+ r: 0,
219
+ g: 0,
220
+ b: 0,
221
+ alpha: 0
222
+ }
223
+ } }).composite([{
224
+ input: data,
225
+ left: input.offsetX,
226
+ top: input.offsetY
227
+ }]).png().toBuffer({ resolveWithObject: true });
228
+ if (composed.info.width !== input.expectedWidth || composed.info.height !== input.expectedHeight) throw new Error(`restore: Cropped image size mismatch for ${targetPath}: expected ${input.expectedWidth}x${input.expectedHeight}, got ${composed.info.width}x${composed.info.height}`);
229
+ return composed.data;
230
+ }
231
+ 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
+ return data;
233
+ }
234
+ const fs = await importNative("node:fs/promises");
235
+ const path = await importNative("node:path");
236
+ return {
237
+ extractImage,
238
+ cropImage: async (input) => {
239
+ await fs.mkdir(path.dirname(input.outputPath), { recursive: true });
240
+ await fs.writeFile(input.outputPath, await extractImage(input));
241
+ }
242
+ };
243
+ }
244
+ /** Restore trusted local published artifacts through the standard Node host adapter. */
245
+ async function restoreNode(options) {
246
+ const [fs, imageProcessors] = await Promise.all([createNodeRestoreFileSystem(), createRestoreImageProcessors()]);
247
+ return restore({
248
+ ...options,
249
+ fs,
250
+ ...imageProcessors
251
+ });
252
+ }
253
+ //#endregion
254
+ export { publishNode, restoreNode };