@openfairygui/functions 0.2.0-alpha.7 → 0.2.0
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/README.md +52 -6
- package/dist/atlas-C6tbl7nn.d.ts +193 -0
- package/dist/atlas-CHsu2Y8i.d.cts +193 -0
- package/dist/index.cjs +17 -3603
- package/dist/index.d.cts +5 -294
- package/dist/index.d.ts +5 -294
- package/dist/index.js +4 -3595
- package/dist/node.cjs +256 -0
- package/dist/node.d.cts +36 -0
- package/dist/node.d.ts +36 -0
- package/dist/node.js +254 -0
- package/dist/publish-CykUJfVa.cjs +3269 -0
- package/dist/publish-DXoaC1Nl.js +3174 -0
- package/dist/restore-BQp01WY3.js +914 -0
- package/dist/restore-BeWaJNjR.d.cts +288 -0
- package/dist/restore-CEywQUHz.cjs +919 -0
- package/dist/restore-Dh0-Nvms.d.ts +288 -0
- package/dist/uam-transaction.cjs +29 -14
- package/dist/uam-transaction.d.cts +2 -1
- package/dist/uam-transaction.d.ts +2 -1
- package/dist/uam-transaction.js +30 -16
- package/dist/web.cjs +440 -0
- package/dist/web.d.cts +44 -0
- package/dist/web.d.ts +44 -0
- package/dist/web.js +439 -0
- package/package.json +29 -4
- package/src/adapters/node/plugins.ts +82 -0
- package/src/adapters/node/publish.ts +130 -0
- package/src/adapters/node/restore.ts +187 -0
- package/src/adapters/web/publish.ts +196 -0
- package/src/adapters/web/raster.ts +421 -0
- package/src/atlas/font.ts +95 -0
- package/src/atlas/inputs.ts +445 -0
- package/src/atlas/jta.ts +157 -0
- package/src/atlas/packing.ts +762 -0
- package/src/atlas.ts +129 -1221
- package/src/codegen.ts +108 -82
- package/src/index.ts +43 -3
- package/src/node.ts +8 -0
- package/src/path-utils.ts +40 -0
- package/src/plugins/types.ts +56 -0
- package/src/publish/contracts.ts +80 -0
- package/src/publish/external-resources.ts +117 -0
- package/src/publish/options.ts +180 -0
- package/src/publish/package-context.ts +608 -0
- package/src/publish/resource-references.ts +210 -0
- package/src/publish.ts +327 -975
- package/src/restore-internals/font.ts +100 -0
- package/src/restore-internals/movie-clip.ts +104 -0
- package/src/restore-internals/output-transaction.ts +124 -0
- package/src/restore.ts +122 -311
- package/src/shared-types.ts +4 -8
- package/src/uam-transaction.ts +34 -17
- package/src/utils.ts +28 -0
- package/src/web.ts +11 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { Document } from '@openfairygui/core';
|
|
2
|
+
import { resolveProjectBasePath } from '../../codegen.js';
|
|
3
|
+
import type { LoadedPlugin } from '../../plugins/types.js';
|
|
4
|
+
import type { AtlasRasterBackend, PublishFileSystem } from '../../publish/contracts.js';
|
|
5
|
+
import { type PublishOptions, publish } from '../../publish.js';
|
|
6
|
+
import { loadPlugins } from './plugins.js';
|
|
7
|
+
|
|
8
|
+
const importNative = new Function('id', 'return import(id)') as <T>(id: string) => Promise<T>;
|
|
9
|
+
|
|
10
|
+
interface NodePublishFileSystem extends PublishFileSystem {
|
|
11
|
+
readFileRaw(path: string): Promise<Uint8Array>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PublishNodeOptions extends Omit<PublishOptions, 'atlas' | 'basePath' | 'encoder' | 'fs' | 'plugins'> {
|
|
15
|
+
document: Document;
|
|
16
|
+
/**
|
|
17
|
+
* Assets directory. Defaults to `<document project dir>/assets` when available.
|
|
18
|
+
*/
|
|
19
|
+
assetsPath?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Override the standard Sharp raster backend.
|
|
22
|
+
*/
|
|
23
|
+
encoder?: AtlasRasterBackend;
|
|
24
|
+
/**
|
|
25
|
+
* Supply already-loaded hooks. Pass an empty array to skip project plugin discovery.
|
|
26
|
+
*/
|
|
27
|
+
plugins?: LoadedPlugin[];
|
|
28
|
+
atlas?: Omit<NonNullable<PublishOptions['atlas']>, 'readFileRaw'>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function createNodePublishFileSystem(): Promise<NodePublishFileSystem> {
|
|
32
|
+
const [fs, path] = await Promise.all([
|
|
33
|
+
importNative<typeof import('node:fs/promises')>('node:fs/promises'),
|
|
34
|
+
importNative<typeof import('node:path')>('node:path'),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
async readFileRaw(filePath: string): Promise<Uint8Array> {
|
|
39
|
+
const data = await fs.readFile(filePath);
|
|
40
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
41
|
+
},
|
|
42
|
+
async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
43
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
44
|
+
await fs.writeFile(filePath, data);
|
|
45
|
+
},
|
|
46
|
+
async mkdir(dirPath: string): Promise<void> {
|
|
47
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
48
|
+
},
|
|
49
|
+
async readdir(dirPath: string): Promise<string[]> {
|
|
50
|
+
return fs.readdir(dirPath);
|
|
51
|
+
},
|
|
52
|
+
async deleteFile(filePath: string): Promise<void> {
|
|
53
|
+
await fs.rm(filePath, { force: true });
|
|
54
|
+
},
|
|
55
|
+
join(...paths: string[]): string {
|
|
56
|
+
return path.join(...paths);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function resolveNodeAssetsPath(document: Document, assetsPath: string | undefined): Promise<string | undefined> {
|
|
62
|
+
if (assetsPath) return assetsPath;
|
|
63
|
+
const projectDir = document.getProjectDir?.() ?? '';
|
|
64
|
+
if (!projectDir) return undefined;
|
|
65
|
+
const path = await importNative<typeof import('node:path')>('node:path');
|
|
66
|
+
return path.join(projectDir, 'assets');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadSharpBackend(): Promise<AtlasRasterBackend | undefined> {
|
|
70
|
+
try {
|
|
71
|
+
const loaded = await importNative<typeof import('sharp')>('sharp');
|
|
72
|
+
const sharp = loaded as unknown as { default?: AtlasRasterBackend };
|
|
73
|
+
return sharp.default ?? (loaded as unknown as AtlasRasterBackend);
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function loadNodePublishPlugins(document: Document, assetsPath: string | undefined): Promise<LoadedPlugin[]> {
|
|
80
|
+
const projectDir = document.getProjectDir?.() || (assetsPath ? resolveProjectBasePath(assetsPath) : '');
|
|
81
|
+
if (!projectDir) return [];
|
|
82
|
+
const path = await importNative<typeof import('node:path')>('node:path');
|
|
83
|
+
return loadPlugins(document, path.join(projectDir, 'plugins'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Publish a FairyGUI project through the standard Node host adapter.
|
|
88
|
+
*
|
|
89
|
+
* The adapter owns Node filesystem, Sharp, and project plugin discovery.
|
|
90
|
+
* For custom environments, use the lower-level `publish()` core with explicit
|
|
91
|
+
* capabilities instead.
|
|
92
|
+
*/
|
|
93
|
+
export async function publishNode(options: PublishNodeOptions): Promise<void> {
|
|
94
|
+
const {
|
|
95
|
+
document,
|
|
96
|
+
assetsPath: configuredAssetsPath,
|
|
97
|
+
atlas,
|
|
98
|
+
encoder: configuredEncoder,
|
|
99
|
+
plugins: configuredPlugins,
|
|
100
|
+
...publishOptions
|
|
101
|
+
} = options;
|
|
102
|
+
const [fileSystem, assetsPath] = await Promise.all([
|
|
103
|
+
createNodePublishFileSystem(),
|
|
104
|
+
resolveNodeAssetsPath(document, configuredAssetsPath),
|
|
105
|
+
]);
|
|
106
|
+
const [encoder, plugins] = await Promise.all([
|
|
107
|
+
configuredEncoder === undefined ? loadSharpBackend() : Promise.resolve(configuredEncoder),
|
|
108
|
+
configuredPlugins === undefined
|
|
109
|
+
? loadNodePublishPlugins(document, assetsPath)
|
|
110
|
+
: Promise.resolve(configuredPlugins),
|
|
111
|
+
]);
|
|
112
|
+
|
|
113
|
+
if (!encoder) {
|
|
114
|
+
throw new Error('publishNode: Sharp is required for a complete publish. Install sharp or provide an encoder.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await document.transform(
|
|
118
|
+
publish({
|
|
119
|
+
...publishOptions,
|
|
120
|
+
basePath: assetsPath,
|
|
121
|
+
encoder,
|
|
122
|
+
atlas: {
|
|
123
|
+
...atlas,
|
|
124
|
+
readFileRaw: fileSystem.readFileRaw,
|
|
125
|
+
},
|
|
126
|
+
fs: fileSystem,
|
|
127
|
+
plugins,
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RestoreFileSystem,
|
|
3
|
+
RestoreImageCropInput,
|
|
4
|
+
RestoreImageCropper,
|
|
5
|
+
RestoreImageExtractInput,
|
|
6
|
+
RestoreImageExtractor,
|
|
7
|
+
RestoreOptions,
|
|
8
|
+
RestoreResult,
|
|
9
|
+
} from '../../restore.js';
|
|
10
|
+
import { restore } from '../../restore.js';
|
|
11
|
+
|
|
12
|
+
const importNative = new Function('id', 'return import(id)') as <T>(id: string) => Promise<T>;
|
|
13
|
+
|
|
14
|
+
interface RestoreImageProcessors {
|
|
15
|
+
cropImage: RestoreImageCropper;
|
|
16
|
+
extractImage: RestoreImageExtractor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface RestoreNodeOptions extends Omit<RestoreOptions, 'fs' | 'cropImage' | 'extractImage'> {}
|
|
20
|
+
|
|
21
|
+
async function createNodeRestoreFileSystem(): Promise<RestoreFileSystem> {
|
|
22
|
+
const [fs, path] = await Promise.all([
|
|
23
|
+
importNative<typeof import('node:fs/promises')>('node:fs/promises'),
|
|
24
|
+
importNative<typeof import('node:path')>('node:path'),
|
|
25
|
+
]);
|
|
26
|
+
return {
|
|
27
|
+
async readFile(filePath: string): Promise<string> {
|
|
28
|
+
return fs.readFile(filePath, 'utf-8');
|
|
29
|
+
},
|
|
30
|
+
async readFileRaw(filePath: string): Promise<Uint8Array> {
|
|
31
|
+
const buffer = await fs.readFile(filePath);
|
|
32
|
+
return new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
33
|
+
},
|
|
34
|
+
async writeFile(filePath: string, content: string): Promise<void> {
|
|
35
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
36
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
37
|
+
},
|
|
38
|
+
async writeFileRaw(filePath: string, data: Uint8Array): Promise<void> {
|
|
39
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
40
|
+
await fs.writeFile(filePath, data);
|
|
41
|
+
},
|
|
42
|
+
async mkdir(dirPath: string): Promise<void> {
|
|
43
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
44
|
+
},
|
|
45
|
+
async readdir(dirPath: string): Promise<string[]> {
|
|
46
|
+
return fs.readdir(dirPath);
|
|
47
|
+
},
|
|
48
|
+
async exists(filePath: string): Promise<boolean> {
|
|
49
|
+
try {
|
|
50
|
+
await fs.access(filePath);
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
async isFile(filePath: string): Promise<boolean> {
|
|
57
|
+
try {
|
|
58
|
+
return (await fs.stat(filePath)).isFile();
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async resolvePath(filePath: string): Promise<string> {
|
|
64
|
+
try {
|
|
65
|
+
return await fs.realpath(filePath);
|
|
66
|
+
} catch {
|
|
67
|
+
return path.resolve(filePath);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async rm(targetPath: string, options?: { recursive?: boolean; force?: boolean }): Promise<void> {
|
|
71
|
+
await fs.rm(targetPath, {
|
|
72
|
+
recursive: options?.recursive ?? false,
|
|
73
|
+
force: options?.force ?? false,
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
async rename(from: string, to: string): Promise<void> {
|
|
77
|
+
await fs.rename(from, to);
|
|
78
|
+
},
|
|
79
|
+
join(...paths: string[]): string {
|
|
80
|
+
return path.join(...paths);
|
|
81
|
+
},
|
|
82
|
+
dirname(filePath: string): string {
|
|
83
|
+
return path.dirname(filePath);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function createRestoreImageProcessors(): Promise<RestoreImageProcessors> {
|
|
89
|
+
let sharp: any;
|
|
90
|
+
try {
|
|
91
|
+
const loaded = await importNative<any>('sharp');
|
|
92
|
+
sharp = loaded.default ?? loaded;
|
|
93
|
+
} catch {
|
|
94
|
+
throw new Error('restoreNode: Sharp is required to crop atlas images. Install sharp before restoring.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function extractImage(input: RestoreImageExtractInput): Promise<Uint8Array> {
|
|
98
|
+
const targetPath = (input as RestoreImageCropInput).outputPath ?? input.sourcePath;
|
|
99
|
+
let image = sharp(input.sourcePath).extract({
|
|
100
|
+
left: input.left,
|
|
101
|
+
top: input.top,
|
|
102
|
+
width: input.width,
|
|
103
|
+
height: input.height,
|
|
104
|
+
});
|
|
105
|
+
if (input.rotated) image = image.rotate(90);
|
|
106
|
+
const { data, info } = await image.png().toBuffer({ resolveWithObject: true });
|
|
107
|
+
const needsOriginalCanvas =
|
|
108
|
+
input.expectedWidth > 0 &&
|
|
109
|
+
input.expectedHeight > 0 &&
|
|
110
|
+
(input.offsetX !== 0 ||
|
|
111
|
+
input.offsetY !== 0 ||
|
|
112
|
+
info.width !== input.expectedWidth ||
|
|
113
|
+
info.height !== input.expectedHeight);
|
|
114
|
+
|
|
115
|
+
if (needsOriginalCanvas) {
|
|
116
|
+
if (
|
|
117
|
+
input.offsetX < 0 ||
|
|
118
|
+
input.offsetY < 0 ||
|
|
119
|
+
input.offsetX + info.width > input.expectedWidth ||
|
|
120
|
+
input.offsetY + info.height > input.expectedHeight
|
|
121
|
+
) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`restore: Cropped image does not fit original canvas for ${targetPath}: ` +
|
|
124
|
+
`crop ${info.width}x${info.height} at ${input.offsetX},${input.offsetY}, ` +
|
|
125
|
+
`canvas ${input.expectedWidth}x${input.expectedHeight}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const composed = await sharp({
|
|
129
|
+
create: {
|
|
130
|
+
width: input.expectedWidth,
|
|
131
|
+
height: input.expectedHeight,
|
|
132
|
+
channels: 4,
|
|
133
|
+
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
.composite([{ input: data, left: input.offsetX, top: input.offsetY }])
|
|
137
|
+
.png()
|
|
138
|
+
.toBuffer({ resolveWithObject: true });
|
|
139
|
+
if (
|
|
140
|
+
composed.info.width !== input.expectedWidth ||
|
|
141
|
+
composed.info.height !== input.expectedHeight
|
|
142
|
+
) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`restore: Cropped image size mismatch for ${targetPath}: ` +
|
|
145
|
+
`expected ${input.expectedWidth}x${input.expectedHeight}, ` +
|
|
146
|
+
`got ${composed.info.width}x${composed.info.height}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return composed.data;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
input.expectedWidth > 0 &&
|
|
154
|
+
input.expectedHeight > 0 &&
|
|
155
|
+
(info.width !== input.expectedWidth || info.height !== input.expectedHeight)
|
|
156
|
+
) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`restore: Cropped image size mismatch for ${targetPath}: ` +
|
|
159
|
+
`expected ${input.expectedWidth}x${input.expectedHeight}, got ${info.width}x${info.height}`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return data;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const fs = await importNative<typeof import('node:fs/promises')>('node:fs/promises');
|
|
166
|
+
const path = await importNative<typeof import('node:path')>('node:path');
|
|
167
|
+
return {
|
|
168
|
+
extractImage,
|
|
169
|
+
cropImage: async (input: RestoreImageCropInput): Promise<void> => {
|
|
170
|
+
await fs.mkdir(path.dirname(input.outputPath), { recursive: true });
|
|
171
|
+
await fs.writeFile(input.outputPath, await extractImage(input));
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Restore trusted local published artifacts through the standard Node host adapter. */
|
|
177
|
+
export async function restoreNode(options: RestoreNodeOptions): Promise<RestoreResult> {
|
|
178
|
+
const [fs, imageProcessors] = await Promise.all([
|
|
179
|
+
createNodeRestoreFileSystem(),
|
|
180
|
+
createRestoreImageProcessors(),
|
|
181
|
+
]);
|
|
182
|
+
return restore({
|
|
183
|
+
...options,
|
|
184
|
+
fs,
|
|
185
|
+
...imageProcessors,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type Document, type ILogger, ProjectType } from '@openfairygui/core';
|
|
2
|
+
import type { AtlasOptions } from '../../atlas.js';
|
|
3
|
+
import { resolveCodeGenerationSettings } from '../../codegen.js';
|
|
4
|
+
import { publish } from '../../publish.js';
|
|
5
|
+
import type {
|
|
6
|
+
PublishFileSystem,
|
|
7
|
+
PublishOutputFileSystem,
|
|
8
|
+
PublishSourceFileSystem,
|
|
9
|
+
} from '../../publish/contracts.js';
|
|
10
|
+
import { resolvePublishOptions } from '../../publish/options.js';
|
|
11
|
+
import { assertBrowserImageSupport, createBrowserImageEncoder } from './raster.js';
|
|
12
|
+
|
|
13
|
+
export type BrowserPublishProjectType = 'layabox';
|
|
14
|
+
|
|
15
|
+
export type BrowserPublishAtlasOptions = Pick<
|
|
16
|
+
AtlasOptions,
|
|
17
|
+
| 'maxSize'
|
|
18
|
+
| 'fast'
|
|
19
|
+
| 'allowRotation'
|
|
20
|
+
| 'padding'
|
|
21
|
+
| 'powerOfTwo'
|
|
22
|
+
| 'square'
|
|
23
|
+
| 'multiPage'
|
|
24
|
+
| 'trimImage'
|
|
25
|
+
| 'extractAlpha'
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
export type BrowserPublishSourceFileSystem = PublishSourceFileSystem;
|
|
29
|
+
|
|
30
|
+
export type BrowserPublishOutputFileSystem = PublishOutputFileSystem;
|
|
31
|
+
|
|
32
|
+
export interface BrowserPublishOptions {
|
|
33
|
+
document: Document;
|
|
34
|
+
sourceFileSystem: BrowserPublishSourceFileSystem;
|
|
35
|
+
outputFileSystem: BrowserPublishOutputFileSystem;
|
|
36
|
+
projectType: BrowserPublishProjectType;
|
|
37
|
+
output: string;
|
|
38
|
+
compressed?: boolean;
|
|
39
|
+
packages?: string[];
|
|
40
|
+
branch?: string;
|
|
41
|
+
atlas?: BrowserPublishAtlasOptions;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface BrowserPublishDiagnostic {
|
|
45
|
+
level: 'debug' | 'info' | 'warning' | 'error';
|
|
46
|
+
message: string;
|
|
47
|
+
code?: 'unsupported_publish_setting' | 'publish_failed';
|
|
48
|
+
setting?: string;
|
|
49
|
+
path?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface BrowserPublishedFile {
|
|
53
|
+
path: string;
|
|
54
|
+
size: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BrowserPublishResult {
|
|
58
|
+
success: boolean;
|
|
59
|
+
files: BrowserPublishedFile[];
|
|
60
|
+
diagnostics: BrowserPublishDiagnostic[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createTrackingFileSystem(
|
|
64
|
+
fileSystem: BrowserPublishOutputFileSystem,
|
|
65
|
+
files: Map<string, number>,
|
|
66
|
+
): PublishFileSystem {
|
|
67
|
+
const tracked: PublishFileSystem = {
|
|
68
|
+
join: (...paths) => fileSystem.join(...paths),
|
|
69
|
+
mkdir: (path) => fileSystem.mkdir(path),
|
|
70
|
+
writeFileRaw: async (path, data) => {
|
|
71
|
+
await fileSystem.writeFileRaw(path, data);
|
|
72
|
+
files.set(path, data.byteLength);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
return tracked;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createDiagnosticLogger(logger: ILogger, diagnostics: BrowserPublishDiagnostic[]): ILogger {
|
|
79
|
+
return {
|
|
80
|
+
debug(message) {
|
|
81
|
+
diagnostics.push({ level: 'debug', message });
|
|
82
|
+
logger.debug(message);
|
|
83
|
+
},
|
|
84
|
+
info(message) {
|
|
85
|
+
diagnostics.push({ level: 'info', message });
|
|
86
|
+
logger.info(message);
|
|
87
|
+
},
|
|
88
|
+
warn(message) {
|
|
89
|
+
diagnostics.push({ level: 'warning', message });
|
|
90
|
+
logger.warn(message);
|
|
91
|
+
},
|
|
92
|
+
error(message) {
|
|
93
|
+
diagnostics.push({ level: 'error', message });
|
|
94
|
+
logger.error(message);
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function toResult(
|
|
100
|
+
success: boolean,
|
|
101
|
+
files: Map<string, number>,
|
|
102
|
+
diagnostics: BrowserPublishDiagnostic[],
|
|
103
|
+
): BrowserPublishResult {
|
|
104
|
+
return {
|
|
105
|
+
success,
|
|
106
|
+
files: [...files].map(([path, size]) => ({ path, size })),
|
|
107
|
+
diagnostics,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function unsupportedSetting(setting: string, path: string, message: string): BrowserPublishDiagnostic {
|
|
112
|
+
return { level: 'error', code: 'unsupported_publish_setting', setting, path, message };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Publish a loaded FairyGUI project to browser-provided storage.
|
|
117
|
+
*
|
|
118
|
+
* The adapter uses browser Canvas APIs for atlas composition, writes only through
|
|
119
|
+
* the supplied output filesystem, and intentionally skips Node publish plugins.
|
|
120
|
+
*/
|
|
121
|
+
export async function publishBrowser(options: BrowserPublishOptions): Promise<BrowserPublishResult> {
|
|
122
|
+
const files = new Map<string, number>();
|
|
123
|
+
const diagnostics: BrowserPublishDiagnostic[] = [];
|
|
124
|
+
const root = options.document.getRoot();
|
|
125
|
+
const previousProjectType = root.getProjectType();
|
|
126
|
+
const previousLogger = options.document.getLogger();
|
|
127
|
+
options.document.setLogger(createDiagnosticLogger(previousLogger, diagnostics));
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
if (options.projectType !== 'layabox') {
|
|
131
|
+
throw new Error(`publishBrowser: unsupported project type "${String(options.projectType)}".`);
|
|
132
|
+
}
|
|
133
|
+
root.setProjectType(ProjectType.LayaBox);
|
|
134
|
+
const resolved = resolvePublishOptions(options.document, {
|
|
135
|
+
compressed: options.compressed,
|
|
136
|
+
packages: options.packages,
|
|
137
|
+
atlas: options.atlas,
|
|
138
|
+
});
|
|
139
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(resolved.fileExtension)) {
|
|
140
|
+
diagnostics.push(unsupportedSetting(
|
|
141
|
+
'fileExtension',
|
|
142
|
+
'settings.publish.fileExtension',
|
|
143
|
+
`publishBrowser: unsupported fileExtension "${resolved.fileExtension}".`,
|
|
144
|
+
));
|
|
145
|
+
return toResult(false, files, diagnostics);
|
|
146
|
+
}
|
|
147
|
+
const selectedPackageNames = options.packages?.length ? new Set(options.packages) : null;
|
|
148
|
+
const selectedPackages = root.listPackages().filter((pkg) => !selectedPackageNames || selectedPackageNames.has(pkg.getName()));
|
|
149
|
+
if (resolveCodeGenerationSettings(options.document).allowGenCode) {
|
|
150
|
+
const packageIndex = selectedPackages.findIndex((pkg) => pkg.getGenCode());
|
|
151
|
+
if (packageIndex >= 0) {
|
|
152
|
+
const pkg = selectedPackages[packageIndex]!;
|
|
153
|
+
diagnostics.push(unsupportedSetting(
|
|
154
|
+
'codeGeneration',
|
|
155
|
+
`packages[${root.listPackages().indexOf(pkg)}].publish.genCode`,
|
|
156
|
+
`publishBrowser: code generation requested by package "${pkg.getName()}" is not supported.`,
|
|
157
|
+
));
|
|
158
|
+
return toResult(false, files, diagnostics);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
assertBrowserImageSupport();
|
|
162
|
+
const outputFileSystem = createTrackingFileSystem(options.outputFileSystem, files);
|
|
163
|
+
const sourceAssetsPath = options.sourceFileSystem.join(options.document.getProjectDir(), 'assets');
|
|
164
|
+
|
|
165
|
+
await options.document.transform(
|
|
166
|
+
publish({
|
|
167
|
+
output: options.output,
|
|
168
|
+
compressed: resolved.compressed,
|
|
169
|
+
fileExtension: resolved.fileExtension,
|
|
170
|
+
packages: options.packages,
|
|
171
|
+
branch: options.branch,
|
|
172
|
+
basePath: sourceAssetsPath,
|
|
173
|
+
encoder: createBrowserImageEncoder(options.sourceFileSystem, outputFileSystem),
|
|
174
|
+
atlas: {
|
|
175
|
+
...options.atlas,
|
|
176
|
+
readFileRaw: (path) => options.sourceFileSystem.readFileRaw(path),
|
|
177
|
+
},
|
|
178
|
+
fs: outputFileSystem,
|
|
179
|
+
plugins: [],
|
|
180
|
+
codeGeneration: false,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return toResult(true, files, diagnostics);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
diagnostics.push({
|
|
187
|
+
level: 'error',
|
|
188
|
+
code: 'publish_failed',
|
|
189
|
+
message: error instanceof Error ? error.message : String(error),
|
|
190
|
+
});
|
|
191
|
+
return toResult(false, files, diagnostics);
|
|
192
|
+
} finally {
|
|
193
|
+
root.setProjectType(previousProjectType);
|
|
194
|
+
options.document.setLogger(previousLogger);
|
|
195
|
+
}
|
|
196
|
+
}
|