@openfairygui/functions 0.2.0-alpha.0 → 0.2.0-alpha.11
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 +4 -0
- package/dist/index.cjs +872 -282
- package/dist/index.d.cts +143 -79
- package/dist/index.d.ts +143 -79
- package/dist/index.js +869 -283
- package/dist/uam-transaction.cjs +79 -0
- package/dist/uam-transaction.d.cts +42 -0
- package/dist/uam-transaction.d.ts +42 -0
- package/dist/uam-transaction.js +78 -0
- package/package.json +66 -53
- package/src/atlas.ts +502 -146
- package/src/codegen.ts +104 -66
- package/src/index.ts +25 -1
- package/src/plugins/loader.ts +85 -0
- package/src/plugins/types.ts +47 -0
- package/src/publish.ts +499 -105
- package/src/restore.ts +12 -5
- package/src/shared-types.ts +1 -0
- package/src/uam-transaction.ts +69 -1
- package/src/utils.ts +28 -0
package/src/codegen.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Component,
|
|
3
|
-
type GComponent,
|
|
4
3
|
type Document,
|
|
4
|
+
type GComponent,
|
|
5
5
|
type GObject,
|
|
6
6
|
type Package,
|
|
7
7
|
ProjectType,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
UNITY_BINDER_TEMPLATE,
|
|
13
13
|
UNITY_COMPONENT_TEMPLATE,
|
|
14
14
|
} from './codegen-templates.js';
|
|
15
|
+
import { formatPluginError, type LoadedPlugin } from './plugins/loader.js';
|
|
15
16
|
import type { CliCodeGenerationSettings, PublishFileSystem, RootProjectSettings } from './shared-types.js';
|
|
16
17
|
|
|
17
18
|
export const AUTO_GENERATED_CODE_MARK = '/** This is an automatically generated class by FairyGUI. Please do not modify it. **/';
|
|
@@ -22,25 +23,15 @@ export interface PublishCodeGenerationOptions {
|
|
|
22
23
|
basePath?: string;
|
|
23
24
|
fs: PublishFileSystem;
|
|
24
25
|
packages: Package[];
|
|
26
|
+
plugins?: LoadedPlugin[];
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
interface
|
|
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 {
|
|
29
|
+
export interface ResolvedPackageCodegenPlan {
|
|
39
30
|
outputDir: string;
|
|
40
31
|
packageFolderName: string;
|
|
41
32
|
packageNamespace: string;
|
|
42
33
|
binderClassName: string;
|
|
43
|
-
settings:
|
|
34
|
+
settings: CliCodeGenerationSettings;
|
|
44
35
|
}
|
|
45
36
|
|
|
46
37
|
interface FguiTypescriptVariant {
|
|
@@ -77,16 +68,22 @@ const SHARED_FGUI_TYPESCRIPT_VARIANT: FguiTypescriptVariant = {
|
|
|
77
68
|
runtimeNamespace: 'fgui',
|
|
78
69
|
};
|
|
79
70
|
|
|
80
|
-
interface CodegenMember {
|
|
71
|
+
export interface CodegenMember {
|
|
81
72
|
index: number;
|
|
82
73
|
kind: 'child' | 'controller' | 'transition';
|
|
83
74
|
name: string;
|
|
84
75
|
originalName: string;
|
|
85
76
|
type: string;
|
|
86
77
|
ignored: boolean;
|
|
78
|
+
referencedComponent?: CodegenReferencedComponent;
|
|
87
79
|
}
|
|
88
80
|
|
|
89
|
-
interface
|
|
81
|
+
export interface CodegenReferencedComponent {
|
|
82
|
+
component: Component;
|
|
83
|
+
package: Package;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CodegenClass {
|
|
90
87
|
classId: string;
|
|
91
88
|
className: string;
|
|
92
89
|
encodedClassName: string;
|
|
@@ -97,14 +94,28 @@ interface CodegenClass {
|
|
|
97
94
|
members: CodegenMember[];
|
|
98
95
|
}
|
|
99
96
|
|
|
100
|
-
export async function publishCodeGeneration(
|
|
101
|
-
doc: Document,
|
|
102
|
-
options: PublishCodeGenerationOptions,
|
|
103
|
-
): Promise<void> {
|
|
97
|
+
export async function publishCodeGeneration(doc: Document, options: PublishCodeGenerationOptions): Promise<void> {
|
|
104
98
|
const logger = doc.getLogger();
|
|
105
99
|
const settings = resolveCodeGenerationSettings(doc);
|
|
106
100
|
if (!settings.allowGenCode) return;
|
|
107
101
|
|
|
102
|
+
const plugins = options.plugins?.filter((plugin) => typeof plugin.plugin.genCode === 'function') ?? [];
|
|
103
|
+
if (plugins.length > 0) {
|
|
104
|
+
let handled = false;
|
|
105
|
+
for (const plugin of plugins) {
|
|
106
|
+
try {
|
|
107
|
+
await plugin.plugin.genCode(doc, settings, options);
|
|
108
|
+
handled = true;
|
|
109
|
+
logger.info(`publish: Generated code using plugin "${plugin.name}"`);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.warn(`publish: Code generation plugin "${plugin.name}" failed: ${formatPluginError(error)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (handled) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
for (const pkg of options.packages) {
|
|
109
120
|
if (!pkg.getGenCode()) continue;
|
|
110
121
|
|
|
@@ -129,7 +140,7 @@ export async function publishCodeGeneration(
|
|
|
129
140
|
}
|
|
130
141
|
}
|
|
131
142
|
|
|
132
|
-
function resolveCodeGenerationSettings(doc: Document):
|
|
143
|
+
export function resolveCodeGenerationSettings(doc: Document): Required<CliCodeGenerationSettings> {
|
|
133
144
|
const settings = (doc.getRoot().getSettings?.() ?? {}) as RootProjectSettings;
|
|
134
145
|
const publish = settings.publish ?? {};
|
|
135
146
|
const codeGeneration = publish.codeGeneration as CliCodeGenerationSettings | undefined;
|
|
@@ -159,11 +170,7 @@ function resolveCodeGenerationSettings(doc: Document): ResolvedCodeGenerationSet
|
|
|
159
170
|
};
|
|
160
171
|
}
|
|
161
172
|
|
|
162
|
-
function resolvePackageCodegenPlan(
|
|
163
|
-
pkg: Package,
|
|
164
|
-
settings: ResolvedCodeGenerationSettings,
|
|
165
|
-
options: PublishCodeGenerationOptions,
|
|
166
|
-
): ResolvedPackageCodegenPlan | null {
|
|
173
|
+
export function resolvePackageCodegenPlan(pkg: Package, settings: CliCodeGenerationSettings, options: PublishCodeGenerationOptions): ResolvedPackageCodegenPlan | null {
|
|
167
174
|
const rawCodePath = (pkg.getCodePath() || settings.codePath || '').trim();
|
|
168
175
|
if (!rawCodePath) return null;
|
|
169
176
|
|
|
@@ -276,17 +283,11 @@ async function cleanupGeneratedFiles(directory: string, fs: PublishFileSystem, e
|
|
|
276
283
|
}
|
|
277
284
|
}
|
|
278
285
|
|
|
279
|
-
function buildCodegenClasses(
|
|
280
|
-
|
|
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()));
|
|
286
|
+
export function buildCodegenClasses(doc: Document, pkg: Package, plan: ResolvedPackageCodegenPlan): CodegenClass[] {
|
|
287
|
+
const codegenComponents = pkg.listComponents().sort((left, right) => left.getId().localeCompare(right.getId()));
|
|
287
288
|
const generatedById = new Map<string, CodegenClass>();
|
|
288
289
|
|
|
289
|
-
for (const component of
|
|
290
|
+
for (const component of codegenComponents) {
|
|
290
291
|
const encodedClassName = `${plan.settings.classNamePrefix}${normalizeTypeName(component.getName()) || 'Component'}`;
|
|
291
292
|
generatedById.set(component.getId(), {
|
|
292
293
|
classId: component.getId(),
|
|
@@ -300,7 +301,19 @@ function buildCodegenClasses(
|
|
|
300
301
|
});
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
for (const component of
|
|
304
|
+
for (const component of codegenComponents) {
|
|
305
|
+
const classInfo = generatedById.get(component.getId());
|
|
306
|
+
if (!classInfo) continue;
|
|
307
|
+
classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const [componentId, classInfo] of generatedById) {
|
|
311
|
+
if (classInfo.members.every((member) => member.ignored)) {
|
|
312
|
+
generatedById.delete(componentId);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (const component of codegenComponents) {
|
|
304
317
|
const classInfo = generatedById.get(component.getId());
|
|
305
318
|
if (!classInfo) continue;
|
|
306
319
|
classInfo.members = buildCodegenMembers(doc, pkg, component, plan, generatedById);
|
|
@@ -327,13 +340,17 @@ function buildCodegenMembers(
|
|
|
327
340
|
}
|
|
328
341
|
|
|
329
342
|
for (const child of component.listChildren()) {
|
|
343
|
+
if (!isRuntimeChild(child)) continue;
|
|
344
|
+
const index = childIndex++;
|
|
345
|
+
const resolvedChild = resolveChildType(doc, pkg, child, generatedById);
|
|
330
346
|
members.push(createMember(
|
|
331
347
|
ownerType,
|
|
332
348
|
'child',
|
|
333
|
-
|
|
349
|
+
resolvedChild.type,
|
|
334
350
|
child.getName(),
|
|
335
|
-
|
|
351
|
+
index,
|
|
336
352
|
plan,
|
|
353
|
+
resolvedChild.referencedComponent,
|
|
337
354
|
));
|
|
338
355
|
}
|
|
339
356
|
|
|
@@ -355,6 +372,10 @@ function buildCodegenMembers(
|
|
|
355
372
|
return members;
|
|
356
373
|
}
|
|
357
374
|
|
|
375
|
+
function isRuntimeChild(child: GObject): boolean {
|
|
376
|
+
return child.propertyType !== 'GGroup' || (child as GObject & { getAdvanced?(): boolean }).getAdvanced?.() === true;
|
|
377
|
+
}
|
|
378
|
+
|
|
358
379
|
function createMember(
|
|
359
380
|
ownerType: string,
|
|
360
381
|
kind: CodegenMember['kind'],
|
|
@@ -362,6 +383,7 @@ function createMember(
|
|
|
362
383
|
originalName: string,
|
|
363
384
|
index: number,
|
|
364
385
|
plan: ResolvedPackageCodegenPlan,
|
|
386
|
+
referencedComponent?: CodegenReferencedComponent,
|
|
365
387
|
): CodegenMember {
|
|
366
388
|
const ignored = plan.settings.ignoreNoname && isDefaultMemberName(ownerType, kind, originalName);
|
|
367
389
|
return {
|
|
@@ -371,43 +393,59 @@ function createMember(
|
|
|
371
393
|
originalName,
|
|
372
394
|
type,
|
|
373
395
|
ignored,
|
|
396
|
+
referencedComponent,
|
|
374
397
|
};
|
|
375
398
|
}
|
|
376
399
|
|
|
400
|
+
interface ResolvedChildCodegenType {
|
|
401
|
+
type: string;
|
|
402
|
+
referencedComponent?: CodegenReferencedComponent;
|
|
403
|
+
}
|
|
404
|
+
|
|
377
405
|
function resolveChildType(
|
|
378
406
|
doc: Document,
|
|
379
407
|
pkg: Package,
|
|
380
408
|
child: GObject,
|
|
381
409
|
generatedById: Map<string, CodegenClass>,
|
|
382
|
-
):
|
|
410
|
+
): ResolvedChildCodegenType {
|
|
383
411
|
const src = (child as GObject & { getSrc?(): string }).getSrc?.();
|
|
384
412
|
if (src) {
|
|
385
|
-
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
413
|
+
let referencedComponent: CodegenReferencedComponent | null = null;
|
|
414
|
+
if (src.startsWith('ui://')) {
|
|
415
|
+
const rest = src.slice(5);
|
|
416
|
+
const pkgId = rest.slice(0, 8);
|
|
417
|
+
const resourceId = rest.slice(8);
|
|
418
|
+
const targetPackage = doc.getRoot().listPackages().find((candidate) => candidate.getId() === pkgId);
|
|
419
|
+
const targetResource = targetPackage?.getResourceById(resourceId);
|
|
420
|
+
if (targetPackage && targetResource?.propertyType === 'Component') {
|
|
421
|
+
referencedComponent = { component: targetResource, package: targetPackage };
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
const packageId = (child as GComponent & { getPackageId?(): string }).getPackageId?.();
|
|
425
|
+
const targetPackage = packageId
|
|
426
|
+
? doc.getRoot().listPackages().find((candidate) => candidate.getId() === packageId)
|
|
427
|
+
: pkg;
|
|
428
|
+
const targetResource = targetPackage?.getResourceById(src);
|
|
429
|
+
if (targetPackage && targetResource?.propertyType === 'Component') {
|
|
430
|
+
referencedComponent = { component: targetResource, package: targetPackage };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (referencedComponent) {
|
|
435
|
+
const localGeneratedClass = referencedComponent.package === pkg
|
|
436
|
+
? generatedById.get(referencedComponent.component.getId())
|
|
437
|
+
: undefined;
|
|
438
|
+
return {
|
|
439
|
+
type: localGeneratedClass?.encodedClassName ?? resolveComponentBaseType(referencedComponent.component),
|
|
440
|
+
referencedComponent,
|
|
441
|
+
};
|
|
389
442
|
}
|
|
390
443
|
}
|
|
391
444
|
|
|
392
445
|
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
|
-
}
|
|
446
|
+
if (instanceExtType) return { type: `G${instanceExtType}` };
|
|
408
447
|
|
|
409
|
-
|
|
410
|
-
return localResource?.propertyType === 'Component' ? localResource : null;
|
|
448
|
+
return { type: child.propertyType };
|
|
411
449
|
}
|
|
412
450
|
|
|
413
451
|
function resolveComponentBaseType(component: Component): string {
|
|
@@ -549,7 +587,7 @@ function resolveCodePath(
|
|
|
549
587
|
return projectBasePath ? trimTrailingSlashes(fs.join(projectBasePath, codePath)) : trimTrailingSlashes(codePath);
|
|
550
588
|
}
|
|
551
589
|
|
|
552
|
-
function resolveProjectBasePath(basePath: string | undefined): string {
|
|
590
|
+
export function resolveProjectBasePath(basePath: string | undefined): string {
|
|
553
591
|
if (!basePath) return '';
|
|
554
592
|
const normalized = trimTrailingSlashes(basePath);
|
|
555
593
|
const assetsMatch = normalized.match(/^(.*)[/\\]assets(?:_[^/\\]+)?$/i);
|
|
@@ -578,13 +616,13 @@ function isDefaultMemberName(ownerType: string, kind: CodegenMember['kind'], nam
|
|
|
578
616
|
if (kind === 'transition') return false;
|
|
579
617
|
|
|
580
618
|
if (ownerType === 'GButton' || ownerType === 'GLabel' || ownerType === 'GComboBox') {
|
|
581
|
-
|
|
619
|
+
if (name === 'title' || name === 'icon') return true;
|
|
582
620
|
}
|
|
583
621
|
if (ownerType === 'GProgressBar') {
|
|
584
|
-
|
|
622
|
+
if (name === 'bar' || name === 'bar_v' || name === 'title' || name === 'ani') return true;
|
|
585
623
|
}
|
|
586
624
|
if (ownerType === 'GSlider') {
|
|
587
|
-
|
|
625
|
+
if (name === 'bar' || name === 'bar_v' || name === 'grip' || name === 'title' || name === 'ani') return true;
|
|
588
626
|
}
|
|
589
627
|
return /^n\d+(?:_.*)?$/i.test(name);
|
|
590
628
|
}
|
|
@@ -647,10 +685,10 @@ async function writeTextFile(fs: PublishFileSystem, filePath: string, content: s
|
|
|
647
685
|
await fs.writeFileRaw(filePath, encodeText(content));
|
|
648
686
|
}
|
|
649
687
|
|
|
650
|
-
function encodeText(value: string): Uint8Array {
|
|
688
|
+
export function encodeText(value: string): Uint8Array {
|
|
651
689
|
return new TextEncoder().encode(value);
|
|
652
690
|
}
|
|
653
691
|
|
|
654
|
-
function decodeText(value: Uint8Array): string {
|
|
692
|
+
export function decodeText(value: Uint8Array): string {
|
|
655
693
|
return new TextDecoder().decode(value);
|
|
656
694
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,7 +3,29 @@ export { validate, type ValidateOptions, type ValidationResult, type ValidationI
|
|
|
3
3
|
export { prune, type PruneOptions } from './prune.js';
|
|
4
4
|
export { rename, type RenameOptions } from './rename.js';
|
|
5
5
|
export { atlas, type AtlasOptions } from './atlas.js';
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
publishCodeGeneration,
|
|
8
|
+
AUTO_GENERATED_CODE_MARK,
|
|
9
|
+
type PublishCodeGenerationOptions,
|
|
10
|
+
type ResolvedPackageCodegenPlan,
|
|
11
|
+
resolvePackageCodegenPlan,
|
|
12
|
+
buildCodegenClasses,
|
|
13
|
+
type CodegenClass,
|
|
14
|
+
type CodegenMember,
|
|
15
|
+
type CodegenReferencedComponent,
|
|
16
|
+
encodeText,
|
|
17
|
+
decodeText,
|
|
18
|
+
} from './codegen.js';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
CodeWriter,
|
|
23
|
+
ICodeWriterConfig,
|
|
24
|
+
MaybePromise,
|
|
25
|
+
Plugin,
|
|
26
|
+
PluginManifest,
|
|
27
|
+
PluginModule,
|
|
28
|
+
} from './plugins/types.js';
|
|
7
29
|
export {
|
|
8
30
|
restore,
|
|
9
31
|
type RestoreFileSystem,
|
|
@@ -24,6 +46,7 @@ export {
|
|
|
24
46
|
} from './publish.js';
|
|
25
47
|
export {
|
|
26
48
|
applyUamTransactionApp,
|
|
49
|
+
type ApplyUamTransactionAppDiagnostic,
|
|
27
50
|
type ApplyUamTransactionAppError,
|
|
28
51
|
type ApplyUamTransactionAppInput,
|
|
29
52
|
type ApplyUamTransactionAppResult,
|
|
@@ -39,4 +62,5 @@ export type {
|
|
|
39
62
|
PublishDependency,
|
|
40
63
|
PublishFileSystem,
|
|
41
64
|
RootProjectSettings,
|
|
65
|
+
CliCodeGenerationSettings,
|
|
42
66
|
} from './shared-types.js';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Document } from '@openfairygui/core';
|
|
2
|
+
import { createJiti } from 'jiti';
|
|
3
|
+
import type { Plugin, PluginManifest, PluginModule } from './types.js';
|
|
4
|
+
|
|
5
|
+
export interface LoadedPlugin {
|
|
6
|
+
name: string;
|
|
7
|
+
plugin: Plugin;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface PluginPackageJson extends Partial<PluginManifest> {
|
|
11
|
+
name?: string;
|
|
12
|
+
main?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Keep Node builtins out of the neutral bundle resolver while still loading plugins in Node.
|
|
16
|
+
const importNative = new Function('id', 'return import(id)') as <T>(id: string) => Promise<T>;
|
|
17
|
+
|
|
18
|
+
export async function loadPlugins(doc: Document, pluginsDir: string): Promise<LoadedPlugin[]> {
|
|
19
|
+
if (!pluginsDir) return [];
|
|
20
|
+
|
|
21
|
+
const fs = await importNative<typeof import('node:fs/promises')>('node:fs/promises');
|
|
22
|
+
const path = await importNative<typeof import('node:path')>('node:path');
|
|
23
|
+
let entries: Array<{ name: string; isDirectory(): boolean }>;
|
|
24
|
+
try {
|
|
25
|
+
entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const plugins: LoadedPlugin[] = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (!entry.isDirectory()) continue;
|
|
33
|
+
const pluginDir = path.join(pluginsDir, entry.name);
|
|
34
|
+
try {
|
|
35
|
+
const manifest = await readPluginManifest(fs, path, pluginDir);
|
|
36
|
+
if (!manifest) continue;
|
|
37
|
+
|
|
38
|
+
const mainPath = resolvePluginMain(path, pluginDir, manifest);
|
|
39
|
+
const plugin = await loadPlugin(mainPath);
|
|
40
|
+
plugins.push({ name: manifest.name, plugin });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
doc.getLogger().warn(`publish: Plugin "${entry.name}" was skipped: ${formatPluginError(error)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return plugins;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function formatPluginError(error: unknown): string {
|
|
50
|
+
return error instanceof Error ? error.message : String(error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function readPluginManifest(
|
|
54
|
+
fs: typeof import('node:fs/promises'),
|
|
55
|
+
path: typeof import('node:path'),
|
|
56
|
+
pluginDir: string,
|
|
57
|
+
): Promise<PluginPackageJson | null> {
|
|
58
|
+
const manifestPath = path.join(pluginDir, 'package.json');
|
|
59
|
+
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
60
|
+
const manifest = JSON.parse(content) as PluginPackageJson;
|
|
61
|
+
if (!manifest.name) throw new Error(`Codegen plugin at ${pluginDir} is missing package.json name.`);
|
|
62
|
+
if (!manifest.main) throw new Error(`Codegen plugin "${manifest.name}" is missing package.json main.`);
|
|
63
|
+
return manifest;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolvePluginMain(path: typeof import('node:path'), pluginDir: string, manifest: PluginPackageJson): string {
|
|
67
|
+
const mainPath = path.resolve(pluginDir, manifest.main!);
|
|
68
|
+
const relative = path.relative(pluginDir, mainPath);
|
|
69
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
70
|
+
throw new Error(`Codegen plugin "${manifest.name}" main must resolve inside its plugin directory.`);
|
|
71
|
+
}
|
|
72
|
+
return mainPath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function loadPlugin(mainPath: string): Promise<Plugin> {
|
|
76
|
+
const jiti = createJiti(import.meta.url);
|
|
77
|
+
const mod = await jiti.import<PluginModule>(mainPath);
|
|
78
|
+
const defaultExport = mod.default;
|
|
79
|
+
const plugin = isObject(defaultExport) ? defaultExport : mod;
|
|
80
|
+
return plugin as Plugin;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
84
|
+
return value !== null && typeof value === 'object';
|
|
85
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Document } from '@openfairygui/core';
|
|
2
|
+
import type { PublishCodeGenerationOptions } from '../codegen.js';
|
|
3
|
+
import type { PublishOptions } from '../publish.js';
|
|
4
|
+
import type { CliCodeGenerationSettings } from '../shared-types.js';
|
|
5
|
+
|
|
6
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
|
|
8
|
+
export interface PluginManifest {
|
|
9
|
+
name: string;
|
|
10
|
+
displayName?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
author?: {
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
icon?: string;
|
|
17
|
+
main: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ICodeWriterConfig {
|
|
21
|
+
blockStart?: string;
|
|
22
|
+
blockEnd?: string;
|
|
23
|
+
blockFromNewLine?: boolean;
|
|
24
|
+
usingTabs?: boolean;
|
|
25
|
+
endOfLine?: string;
|
|
26
|
+
fileMark?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CodeWriter {
|
|
30
|
+
writeMark(): void;
|
|
31
|
+
writeln(fmt?: string, ...args: any[]): CodeWriter;
|
|
32
|
+
startBlock(): CodeWriter;
|
|
33
|
+
endBlock(): CodeWriter;
|
|
34
|
+
incIndent(): CodeWriter;
|
|
35
|
+
decIndent(): CodeWriter;
|
|
36
|
+
reset(): void;
|
|
37
|
+
toString(): string;
|
|
38
|
+
save(filePath: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Plugin {
|
|
42
|
+
genCode?: (doc: Document, settings: Required<CliCodeGenerationSettings>, options: PublishCodeGenerationOptions) => MaybePromise<void>;
|
|
43
|
+
onPublishStart?: (doc: Document, options: PublishOptions) => MaybePromise<void>;
|
|
44
|
+
onPublishEnd?: (doc: Document, options: PublishOptions) => MaybePromise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type PluginModule = Plugin & { default?: Plugin };
|