@openfairygui/cli 0.2.0-alpha.11 → 0.2.0-alpha.13

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 CHANGED
@@ -3,7 +3,6 @@ import { Command } from "commander";
3
3
  import { createNodeBackendRuntime } from "@openfairygui/backend/node";
4
4
  import * as path$1 from "node:path";
5
5
  import path from "node:path";
6
- import { createJiti } from "jiti";
7
6
  import * as fs$1 from "node:fs/promises";
8
7
  import fs from "node:fs/promises";
9
8
  //#region src/commands/backend-capabilities.ts
@@ -21884,7 +21883,7 @@ async function resolveEditorCompatibleResourceOrder(pkg, allResources, options)
21884
21883
  *
21885
21884
  * This transform performs MaxRects bin-packing on all ImageResource items
21886
21885
  * within each package, creating Atlas and Sprite property nodes. When an
21887
- * `encoder` (sharp) is provided, it also composites the actual PNG files.
21886
+ * a raster backend is provided, it also composites the actual PNG files.
21888
21887
  *
21889
21888
  * When `trimImage` is enabled and encoder is available, transparent pixels
21890
21889
  * at image edges are trimmed before packing. The trimmed offset and original
@@ -22486,7 +22485,7 @@ function groupStandaloneInputs(doc, inputs, options) {
22486
22485
  };
22487
22486
  }
22488
22487
  /**
22489
- * Trim transparent edges from an image using sharp.
22488
+ * Trim transparent edges from an image using the host raster backend.
22490
22489
  * Returns the trimmed buffer, dimensions, and offsets.
22491
22490
  * Falls back to the original image if trim fails (e.g. no alpha channel, no transparent edges).
22492
22491
  */
@@ -22576,7 +22575,11 @@ async function _collectImage(resource, pkg, inputs, encoder, options, doTrim, lo
22576
22575
  }
22577
22576
  sourceHasAlpha = metadata.hasAlpha === true || metadata.channels === 4;
22578
22577
  if (/\.svg$/i.test(resolveImageFileName$1(resource)) && declaredWidth > 0 && declaredHeight > 0) {
22579
- rasterizedBuffer = await encoder(filePath).resize(declaredWidth, declaredHeight, { fit: "fill" }).png().toBuffer();
22578
+ rasterizedBuffer = await encoder(filePath).resize({
22579
+ width: declaredWidth,
22580
+ height: declaredHeight,
22581
+ fit: "fill"
22582
+ }).png().toBuffer();
22580
22583
  sourceHasAlpha = true;
22581
22584
  }
22582
22585
  } catch {
@@ -23045,61 +23048,10 @@ const FGUI_TYPESCRIPT_BINDER_TEMPLATE = `{{generatedMark}}
23045
23048
  }
23046
23049
  `;
23047
23050
  //#endregion
23048
- //#region ../functions/src/plugins/loader.ts
23049
- const importNative = new Function("id", "return import(id)");
23050
- async function loadPlugins(doc, pluginsDir) {
23051
- if (!pluginsDir) return [];
23052
- const fs = await importNative("node:fs/promises");
23053
- const path = await importNative("node:path");
23054
- let entries;
23055
- try {
23056
- entries = await fs.readdir(pluginsDir, { withFileTypes: true });
23057
- } catch {
23058
- return [];
23059
- }
23060
- const plugins = [];
23061
- for (const entry of entries) {
23062
- if (!entry.isDirectory()) continue;
23063
- const pluginDir = path.join(pluginsDir, entry.name);
23064
- try {
23065
- const manifest = await readPluginManifest(fs, path, pluginDir);
23066
- if (!manifest) continue;
23067
- const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
23068
- plugins.push({
23069
- name: manifest.name,
23070
- plugin
23071
- });
23072
- } catch (error) {
23073
- doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
23074
- }
23075
- }
23076
- return plugins;
23077
- }
23051
+ //#region ../functions/src/plugins/types.ts
23078
23052
  function formatPluginError(error) {
23079
23053
  return error instanceof Error ? error.message : String(error);
23080
23054
  }
23081
- async function readPluginManifest(fs, path, pluginDir) {
23082
- const manifestPath = path.join(pluginDir, "package.json");
23083
- const content = await fs.readFile(manifestPath, "utf-8");
23084
- const manifest = JSON.parse(content);
23085
- if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
23086
- if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
23087
- return manifest;
23088
- }
23089
- function resolvePluginMain(path, pluginDir, manifest) {
23090
- const mainPath = path.resolve(pluginDir, manifest.main);
23091
- const relative = path.relative(pluginDir, mainPath);
23092
- if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
23093
- return mainPath;
23094
- }
23095
- async function loadPlugin(mainPath) {
23096
- const mod = await createJiti(import.meta.url).import(mainPath);
23097
- const defaultExport = mod.default;
23098
- return isObject(defaultExport) ? defaultExport : mod;
23099
- }
23100
- function isObject(value) {
23101
- return value !== null && typeof value === "object";
23102
- }
23103
23055
  //#endregion
23104
23056
  //#region ../functions/src/codegen.ts
23105
23057
  const AUTO_GENERATED_CODE_MARK = "/** This is an automatically generated class by FairyGUI. Please do not modify it. **/";
@@ -24331,14 +24283,6 @@ async function runPublishPluginHook(plugins, hook, doc, options) {
24331
24283
  }
24332
24284
  }
