@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/rename.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Document, Transform } from '@openfairygui/core';
|
|
2
|
+
import { createTransform } from './utils.js';
|
|
3
|
+
|
|
4
|
+
export interface RenameOptions {
|
|
5
|
+
/** Package name to rename from. Required. */
|
|
6
|
+
packageName: string;
|
|
7
|
+
/** Resource name to rename from. Required. */
|
|
8
|
+
resourceName: string;
|
|
9
|
+
/** New name for the resource. Required. */
|
|
10
|
+
newName: string;
|
|
11
|
+
/** If true, also update all `ui://` references pointing to this resource. Default: true. */
|
|
12
|
+
updateReferences?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Renames a resource and optionally updates all references to it.
|
|
17
|
+
*
|
|
18
|
+
* This searches all display objects' `src` attributes across all packages
|
|
19
|
+
* for `ui://` URLs that point to the renamed resource, and updates them
|
|
20
|
+
* to reflect the new name (the resource ID doesn't change, so references
|
|
21
|
+
* are already valid — but the name stored in package.xml is updated).
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* await doc.transform(rename({
|
|
25
|
+
* packageName: 'Basics',
|
|
26
|
+
* resourceName: 'Button',
|
|
27
|
+
* newName: 'PrimaryButton',
|
|
28
|
+
* }));
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function rename(options: RenameOptions): Transform {
|
|
32
|
+
const updateReferences = options.updateReferences ?? true;
|
|
33
|
+
|
|
34
|
+
return createTransform('rename', (doc: Document): void => {
|
|
35
|
+
const root = doc.getRoot();
|
|
36
|
+
const logger = doc.getLogger();
|
|
37
|
+
const pkg = root.listPackages().find((p) => p.getName() === options.packageName);
|
|
38
|
+
|
|
39
|
+
if (!pkg) {
|
|
40
|
+
logger.warn(`rename: Package "${options.packageName}" not found.`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Find the resource
|
|
45
|
+
const resource = pkg.listResources().find((r) => r.getName() === options.resourceName)
|
|
46
|
+
|| pkg.listComponents().find((c) => c.getName() === options.resourceName);
|
|
47
|
+
|
|
48
|
+
if (!resource) {
|
|
49
|
+
logger.warn(`rename: Resource "${options.resourceName}" not found in package "${options.packageName}".`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const oldName = resource.getName();
|
|
54
|
+
resource.setName(options.newName);
|
|
55
|
+
logger.info(`rename: Renamed "${oldName}" → "${options.newName}" in package "${options.packageName}".`);
|
|
56
|
+
|
|
57
|
+
// If this is a component resource, we don't need to update src references
|
|
58
|
+
// because src references use resource IDs (not names)
|
|
59
|
+
if (updateReferences) {
|
|
60
|
+
// Resource IDs are stable, so ui:// references don't need updating
|
|
61
|
+
// unless the consumer relies on name-based lookups.
|
|
62
|
+
// The primary rename is the resource name itself.
|
|
63
|
+
logger.info(`rename: References use resource IDs — no src updates needed.`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { FileSystem, ProjectSettings, PublishSettings } from '@openfairygui/core';
|
|
2
|
+
|
|
3
|
+
export type ExtrasMap = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export interface CliCodeGenerationSettings extends NonNullable<PublishSettings['codeGeneration']> {
|
|
6
|
+
allowGenCode?: boolean;
|
|
7
|
+
classNamePrefix?: string;
|
|
8
|
+
codePath?: string;
|
|
9
|
+
codeType?: string;
|
|
10
|
+
getMemberByName?: boolean;
|
|
11
|
+
ignoreNoname?: boolean;
|
|
12
|
+
memberNamePrefix?: string;
|
|
13
|
+
packageName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CliAtlasSettings extends NonNullable<PublishSettings['atlasSetting']> {
|
|
17
|
+
maxSize?: number;
|
|
18
|
+
paging?: boolean;
|
|
19
|
+
sizeOption?: string;
|
|
20
|
+
forceSquare?: boolean;
|
|
21
|
+
fast?: boolean;
|
|
22
|
+
allowRotation?: boolean;
|
|
23
|
+
padding?: number;
|
|
24
|
+
trimImage?: boolean;
|
|
25
|
+
extractAlpha?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CliPublishSettings extends PublishSettings {
|
|
29
|
+
atlasSetting?: CliAtlasSettings;
|
|
30
|
+
codeGeneration?: CliCodeGenerationSettings;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type RootProjectSettings = ProjectSettings & {
|
|
34
|
+
publish?: CliPublishSettings;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export interface PublishDependency {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PackagePublishArtifactsExtras extends ExtrasMap {
|
|
43
|
+
publishedResourceIds?: string[];
|
|
44
|
+
publishedIncludeBranches?: boolean;
|
|
45
|
+
publishedEffectiveResourceIds?: Record<string, string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface HasOptionalFont {
|
|
49
|
+
getFont?(): string | string[] | null | undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface HasOptionalSrc {
|
|
53
|
+
getSrc?(): string | undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface HasOptionalUrl {
|
|
57
|
+
getUrl?(): string | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type PublishFileSystem = Pick<FileSystem, 'join' | 'mkdir' | 'writeFileRaw'> & {
|
|
61
|
+
deleteFile?: (path: string) => Promise<void>;
|
|
62
|
+
exists?: FileSystem['exists'];
|
|
63
|
+
readdir?: FileSystem['readdir'];
|
|
64
|
+
readFileRaw?: FileSystem['readFileRaw'];
|
|
65
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Transform } from '@openfairygui/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a transform function, assigning it a name for the transform stack.
|
|
5
|
+
*/
|
|
6
|
+
export function createTransform(name: string, fn: Transform): Transform {
|
|
7
|
+
Object.defineProperty(fn, 'name', { value: name });
|
|
8
|
+
return fn;
|
|
9
|
+
}
|
package/src/validate.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { Document, Transform } from '@openfairygui/core';
|
|
2
|
+
import { createTransform } from './utils.js';
|
|
3
|
+
import type { HasOptionalSrc } from './shared-types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Severity of a validation issue.
|
|
7
|
+
*/
|
|
8
|
+
export enum ValidationSeverity {
|
|
9
|
+
ERROR = 'error',
|
|
10
|
+
WARNING = 'warning',
|
|
11
|
+
INFO = 'info',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A single validation issue found in the project.
|
|
16
|
+
*/
|
|
17
|
+
export interface ValidationIssue {
|
|
18
|
+
severity: ValidationSeverity;
|
|
19
|
+
message: string;
|
|
20
|
+
/** Package name where the issue was found. */
|
|
21
|
+
packageName?: string;
|
|
22
|
+
/** Component name where the issue was found. */
|
|
23
|
+
componentName?: string;
|
|
24
|
+
/** Resource/object name related to the issue. */
|
|
25
|
+
resourceName?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result returned by `validate()`.
|
|
30
|
+
*/
|
|
31
|
+
export interface ValidationResult {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
errors: ValidationIssue[];
|
|
34
|
+
warnings: ValidationIssue[];
|
|
35
|
+
infos: ValidationIssue[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ValidateOptions {
|
|
39
|
+
/** If true, the transform throws on errors. Default: false. */
|
|
40
|
+
throwOnError?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const VALIDATE_DEFAULTS: Required<ValidateOptions> = {
|
|
44
|
+
throwOnError: false,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates a FairyGUI project for common issues:
|
|
49
|
+
* - Missing resource IDs
|
|
50
|
+
* - Broken `ui://` references (src pointing to non-existent resources)
|
|
51
|
+
* - Empty components (no children)
|
|
52
|
+
* - Controllers with no pages
|
|
53
|
+
* - Duplicate resource IDs within a package
|
|
54
|
+
*
|
|
55
|
+
* The validation result is stored in `doc.getRoot().getExtras()._validation`.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* await doc.transform(validate({ throwOnError: true }));
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export function validate(_options: ValidateOptions = {}): Transform {
|
|
62
|
+
const options = { ...VALIDATE_DEFAULTS, ..._options };
|
|
63
|
+
|
|
64
|
+
return createTransform('validate', (doc: Document): void => {
|
|
65
|
+
const issues: ValidationIssue[] = [];
|
|
66
|
+
const root = doc.getRoot();
|
|
67
|
+
|
|
68
|
+
if (!root.getProjectId()) {
|
|
69
|
+
issues.push({
|
|
70
|
+
severity: ValidationSeverity.WARNING,
|
|
71
|
+
message: 'Project has no ID.',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build global resource ID map for cross-reference validation
|
|
76
|
+
const globalResources = new Map<string, string>(); // id → "pkg/name"
|
|
77
|
+
|
|
78
|
+
for (const pkg of root.listPackages()) {
|
|
79
|
+
if (!pkg.getId()) {
|
|
80
|
+
issues.push({
|
|
81
|
+
severity: ValidationSeverity.ERROR,
|
|
82
|
+
message: `Package "${pkg.getName()}" has no ID.`,
|
|
83
|
+
packageName: pkg.getName(),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check for duplicate resource IDs within the package
|
|
88
|
+
const idSet = new Set<string>();
|
|
89
|
+
for (const res of pkg.listResources()) {
|
|
90
|
+
const resId = res.getId();
|
|
91
|
+
if (!resId) {
|
|
92
|
+
issues.push({
|
|
93
|
+
severity: ValidationSeverity.WARNING,
|
|
94
|
+
message: `Resource "${res.getName()}" has no ID.`,
|
|
95
|
+
packageName: pkg.getName(),
|
|
96
|
+
resourceName: res.getName(),
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (idSet.has(resId)) {
|
|
101
|
+
issues.push({
|
|
102
|
+
severity: ValidationSeverity.ERROR,
|
|
103
|
+
message: `Duplicate resource ID "${resId}" in package "${pkg.getName()}".`,
|
|
104
|
+
packageName: pkg.getName(),
|
|
105
|
+
resourceName: res.getName(),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
idSet.add(resId);
|
|
109
|
+
globalResources.set(`${pkg.getId()}${resId}`, `${pkg.getName()}/${res.getName()}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Validate components
|
|
113
|
+
for (const comp of pkg.listComponents()) {
|
|
114
|
+
const children = comp.listChildren();
|
|
115
|
+
if (children.length === 0) {
|
|
116
|
+
issues.push({
|
|
117
|
+
severity: ValidationSeverity.INFO,
|
|
118
|
+
message: `Component "${comp.getName()}" has no children.`,
|
|
119
|
+
packageName: pkg.getName(),
|
|
120
|
+
componentName: comp.getName(),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const ctrl of comp.listControllers()) {
|
|
125
|
+
if (ctrl.listPages().length === 0) {
|
|
126
|
+
issues.push({
|
|
127
|
+
severity: ValidationSeverity.WARNING,
|
|
128
|
+
message: `Controller "${ctrl.getName()}" in "${comp.getName()}" has no pages.`,
|
|
129
|
+
packageName: pkg.getName(),
|
|
130
|
+
componentName: comp.getName(),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Validate src references in display objects
|
|
136
|
+
for (const child of children) {
|
|
137
|
+
const src = (child as HasOptionalSrc).getSrc?.();
|
|
138
|
+
if (!src) continue;
|
|
139
|
+
|
|
140
|
+
// ui://[8-char-packageId][resourceId] format
|
|
141
|
+
if (src.startsWith('ui://')) {
|
|
142
|
+
const idPart = src.slice(5);
|
|
143
|
+
if (idPart.length > 8) {
|
|
144
|
+
const pkgId = idPart.slice(0, 8);
|
|
145
|
+
const resId = idPart.slice(8);
|
|
146
|
+
const key = `${pkgId}${resId}`;
|
|
147
|
+
if (!globalResources.has(key)) {
|
|
148
|
+
issues.push({
|
|
149
|
+
severity: ValidationSeverity.ERROR,
|
|
150
|
+
message: `Broken reference "${src}" in "${child.getName()}" (component "${comp.getName()}").`,
|
|
151
|
+
packageName: pkg.getName(),
|
|
152
|
+
componentName: comp.getName(),
|
|
153
|
+
resourceName: child.getName(),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Store result
|
|
163
|
+
const errors = issues.filter((i) => i.severity === ValidationSeverity.ERROR);
|
|
164
|
+
const warnings = issues.filter((i) => i.severity === ValidationSeverity.WARNING);
|
|
165
|
+
const infos = issues.filter((i) => i.severity === ValidationSeverity.INFO);
|
|
166
|
+
|
|
167
|
+
const result: ValidationResult = {
|
|
168
|
+
ok: errors.length === 0,
|
|
169
|
+
errors,
|
|
170
|
+
warnings,
|
|
171
|
+
infos,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
root.setExtras({ ...root.getExtras(), _validation: result });
|
|
175
|
+
|
|
176
|
+
// Log summary
|
|
177
|
+
const logger = doc.getLogger();
|
|
178
|
+
if (errors.length) logger.warn(`validate: ${errors.length} error(s) found.`);
|
|
179
|
+
if (warnings.length) logger.warn(`validate: ${warnings.length} warning(s) found.`);
|
|
180
|
+
logger.info(`validate: ${issues.length} issue(s) total.`);
|
|
181
|
+
|
|
182
|
+
if (options.throwOnError && errors.length > 0) {
|
|
183
|
+
throw new Error(`Validation failed with ${errors.length} error(s):\n${errors.map((e) => ` - ${e.message}`).join('\n')}`);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|