@openfairygui/functions 0.1.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/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/index.cjs +3110 -0
- package/dist/index.d.cts +422 -0
- package/dist/index.d.ts +422 -0
- package/dist/index.js +3099 -0
- package/package.json +54 -0
- package/src/atlas.ts +1651 -0
- package/src/codegen-templates.ts +67 -0
- package/src/codegen.ts +656 -0
- package/src/index.ts +26 -0
- package/src/inspect.ts +146 -0
- package/src/max-rects-compat.ts +431 -0
- package/src/max-rects-packer-compat.ts +412 -0
- package/src/prune.ts +86 -0
- package/src/publish.ts +1093 -0
- package/src/rename.ts +66 -0
- package/src/shared-types.ts +65 -0
- package/src/utils.ts +9 -0
- package/src/validate.ts +186 -0
package/src/codegen.ts
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
type GComponent,
|
|
4
|
+
type Document,
|
|
5
|
+
type GObject,
|
|
6
|
+
type Package,
|
|
7
|
+
ProjectType,
|
|
8
|
+
} from '@openfairygui/core';
|
|
9
|
+
import {
|
|
10
|
+
FGUI_TYPESCRIPT_BINDER_TEMPLATE,
|
|
11
|
+
FGUI_TYPESCRIPT_COMPONENT_TEMPLATE,
|
|
12
|
+
UNITY_BINDER_TEMPLATE,
|
|
13
|
+
UNITY_COMPONENT_TEMPLATE,
|
|
14
|
+
} from './codegen-templates.js';
|
|
15
|
+
import type { CliCodeGenerationSettings, PublishFileSystem, RootProjectSettings } from './shared-types.js';
|
|
16
|
+
|
|
17
|
+
export const AUTO_GENERATED_CODE_MARK = '/** This is an automatically generated class by FairyGUI. Please do not modify it. **/';
|
|
18
|
+
const DEFAULT_CLASS_NAME_PREFIX = 'UI_';
|
|
19
|
+
const DEFAULT_MEMBER_NAME_PREFIX = 'm_';
|
|
20
|
+
|
|
21
|
+
export interface PublishCodeGenerationOptions {
|
|
22
|
+
basePath?: string;
|
|
23
|
+
fs: PublishFileSystem;
|
|
24
|
+
packages: Package[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ResolvedCodeGenerationSettings {
|
|
28
|
+
allowGenCode: boolean;
|
|
29
|
+
classNamePrefix: string;
|
|
30
|
+
memberNamePrefix: string;
|
|
31
|
+
packageName: string;
|
|
32
|
+
ignoreNoname: boolean;
|
|
33
|
+
getMemberByName: boolean;
|
|
34
|
+
codePath: string;
|
|
35
|
+
codeType: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ResolvedPackageCodegenPlan {
|
|
39
|
+
outputDir: string;
|
|
40
|
+
packageFolderName: string;
|
|
41
|
+
packageNamespace: string;
|
|
42
|
+
binderClassName: string;
|
|
43
|
+
settings: ResolvedCodeGenerationSettings;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface FguiTypescriptVariant {
|
|
47
|
+
binderMethod: 'setExtension';
|
|
48
|
+
runtimeNamespace: 'fgui';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const FGUI_TYPESCRIPT_RUNTIME_TYPES = new Set([
|
|
52
|
+
'Controller',
|
|
53
|
+
'GButton',
|
|
54
|
+
'GComboBox',
|
|
55
|
+
'GComponent',
|
|
56
|
+
'GGraph',
|
|
57
|
+
'GGroup',
|
|
58
|
+
'GImage',
|
|
59
|
+
'GLabel',
|
|
60
|
+
'GList',
|
|
61
|
+
'GLoader',
|
|
62
|
+
'GLoader3D',
|
|
63
|
+
'GMovieClip',
|
|
64
|
+
'GProgressBar',
|
|
65
|
+
'GRichTextField',
|
|
66
|
+
'GScrollBar',
|
|
67
|
+
'GSlider',
|
|
68
|
+
'GSwfObject',
|
|
69
|
+
'GTextField',
|
|
70
|
+
'GTextInput',
|
|
71
|
+
'GTree',
|
|
72
|
+
'Transition',
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
const SHARED_FGUI_TYPESCRIPT_VARIANT: FguiTypescriptVariant = {
|
|
76
|
+
binderMethod: 'setExtension',
|
|
77
|
+
runtimeNamespace: 'fgui',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
interface CodegenMember {
|
|
81
|
+
index: number;
|
|
82
|
+
kind: 'child' | 'controller' | 'transition';
|
|
83
|
+
name: string;
|
|
84
|
+
originalName: string;
|
|
85
|
+
type: string;
|
|
86
|
+
ignored: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface CodegenClass {
|
|
90
|
+
classId: string;
|
|
91
|
+
className: string;
|
|
92
|
+
encodedClassName: string;
|
|
93
|
+
componentType: string;
|
|
94
|
+
componentName: string;
|
|
95
|
+
packageName: string;
|
|
96
|
+
url: string;
|
|
97
|
+
members: CodegenMember[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function publishCodeGeneration(
|
|
101
|
+
doc: Document,
|
|
102
|
+
options: PublishCodeGenerationOptions,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const logger = doc.getLogger();
|
|
105
|
+
const settings = resolveCodeGenerationSettings(doc);
|
|
106
|
+
if (!settings.allowGenCode) return;
|
|
107
|
+
|
|
108
|
+
for (const pkg of options.packages) {
|
|
109
|
+
if (!pkg.getGenCode()) continue;
|
|
110
|
+
|
|
111
|
+
const plan = resolvePackageCodegenPlan(pkg, settings, options);
|
|
112
|
+
if (!plan) {
|
|
113
|
+
logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because no codePath was resolved.`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!supportsCodeGenerationLane(doc, settings.codeType)) {
|
|
118
|
+
logger.warn(`publish: Code generation skipped for package "${pkg.getName()}" because project/codeType is not supported yet.`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fguiTypescriptVariant = resolveFguiTypescriptVariant(doc);
|
|
123
|
+
if (fguiTypescriptVariant) {
|
|
124
|
+
await generateFguiTypescriptCode(doc, pkg, plan, options.fs, fguiTypescriptVariant);
|
|
125
|
+
} else {
|
|
126
|
+
await generateUnityCode(doc, pkg, plan, options.fs);
|
|
127
|
+
}
|
|
128
|
+
logger.info(`publish: Generated code for package "${pkg.getName()}" into ${plan.outputDir}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function resolveCodeGenerationSettings(doc: Document): ResolvedCodeGenerationSettings {
|
|
133
|
+
const settings = (doc.getRoot().getSettings?.() ?? {}) as RootProjectSettings;
|
|
134
|
+
const publish = settings.publish ?? {};
|
|
135
|
+
const codeGeneration = publish.codeGeneration as CliCodeGenerationSettings | undefined;
|
|
136
|
+
|
|
137
|
+
if (!codeGeneration) {
|
|
138
|
+
return {
|
|
139
|
+
allowGenCode: true,
|
|
140
|
+
classNamePrefix: 'UI_',
|
|
141
|
+
memberNamePrefix: 'm_',
|
|
142
|
+
packageName: '',
|
|
143
|
+
ignoreNoname: false,
|
|
144
|
+
getMemberByName: false,
|
|
145
|
+
codePath: '',
|
|
146
|
+
codeType: '',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
allowGenCode: codeGeneration.allowGenCode ?? true,
|
|
152
|
+
classNamePrefix: codeGeneration.classNamePrefix ?? DEFAULT_CLASS_NAME_PREFIX,
|
|
153
|
+
memberNamePrefix: codeGeneration.memberNamePrefix ?? DEFAULT_MEMBER_NAME_PREFIX,
|
|
154
|
+
packageName: codeGeneration.packageName ?? '',
|
|
155
|
+
ignoreNoname: codeGeneration.ignoreNoname ?? false,
|
|
156
|
+
getMemberByName: Boolean(codeGeneration.getMemberByName),
|
|
157
|
+
codePath: codeGeneration.codePath ?? '',
|
|
158
|
+
codeType: codeGeneration.codeType?.trim() ?? '',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolvePackageCodegenPlan(
|
|
163
|
+
pkg: Package,
|
|
164
|
+
settings: ResolvedCodeGenerationSettings,
|
|
165
|
+
options: PublishCodeGenerationOptions,
|
|
166
|
+
): ResolvedPackageCodegenPlan | null {
|
|
167
|
+
const rawCodePath = (pkg.getCodePath() || settings.codePath || '').trim();
|
|
168
|
+
if (!rawCodePath) return null;
|
|
169
|
+
|
|
170
|
+
const packageFolderName = normalizeTypeName(pkg.getName()) || 'Package';
|
|
171
|
+
const outputDir = resolveCodePath(rawCodePath, options.basePath, options.fs);
|
|
172
|
+
const packageNamespace = settings.packageName
|
|
173
|
+
? `${settings.packageName}.${packageFolderName}`
|
|
174
|
+
: packageFolderName;
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
outputDir,
|
|
178
|
+
packageFolderName,
|
|
179
|
+
packageNamespace,
|
|
180
|
+
binderClassName: `${packageFolderName}Binder`,
|
|
181
|
+
settings,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function supportsCodeGenerationLane(doc: Document, codeType: string): boolean {
|
|
186
|
+
const projectType = doc.getRoot().getProjectType();
|
|
187
|
+
if (projectType === ProjectType.Unity) return codeType === '';
|
|
188
|
+
if (projectType === ProjectType.LayaBox || projectType === ProjectType.CocosCreator) return true;
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Layabox and Cocos Creator currently share the same modern fgui TypeScript output contract.
|
|
193
|
+
function resolveFguiTypescriptVariant(doc: Document): FguiTypescriptVariant | null {
|
|
194
|
+
const projectType = doc.getRoot().getProjectType();
|
|
195
|
+
if (projectType !== ProjectType.LayaBox && projectType !== ProjectType.CocosCreator) return null;
|
|
196
|
+
return SHARED_FGUI_TYPESCRIPT_VARIANT;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function generateUnityCode(
|
|
200
|
+
doc: Document,
|
|
201
|
+
pkg: Package,
|
|
202
|
+
plan: ResolvedPackageCodegenPlan,
|
|
203
|
+
fs: PublishFileSystem,
|
|
204
|
+
): Promise<void> {
|
|
205
|
+
const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
|
|
206
|
+
await fs.mkdir(plan.outputDir);
|
|
207
|
+
await fs.mkdir(packageDir);
|
|
208
|
+
await cleanupGeneratedFiles(packageDir, fs);
|
|
209
|
+
|
|
210
|
+
const classes = buildCodegenClasses(doc, pkg, plan);
|
|
211
|
+
for (const classInfo of classes) {
|
|
212
|
+
await writeTextFile(
|
|
213
|
+
fs,
|
|
214
|
+
fs.join(packageDir, `${classInfo.encodedClassName}.cs`),
|
|
215
|
+
renderUnityComponentClass(classInfo, plan),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await writeTextFile(
|
|
220
|
+
fs,
|
|
221
|
+
fs.join(packageDir, `${plan.binderClassName}.cs`),
|
|
222
|
+
renderUnityBinder(classes, plan),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function generateFguiTypescriptCode(
|
|
227
|
+
doc: Document,
|
|
228
|
+
pkg: Package,
|
|
229
|
+
plan: ResolvedPackageCodegenPlan,
|
|
230
|
+
fs: PublishFileSystem,
|
|
231
|
+
variant: FguiTypescriptVariant,
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
const packageDir = fs.join(plan.outputDir, plan.packageFolderName);
|
|
234
|
+
await fs.mkdir(plan.outputDir);
|
|
235
|
+
await fs.mkdir(packageDir);
|
|
236
|
+
await cleanupGeneratedFiles(packageDir, fs, '.ts');
|
|
237
|
+
|
|
238
|
+
const classes = buildCodegenClasses(doc, pkg, plan);
|
|
239
|
+
for (const classInfo of classes) {
|
|
240
|
+
await writeTextFile(
|
|
241
|
+
fs,
|
|
242
|
+
fs.join(packageDir, `${classInfo.encodedClassName}.ts`),
|
|
243
|
+
renderFguiTypescriptComponentClass(classInfo, plan, variant),
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
await writeTextFile(
|
|
248
|
+
fs,
|
|
249
|
+
fs.join(packageDir, `${plan.binderClassName}.ts`),
|
|
250
|
+
renderFguiTypescriptBinder(classes, plan, variant),
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function cleanupGeneratedFiles(directory: string, fs: PublishFileSystem, extension = '.cs'): Promise<void> {
|
|
255
|
+
if (!fs.readdir || !fs.readFileRaw || !fs.deleteFile) return;
|
|
256
|
+
|
|
257
|
+
let entries: string[];
|
|
258
|
+
try {
|
|
259
|
+
entries = await fs.readdir(directory);
|
|
260
|
+
} catch {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const entry of entries) {
|
|
265
|
+
if (!entry.toLowerCase().endsWith(extension)) continue;
|
|
266
|
+
const filePath = fs.join(directory, entry);
|
|
267
|
+
try {
|
|
268
|
+
const bytes = await fs.readFileRaw(filePath);
|
|
269
|
+
const content = decodeText(bytes);
|
|
270
|
+
if (content.startsWith(AUTO_GENERATED_CODE_MARK)) {
|
|
271
|
+
await fs.deleteFile(filePath);
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
// Skip unreadable entries and nested paths; cleanup is best-effort and package-scoped.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function buildCodegenClasses(
|
|
280
|
+
doc: Document,
|
|
281
|
+
pkg: Package,
|
|
282
|
+
plan: ResolvedPackageCodegenPlan,
|
|
283
|
+
): CodegenClass[] {
|
|
284
|
+
const exportedComponents = pkg.listComponents()
|
|
285
|
+
.filter((component) => component.getExported())
|
|
286
|
+
.sort((left, right) => left.getId().localeCompare(right.getId()));
|
|
287
|
+
const generatedById = new Map<string, CodegenClass>();
|
|
288
|
+
|
|
289
|
+
for (const component of exportedComponents) {
|
|
290
|
+
const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || 'Component'}`;
|
|
291
|
+
generatedById.set(component.getId(), {
|
|
292
|
+
classId: component.getId(),
|
|
293
|
+
className: component.getName(),
|
|
294
|
+
encodedClassName,
|
|
295
|
+
componentType: resolveComponentBaseType(component),
|
|
296
|
+
componentName: component.getName(),
|
|
297
|
+
packageName: pkg.getName(),
|
|
298
|
+
url: `ui://${pkg.getId()}${component.getId()}`,
|
|
299
|
+
members: [],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
for (const component of exportedComponents) {
|
|
304
|
+
const classInfo = generatedById.get(component.getId());
|
|
305
|
+
if (!classInfo) continue;
|
|
306
|
+
classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return [...generatedById.values()];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function buildCodegenMembers(
|
|
313
|
+
doc: Document,
|
|
314
|
+
pkg: Package,
|
|
315
|
+
component: Component,
|
|
316
|
+
plan: ResolvedPackageCodegenPlan,
|
|
317
|
+
generatedById: Map<string, CodegenClass>,
|
|
318
|
+
): CodegenMember[] {
|
|
319
|
+
const members: CodegenMember[] = [];
|
|
320
|
+
const ownerType = resolveComponentBaseType(component);
|
|
321
|
+
let controllerIndex = 0;
|
|
322
|
+
let childIndex = 0;
|
|
323
|
+
let transitionIndex = 0;
|
|
324
|
+
|
|
325
|
+
for (const controller of component.listControllers()) {
|
|
326
|
+
members.push(createMember(ownerType, 'controller', 'Controller', controller.getName(), controllerIndex++, plan));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
for (const child of component.listChildren()) {
|
|
330
|
+
members.push(createMember(
|
|
331
|
+
ownerType,
|
|
332
|
+
'child',
|
|
333
|
+
resolveChildType(doc, pkg, child, generatedById),
|
|
334
|
+
child.getName(),
|
|
335
|
+
childIndex++,
|
|
336
|
+
plan,
|
|
337
|
+
));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
for (const transition of component.listTransitions()) {
|
|
341
|
+
members.push(createMember(ownerType, 'transition', 'Transition', transition.getName(), transitionIndex++, plan));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const usedNames = new Map<string, number>();
|
|
345
|
+
for (const member of members) {
|
|
346
|
+
if (member.ignored) continue;
|
|
347
|
+
const key = applyMemberNamePrefix(member.originalName, plan.settings.memberNamePrefix);
|
|
348
|
+
const current = usedNames.get(key) ?? 0;
|
|
349
|
+
if (current > 0) {
|
|
350
|
+
member.name = `${key}_${current + 1}`;
|
|
351
|
+
}
|
|
352
|
+
usedNames.set(key, current + 1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return members;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function createMember(
|
|
359
|
+
ownerType: string,
|
|
360
|
+
kind: CodegenMember['kind'],
|
|
361
|
+
type: string,
|
|
362
|
+
originalName: string,
|
|
363
|
+
index: number,
|
|
364
|
+
plan: ResolvedPackageCodegenPlan,
|
|
365
|
+
): CodegenMember {
|
|
366
|
+
const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
|
|
367
|
+
return {
|
|
368
|
+
index,
|
|
369
|
+
kind,
|
|
370
|
+
name: applyMemberNamePrefix(originalName, plan.settings.memberNamePrefix),
|
|
371
|
+
originalName,
|
|
372
|
+
type,
|
|
373
|
+
ignored,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function resolveChildType(
|
|
378
|
+
doc: Document,
|
|
379
|
+
pkg: Package,
|
|
380
|
+
child: GObject,
|
|
381
|
+
generatedById: Map<string, CodegenClass>,
|
|
382
|
+
): string {
|
|
383
|
+
const src = (child as GObject & { getSrc?(): string }).getSrc?.();
|
|
384
|
+
if (src) {
|
|
385
|
+
const localResource = resolveChildSourceComponent(doc, pkg, src);
|
|
386
|
+
if (localResource) {
|
|
387
|
+
return generatedById.get(localResource.getId())?.encodedClassName
|
|
388
|
+
?? resolveComponentBaseType(localResource);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const instanceExtType = (child as GComponent & { getInstanceExtType?(): string }).getInstanceExtType?.();
|
|
393
|
+
if (instanceExtType) return `G${instanceExtType}`;
|
|
394
|
+
|
|
395
|
+
return child.propertyType;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function resolveChildSourceComponent(doc: Document, pkg: Package, src: string): Component | null {
|
|
399
|
+
if (!src) return null;
|
|
400
|
+
if (src.startsWith('ui://')) {
|
|
401
|
+
const rest = src.slice(5);
|
|
402
|
+
const pkgId = rest.slice(0, 8);
|
|
403
|
+
const resourceId = rest.slice(8);
|
|
404
|
+
const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
|
|
405
|
+
const targetResource = targetPackage?.getResourceById(resourceId);
|
|
406
|
+
return targetResource?.propertyType === 'Component' ? targetResource : null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const localResource = pkg.getResourceById(src);
|
|
410
|
+
return localResource?.propertyType === 'Component' ? localResource : null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function resolveComponentBaseType(component: Component): string {
|
|
414
|
+
const extensionType = component.getExtensionType();
|
|
415
|
+
return extensionType ? `G${extensionType}` : 'GComponent';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function renderUnityComponentClass(classInfo: CodegenClass, plan: ResolvedPackageCodegenPlan): string {
|
|
419
|
+
const variableLines = classInfo.members
|
|
420
|
+
.filter((member) => !member.ignored)
|
|
421
|
+
.map((member) => `\t\tpublic ${member.type} ${member.name};`)
|
|
422
|
+
.join('\n');
|
|
423
|
+
const contentLines = classInfo.members
|
|
424
|
+
.map((member) => renderMemberAssignment(member, plan.settings.getMemberByName))
|
|
425
|
+
.filter((line): line is string => Boolean(line))
|
|
426
|
+
.join('\n');
|
|
427
|
+
|
|
428
|
+
return renderTemplate(UNITY_COMPONENT_TEMPLATE, {
|
|
429
|
+
assignmentLines: contentLines ? `${contentLines}\n` : '',
|
|
430
|
+
className: classInfo.encodedClassName,
|
|
431
|
+
componentName: escapeCSharpString(classInfo.className),
|
|
432
|
+
componentType: classInfo.componentType,
|
|
433
|
+
generatedMark: AUTO_GENERATED_CODE_MARK,
|
|
434
|
+
namespaceName: plan.packageNamespace,
|
|
435
|
+
packageName: escapeCSharpString(classInfo.packageName),
|
|
436
|
+
url: escapeCSharpString(classInfo.url),
|
|
437
|
+
variableLines: variableLines ? `${variableLines}\n` : '',
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function renderUnityBinder(classes: CodegenClass[], plan: ResolvedPackageCodegenPlan): string {
|
|
442
|
+
const bindLines = classes
|
|
443
|
+
.map((classInfo) => `\t\t\tUIObjectFactory.SetPackageItemExtension(${classInfo.encodedClassName}.URL, typeof(${classInfo.encodedClassName}));`)
|
|
444
|
+
.join('\n');
|
|
445
|
+
|
|
446
|
+
return renderTemplate(UNITY_BINDER_TEMPLATE, {
|
|
447
|
+
binderClassName: plan.binderClassName,
|
|
448
|
+
bindLines: bindLines ? `${bindLines}\n` : '',
|
|
449
|
+
generatedMark: AUTO_GENERATED_CODE_MARK,
|
|
450
|
+
namespaceName: plan.packageNamespace,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function renderFguiTypescriptComponentClass(
|
|
455
|
+
classInfo: CodegenClass,
|
|
456
|
+
plan: ResolvedPackageCodegenPlan,
|
|
457
|
+
variant: FguiTypescriptVariant,
|
|
458
|
+
): string {
|
|
459
|
+
const variableLines = classInfo.members
|
|
460
|
+
.filter((member) => !member.ignored)
|
|
461
|
+
.map((member) => `\tpublic ${member.name}:${translateFguiTypescriptType(member.type, variant)};`)
|
|
462
|
+
.join('\n');
|
|
463
|
+
const assignmentLines = classInfo.members
|
|
464
|
+
.map((member) => renderFguiTypescriptMemberAssignment(member, plan.settings.getMemberByName, variant))
|
|
465
|
+
.filter((line): line is string => Boolean(line))
|
|
466
|
+
.join('\n');
|
|
467
|
+
const importLines = collectFguiTypescriptImports(classInfo, variant);
|
|
468
|
+
|
|
469
|
+
return renderTemplate(FGUI_TYPESCRIPT_COMPONENT_TEMPLATE, {
|
|
470
|
+
assignmentLines: assignmentLines ? `${assignmentLines}\n` : '',
|
|
471
|
+
className: classInfo.encodedClassName,
|
|
472
|
+
componentName: escapeTypeScriptString(classInfo.className),
|
|
473
|
+
componentType: translateFguiTypescriptType(classInfo.componentType, variant),
|
|
474
|
+
generatedMark: AUTO_GENERATED_CODE_MARK,
|
|
475
|
+
importLines,
|
|
476
|
+
packageName: escapeTypeScriptString(classInfo.packageName),
|
|
477
|
+
runtimeNamespace: variant.runtimeNamespace,
|
|
478
|
+
url: escapeTypeScriptString(classInfo.url),
|
|
479
|
+
variableLines: variableLines ? `${variableLines}\n` : '',
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function renderFguiTypescriptBinder(
|
|
484
|
+
classes: CodegenClass[],
|
|
485
|
+
plan: ResolvedPackageCodegenPlan,
|
|
486
|
+
variant: FguiTypescriptVariant,
|
|
487
|
+
): string {
|
|
488
|
+
const bindLines = classes
|
|
489
|
+
.map((classInfo) => `\t\t${variant.runtimeNamespace}.UIObjectFactory.${variant.binderMethod}(${classInfo.encodedClassName}.URL, ${classInfo.encodedClassName});`)
|
|
490
|
+
.join('\n');
|
|
491
|
+
const importLines = classes
|
|
492
|
+
.map((classInfo) => `import ${classInfo.encodedClassName} from "./${classInfo.encodedClassName}";`)
|
|
493
|
+
.join('\n');
|
|
494
|
+
|
|
495
|
+
return renderTemplate(FGUI_TYPESCRIPT_BINDER_TEMPLATE, {
|
|
496
|
+
binderClassName: plan.binderClassName,
|
|
497
|
+
bindLines: bindLines ? `${bindLines}\n` : '',
|
|
498
|
+
generatedMark: AUTO_GENERATED_CODE_MARK,
|
|
499
|
+
importLines: importLines ? `${importLines}\n\n` : '',
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function renderMemberAssignment(member: CodegenMember, getMemberByName: boolean): string | null {
|
|
504
|
+
if (member.ignored) return null;
|
|
505
|
+
if (member.type === 'Controller') {
|
|
506
|
+
return getMemberByName
|
|
507
|
+
? `\t\t\t${member.name} = this.GetController("${escapeCSharpString(member.originalName)}");`
|
|
508
|
+
: `\t\t\t${member.name} = this.GetControllerAt(${member.index});`;
|
|
509
|
+
}
|
|
510
|
+
if (member.type === 'Transition') {
|
|
511
|
+
return getMemberByName
|
|
512
|
+
? `\t\t\t${member.name} = this.GetTransition("${escapeCSharpString(member.originalName)}");`
|
|
513
|
+
: `\t\t\t${member.name} = this.GetTransitionAt(${member.index});`;
|
|
514
|
+
}
|
|
515
|
+
return getMemberByName
|
|
516
|
+
? `\t\t\t${member.name} = (${member.type})this.GetChild("${escapeCSharpString(member.originalName)}");`
|
|
517
|
+
: `\t\t\t${member.name} = (${member.type})this.GetChildAt(${member.index});`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function renderFguiTypescriptMemberAssignment(
|
|
521
|
+
member: CodegenMember,
|
|
522
|
+
getMemberByName: boolean,
|
|
523
|
+
variant: FguiTypescriptVariant,
|
|
524
|
+
): string | null {
|
|
525
|
+
if (member.ignored) return null;
|
|
526
|
+
if (member.type === 'Controller') {
|
|
527
|
+
return getMemberByName
|
|
528
|
+
? `\t\tthis.${member.name} = this.getController("${escapeTypeScriptString(member.originalName)}");`
|
|
529
|
+
: `\t\tthis.${member.name} = this.getControllerAt(${member.index});`;
|
|
530
|
+
}
|
|
531
|
+
if (member.type === 'Transition') {
|
|
532
|
+
return getMemberByName
|
|
533
|
+
? `\t\tthis.${member.name} = this.getTransition("${escapeTypeScriptString(member.originalName)}");`
|
|
534
|
+
: `\t\tthis.${member.name} = this.getTransitionAt(${member.index});`;
|
|
535
|
+
}
|
|
536
|
+
const translatedType = translateFguiTypescriptType(member.type, variant);
|
|
537
|
+
return getMemberByName
|
|
538
|
+
? `\t\tthis.${member.name} = <${translatedType}><any>(this.getChild("${escapeTypeScriptString(member.originalName)}"));`
|
|
539
|
+
: `\t\tthis.${member.name} = <${translatedType}><any>(this.getChildAt(${member.index}));`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function resolveCodePath(
|
|
543
|
+
codePath: string,
|
|
544
|
+
basePath: string | undefined,
|
|
545
|
+
fs: Pick<PublishFileSystem, 'join'>,
|
|
546
|
+
): string {
|
|
547
|
+
if (isAbsolutePath(codePath)) return trimTrailingSlashes(codePath);
|
|
548
|
+
const projectBasePath = resolveProjectBasePath(basePath);
|
|
549
|
+
return projectBasePath ? trimTrailingSlashes(fs.join(projectBasePath, codePath)) : trimTrailingSlashes(codePath);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function resolveProjectBasePath(basePath: string | undefined): string {
|
|
553
|
+
if (!basePath) return '';
|
|
554
|
+
const normalized = trimTrailingSlashes(basePath);
|
|
555
|
+
const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
|
|
556
|
+
if (assetsMatch?.[1]) return assetsMatch[1];
|
|
557
|
+
return dirname(normalized);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function dirname(filePath: string): string {
|
|
561
|
+
const trimmed = trimTrailingSlashes(filePath);
|
|
562
|
+
const match = trimmed.match(/^(.*)[/\\][^/\\]+$/);
|
|
563
|
+
return match?.[1] ?? '';
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function trimTrailingSlashes(value: string): string {
|
|
567
|
+
return value.replace(/[/\\]+$/, '');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function isAbsolutePath(value: string): boolean {
|
|
571
|
+
return /^[a-z]:[/\\]/i.test(value) || value.startsWith('/') || value.startsWith('\\\\');
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function isDefaultMemberName(ownerType: string, kind: CodegenMember['kind'], name: string): boolean {
|
|
575
|
+
if (kind === 'controller') {
|
|
576
|
+
return (ownerType === 'GButton' || ownerType === 'GComboBox') && name === 'button';
|
|
577
|
+
}
|
|
578
|
+
if (kind === 'transition') return false;
|
|
579
|
+
|
|
580
|
+
if (ownerType === 'GButton' || ownerType === 'GLabel' || ownerType === 'GComboBox') {
|
|
581
|
+
return name === 'title' || name === 'icon';
|
|
582
|
+
}
|
|
583
|
+
if (ownerType === 'GProgressBar') {
|
|
584
|
+
return name === 'bar' || name === 'bar_v' || name === 'title' || name === 'ani';
|
|
585
|
+
}
|
|
586
|
+
if (ownerType === 'GSlider') {
|
|
587
|
+
return name === 'bar' || name === 'bar_v' || name === 'grip' || name === 'title' || name === 'ani';
|
|
588
|
+
}
|
|
589
|
+
return /^n\d+(?:_.*)?$/i.test(name);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function applyMemberNamePrefix(name: string, prefix: string): string {
|
|
593
|
+
const normalized = normalizeMemberName(name) || 'member';
|
|
594
|
+
return prefix ? `${prefix}${normalized}` : normalized;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function normalizeMemberName(value: string): string {
|
|
598
|
+
const cleaned = value.replace(/[^0-9A-Za-z_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
599
|
+
if (!cleaned) return '';
|
|
600
|
+
return /^[0-9]/.test(cleaned) ? `_${cleaned}` : cleaned;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function normalizeTypeName(value: string): string {
|
|
604
|
+
const cleaned = value.replace(/[^0-9A-Za-z_]+/g, '_').replace(/^_+|_+$/g, '');
|
|
605
|
+
if (!cleaned) return '';
|
|
606
|
+
const parts = cleaned.split(/_+/).filter(Boolean);
|
|
607
|
+
const normalized = parts.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('');
|
|
608
|
+
return /^[0-9]/.test(normalized) ? `_${normalized}` : normalized;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function collectFguiTypescriptImports(classInfo: CodegenClass, variant: FguiTypescriptVariant): string {
|
|
612
|
+
const imports = new Set<string>();
|
|
613
|
+
for (const member of classInfo.members) {
|
|
614
|
+
if (member.ignored) continue;
|
|
615
|
+
const translated = translateFguiTypescriptType(member.type, variant);
|
|
616
|
+
if (!translated.includes('.')) {
|
|
617
|
+
imports.add(`import ${translated} from "./${translated}";`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return imports.size > 0 ? `${[...imports].sort().join('\n')}\n\n` : '';
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function translateFguiTypescriptType(typeName: string, variant: FguiTypescriptVariant): string {
|
|
624
|
+
if (FGUI_TYPESCRIPT_RUNTIME_TYPES.has(typeName)) {
|
|
625
|
+
return `${variant.runtimeNamespace}.${typeName}`;
|
|
626
|
+
}
|
|
627
|
+
return typeName;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function renderTemplate(template: string, data: Record<string, string>): string {
|
|
631
|
+
let output = template;
|
|
632
|
+
for (const [key, value] of Object.entries(data)) {
|
|
633
|
+
output = output.replaceAll(`{{${key}}}`, value);
|
|
634
|
+
}
|
|
635
|
+
return output;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function escapeCSharpString(value: string): string {
|
|
639
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function escapeTypeScriptString(value: string): string {
|
|
643
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function writeTextFile(fs: PublishFileSystem, filePath: string, content: string): Promise<void> {
|
|
647
|
+
await fs.writeFileRaw(filePath, encodeText(content));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function encodeText(value: string): Uint8Array {
|
|
651
|
+
return new TextEncoder().encode(value);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function decodeText(value: Uint8Array): string {
|
|
655
|
+
return new TextDecoder().decode(value);
|
|
656
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { inspect, type InspectReport, type InspectCategoryReport } from './inspect.js';
|
|
2
|
+
export { validate, type ValidateOptions, type ValidationResult, type ValidationIssue, ValidationSeverity } from './validate.js';
|
|
3
|
+
export { prune, type PruneOptions } from './prune.js';
|
|
4
|
+
export { rename, type RenameOptions } from './rename.js';
|
|
5
|
+
export { atlas, type AtlasOptions } from './atlas.js';
|
|
6
|
+
export { publishCodeGeneration, AUTO_GENERATED_CODE_MARK, type PublishCodeGenerationOptions } from './codegen.js';
|
|
7
|
+
export {
|
|
8
|
+
publish,
|
|
9
|
+
resolvePublishOptions,
|
|
10
|
+
type PublishOptions,
|
|
11
|
+
type ResolvedPublishAtlasOptions,
|
|
12
|
+
type ResolvedPublishOptions,
|
|
13
|
+
type ResolvePublishOptionsOverrides,
|
|
14
|
+
} from './publish.js';
|
|
15
|
+
export { createTransform } from './utils.js';
|
|
16
|
+
export type {
|
|
17
|
+
CliAtlasSettings,
|
|
18
|
+
CliPublishSettings,
|
|
19
|
+
ExtrasMap,
|
|
20
|
+
HasOptionalFont,
|
|
21
|
+
HasOptionalSrc,
|
|
22
|
+
HasOptionalUrl,
|
|
23
|
+
PublishDependency,
|
|
24
|
+
PublishFileSystem,
|
|
25
|
+
RootProjectSettings,
|
|
26
|
+
} from './shared-types.js';
|