24333
24285
  }
24334
- function resolvePublishPluginsDir(doc, options) {
24335
- const fs = options.fs;
24336
- const projectDir = doc.getProjectDir?.() ?? "";
24337
- if (projectDir) return fs?.join ? fs.join(projectDir, "plugins") : `${projectDir.replace(/[/\\]+$/, "")}/plugins`;
24338
- const projectBasePath = resolveProjectBasePath(options.basePath);
24339
- if (!projectBasePath) return "";
24340
- return fs?.join ? fs.join(projectBasePath, "plugins") : `${projectBasePath.replace(/[/\\]+$/, "")}/plugins`;
24341
- }
24342
24286
  const UNITY_PROJECT_TYPE = ProjectType.Unity;
24343
24287
  const COCOS_CREATOR_PROJECT_TYPE = ProjectType.CocosCreator;
24344
24288
  function resolveDefaultPublishFileExtension(projectType, publishSettings) {
@@ -24942,10 +24886,13 @@ async function exportPackageExternalResources(pkg, outputDir, basePath, fs, read
24942
24886
  * Publishes a FairyGUI project.
24943
24887
  *
24944
24888
  * Orchestrates:
24945
- * 1. Atlas packing (MaxRects layout + optional sharp compositing)
24889
+ * 1. Atlas packing (MaxRects layout + optional raster compositing)
24946
24890
  * 2. Per-package .fui binary serialization
24947
24891
  * 3. File writing to the output directory
24948
24892
  *
24893
+ * This is the capability-injected core. Standard hosts should use
24894
+ * `publishNode()` or `publishBrowser()` through their dedicated entries.
24895
+ *
24949
24896
  * ```ts
24950
24897
  * import sharp from 'sharp';
24951
24898
  * const io = new NodeIO();
@@ -25055,8 +25002,7 @@ function publish(options) {
25055
25002
  const root = doc.getRoot();
25056
25003
  const logger = doc.getLogger();
25057
25004
  const projectBasePath = resolveProjectBasePath(options.basePath) || doc.getProjectDir?.() || "";
25058
- const pluginsDir = resolvePublishPluginsDir(doc, options);
25059
- const plugins = pluginsDir ? await loadPlugins(doc, pluginsDir) : [];
25005
+ const plugins = options.plugins ?? [];
25060
25006
  await runPublishPluginHook(plugins, "onPublishStart", doc, options);
25061
25007
  const resolved = resolveProjectPublishConfig();
25062
25008
  let allPackages = root.listPackages();
@@ -25093,7 +25039,7 @@ function publish(options) {
25093
25039
  if (unresolvedPlan) throw new Error(`publish: no output directory resolved for package "${unresolvedPlan.pkg.getName()}". Provide --output, or configure global publish.path / package publishPath.`);
25094
25040
  const writerFs = toBinaryWriterFileSystem(options.fs);
25095
25041
  for (const plan of plans) await publishPackage(plan, writerFs, allDocPackages.indexOf(plan.pkg));
25096
- await publishCodeGeneration(doc, {
25042
+ if (options.codeGeneration !== false) await publishCodeGeneration(doc, {
25097
25043
  basePath: options.basePath,
25098
25044
  fs: options.fs,
25099
25045
  packages: allPackages,
@@ -25323,6 +25269,131 @@ function printReport(report) {
25323
25269
  }
25324
25270
  }
25325
25271
  //#endregion
25272
+ //#region ../functions/src/adapters/node/plugins.ts
25273
+ const importNative$1 = new Function("id", "return import(id)");
25274
+ async function loadPlugins(doc, pluginsDir) {
25275
+ if (!pluginsDir) return [];
25276
+ const fs = await importNative$1("node:fs/promises");
25277
+ const path = await importNative$1("node:path");
25278
+ let entries;
25279
+ try {
25280
+ entries = await fs.readdir(pluginsDir, { withFileTypes: true });
25281
+ } catch {
25282
+ return [];
25283
+ }
25284
+ const plugins = [];
25285
+ for (const entry of entries) {
25286
+ if (!entry.isDirectory()) continue;
25287
+ const pluginDir = path.join(pluginsDir, entry.name);
25288
+ try {
25289
+ const manifest = await readPluginManifest(fs, path, pluginDir);
25290
+ if (!manifest) continue;
25291
+ const plugin = await loadPlugin(resolvePluginMain(path, pluginDir, manifest));
25292
+ plugins.push({
25293
+ name: manifest.name,
25294
+ plugin
25295
+ });
25296
+ } catch (error) {
25297
+ doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
25298
+ }
25299
+ }
25300
+ return plugins;
25301
+ }
25302
+ async function readPluginManifest(fs, path, pluginDir) {
25303
+ const manifestPath = path.join(pluginDir, "package.json");
25304
+ const content = await fs.readFile(manifestPath, "utf-8");
25305
+ const manifest = JSON.parse(content);
25306
+ if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
25307
+ if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
25308
+ return manifest;
25309
+ }
25310
+ function resolvePluginMain(path, pluginDir, manifest) {
25311
+ const mainPath = path.resolve(pluginDir, manifest.main);
25312
+ const relative = path.relative(pluginDir, mainPath);
25313
+ if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
25314
+ return mainPath;
25315
+ }
25316
+ async function loadPlugin(mainPath) {
25317
+ const { createJiti } = await importNative$1("jiti");
25318
+ const mod = await createJiti(import.meta.url).import(mainPath);
25319
+ const defaultExport = mod.default;
25320
+ return isObject(defaultExport) ? defaultExport : mod;
25321
+ }
25322
+ function isObject(value) {
25323
+ return value !== null && typeof value === "object";
25324
+ }
25325
+ //#endregion
25326
+ //#region ../functions/src/adapters/node/publish.ts
25327
+ const importNative = new Function("id", "return import(id)");
25328
+ async function createNodePublishFileSystem() {
25329
+ const [fs, path] = await Promise.all([importNative("node:fs/promises"), importNative("node:path")]);
25330
+ return {
25331
+ async readFileRaw(filePath) {
25332
+ const data = await fs.readFile(filePath);
25333
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
25334
+ },
25335
+ async writeFileRaw(filePath, data) {
25336
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
25337
+ await fs.writeFile(filePath, data);
25338
+ },
25339
+ async mkdir(dirPath) {
25340
+ await fs.mkdir(dirPath, { recursive: true });
25341
+ },
25342
+ async readdir(dirPath) {
25343
+ return fs.readdir(dirPath);
25344
+ },
25345
+ async deleteFile(filePath) {
25346
+ await fs.rm(filePath, { force: true });
25347
+ },
25348
+ join(...paths) {
25349
+ return path.join(...paths);
25350
+ }
25351
+ };
25352
+ }
25353
+ async function resolveNodeAssetsPath(document, assetsPath) {
25354
+ if (assetsPath) return assetsPath;
25355
+ const projectDir = document.getProjectDir?.() ?? "";
25356
+ if (!projectDir) return void 0;
25357
+ return (await importNative("node:path")).join(projectDir, "assets");
25358
+ }
25359
+ async function loadSharpBackend() {
25360
+ try {
25361
+ const sharp = await importNative("sharp");
25362
+ return sharp.default ?? sharp;
25363
+ } catch {
25364
+ return;
25365
+ }
25366
+ }
25367
+ async function loadNodePublishPlugins(document, assetsPath) {
25368
+ const projectDir = document.getProjectDir?.() || (assetsPath ? resolveProjectBasePath(assetsPath) : "");
25369
+ if (!projectDir) return [];
25370
+ return loadPlugins(document, (await importNative("node:path")).join(projectDir, "plugins"));
25371
+ }
25372
+ /**
25373
+ * Publish a FairyGUI project through the standard Node host adapter.
25374
+ *
25375
+ * The adapter owns Node filesystem, Sharp, and project plugin discovery.
25376
+ * For custom environments, use the lower-level `publish()` core with explicit
25377
+ * capabilities instead.
25378
+ */
25379
+ async function publishNode(options) {
25380
+ const { document, assetsPath: configuredAssetsPath, atlas, encoder: configuredEncoder, plugins: configuredPlugins, ...publishOptions } = options;
25381
+ const [fileSystem, assetsPath] = await Promise.all([createNodePublishFileSystem(), resolveNodeAssetsPath(document, configuredAssetsPath)]);
25382
+ const [encoder, plugins] = await Promise.all([configuredEncoder === void 0 ? loadSharpBackend() : Promise.resolve(configuredEncoder), configuredPlugins === void 0 ? loadNodePublishPlugins(document, assetsPath) : Promise.resolve(configuredPlugins)]);
25383
+ if (!encoder) document.getLogger().warn("publish: Sharp is unavailable; atlas layout will be generated without PNG output.");
25384
+ await document.transform(publish({
25385
+ ...publishOptions,
25386
+ basePath: assetsPath,
25387
+ encoder,
25388
+ atlas: {
25389
+ ...atlas,
25390
+ readFileRaw: fileSystem.readFileRaw
25391
+ },
25392
+ fs: fileSystem,
25393
+ plugins
25394
+ }));
25395
+ }
25396
+ //#endregion
25326
25397
  //#region src/utils/project-type.ts
25327
25398
  function parseProjectType(value) {
25328
25399
  if (!value) return void 0;
@@ -25369,54 +25440,16 @@ function registerPublishCommand(program) {
25369
25440
  });
25370
25441
  console.log(`Settings: ext=${resolved.fileExtension}, compressed=${resolved.compressed}`);
25371
25442
  if (options.branch) console.log(`Active branch: ${options.branch}`);
25372
- const atlasConfig = {
25373
- ...resolved.atlas,
25374
- readFileRaw: async (filePath) => {
25375
- const buf = await fs.readFile(filePath);
25376
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25377
- }
25378
- };
25379
- let encoder;
25380
- try {
25381
- const sharp = await import("sharp");
25382
- encoder = sharp.default ?? sharp;
25383
- console.log("Sharp loaded — atlas PNGs will be generated.");
25384
- } catch {
25385
- console.log("Sharp not available — atlas PNGs will NOT be generated (layout only).");
25386
- console.log(" Install sharp to enable: pnpm add sharp");
25387
- }
25388
- await doc.transform(publish({
25443
+ await publishNode({
25444
+ document: doc,
25389
25445
  output: outputDir,
25390
25446
  compressed: resolved.compressed,
25391
25447
  fileExtension: resolved.fileExtension,
25392
25448
  packages: resolved.packages,
25393
- fs: {
25394
- async readFileRaw(filePath) {
25395
- const buf = await fs.readFile(filePath);
25396
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
25397
- },
25398
- async writeFileRaw(filePath, data) {
25399
- await fs.mkdir(path.dirname(filePath), { recursive: true });
25400
- await fs.writeFile(filePath, data);
25401
- },
25402
- async mkdir(dirPath) {
25403
- await fs.mkdir(dirPath, { recursive: true });
25404
- },
25405
- async readdir(dirPath) {
25406
- return fs.readdir(dirPath);
25407
- },
25408
- async deleteFile(filePath) {
25409
- await fs.rm(filePath, { force: true });
25410
- },
25411
- join(...paths) {
25412
- return path.join(...paths);
25413
- }
25414
- },
25415
- encoder,
25416
- basePath: path.join(projectRootDir, "assets"),
25417
- atlas: atlasConfig,
25449
+ assetsPath: path.join(projectRootDir, "assets"),
25450
+ atlas: resolved.atlas,
25418
25451
  branch: options.branch
25419
- }));
25452
+ });
25420
25453
  console.log(`\nDone!${outputDir ? ` Output override: ${outputDir}` : ""}`);
25421
25454
  });
25422
25455
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/cli",
3
- "version": "0.2.0-alpha.11",
3
+ "version": "0.2.0-alpha.13",
4
4
  "description": "FairyGUI Headless Authoring SDK — command-line interface.",
5
5
  "author": "OpenFairyGUI Contributors",
6
6
  "license": "MIT",
@@ -33,13 +33,13 @@
33
33
  "restore"
34
34
  ],
35
35
  "devDependencies": {
36
- "@openfairygui/core": "0.2.0-alpha.11",
37
- "@openfairygui/functions": "0.2.0-alpha.11"
36
+ "@openfairygui/core": "0.2.0-alpha.13",
37
+ "@openfairygui/functions": "0.2.0-alpha.13"
38
38
  },
39
39
  "dependencies": {
40
40
  "commander": "^14.0.2",
41
41
  "jiti": "^2.7.0",
42
- "@openfairygui/backend": "0.2.0-alpha.10"
42
+ "@openfairygui/backend": "0.2.0-alpha.13"
43
43
  },
44
44
  "optionalDependencies": {
45
45
  "sharp": ">=0.33.0"
@@ -1,7 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { NodeIO } from '@openfairygui/core/node';
3
- import { publish, resolvePublishOptions, type PublishOptions } from '@openfairygui/functions';
4
- import fs from 'node:fs/promises';
3
+ import { resolvePublishOptions } from '@openfairygui/functions';
4
+ import { publishNode } from '@openfairygui/functions/node';
5
5
  import path from 'node:path';
6
6
  import { resolveFairyPath } from '../utils/project-input.js';
7
7
  import { parseProjectType } from '../utils/project-type.js';
@@ -23,7 +23,10 @@ export function registerPublishCommand(program: Command): void {
23
23
  .option('-c, --compressed', 'Compress binary data (overrides project setting)')
24
24
  .option('-p, --packages <a,b,c>', 'Only publish specific packages (comma-separated)')
25
25
  .option('-b, --branch <name>', 'Active branch used by "主干合并活跃分支"; omit for main branch')
26
- .option('-t, --project-type <name|id>', 'Override project type (for example: unity, layabox, cocoscreator, 0, 4, 3)')
26
+ .option(
27
+ '-t, --project-type <name|id>',
28
+ 'Override project type (for example: unity, layabox, cocoscreator, 0, 4, 3)',
29
+ )
27
30
  .action(async (projectDir: string, options: PublishCommandOptions) => {
28
31
  const fairyPath = await resolveFairyPath(projectDir);
29
32
  const projectRootDir = path.dirname(fairyPath);
@@ -48,60 +51,16 @@ export function registerPublishCommand(program: Command): void {
48
51
  console.log(`Active branch: ${options.branch}`);
49
52
  }
50
53
 
51
- const atlasConfig: NonNullable<PublishOptions['atlas']> = {
52
- ...resolved.atlas,
53
- readFileRaw: async (filePath: string) => {
54
- const buf = await fs.readFile(filePath);
55
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
56
- },
57
- };
58
-
59
- let encoder: PublishOptions['encoder'];
60
- try {
61
- const sharp = await import('sharp');
62
- encoder = sharp.default ?? sharp;
63
- console.log('Sharp loaded — atlas PNGs will be generated.');
64
- } catch {
65
- console.log('Sharp not available — atlas PNGs will NOT be generated (layout only).');
66
- console.log(' Install sharp to enable: pnpm add sharp');
67
- }
68
-
69
- const publishFs: NonNullable<PublishOptions['fs']> = {
70
- async readFileRaw(filePath: string): Promise<Uint8Array> {
71
- const buf = await fs.readFile(filePath);
72
- return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
73
- },
74
- async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
75
- await fs.mkdir(path.dirname(filePath), { recursive: true });
76
- await fs.writeFile(filePath, data);
77
- },
78
- async mkdir(dirPath: string): Promise<void> {
79
- await fs.mkdir(dirPath, { recursive: true });
80
- },
81
- async readdir(dirPath: string): Promise<string[]> {
82
- return fs.readdir(dirPath);
83
- },
84
- async deleteFile(filePath: string): Promise<void> {
85
- await fs.rm(filePath, { force: true });
86
- },
87
- join(...paths: string[]): string {
88
- return path.join(...paths);
89
- },
90
- };
91
-
92
- await doc.transform(
93
- publish({
94
- output: outputDir,
95
- compressed: resolved.compressed,
96
- fileExtension: resolved.fileExtension,
97
- packages: resolved.packages,
98
- fs: publishFs,
99
- encoder,
100
- basePath: path.join(projectRootDir, 'assets'),
101
- atlas: atlasConfig,
102
- branch: options.branch,
103
- }),
104
- );
54
+ await publishNode({
55
+ document: doc,
56
+ output: outputDir,
57
+ compressed: resolved.compressed,
58
+ fileExtension: resolved.fileExtension,
59
+ packages: resolved.packages,
60
+ assetsPath: path.join(projectRootDir, 'assets'),
61
+ atlas: resolved.atlas,
62
+ branch: options.branch,
63
+ });
105
64
 
106
65
  console.log(`\nDone!${outputDir ? ` Output override: ${outputDir}` : ''}`);
107
66
  });