@invarn/cibuild 2.0.7 → 2.0.9
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.cjs +688 -69
- package/dist/src/lib.d.ts +4 -0
- package/dist/src/lib.d.ts.map +1 -1
- package/dist/src/lib.js +3 -0
- package/dist/src/yaml/steps/index.d.ts.map +1 -1
- package/dist/src/yaml/steps/index.js +22 -0
- package/dist/src/yaml/steps/render-post-processor.d.ts +73 -0
- package/dist/src/yaml/steps/render-post-processor.d.ts.map +1 -0
- package/dist/src/yaml/steps/render-post-processor.js +290 -0
- package/dist/src/yaml/steps/render-post-processor.test.d.ts +16 -0
- package/dist/src/yaml/steps/render-post-processor.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/render-post-processor.test.js +194 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts +66 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +216 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts +13 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +247 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +99 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.js +152 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts +11 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-preview.test.js +168 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +92 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +726 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts +2 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +377 -0
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts +0 -28
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts.map +1 -1
- package/dist/src/yaml/steps/ui-fidelity-render.js +1 -259
- package/dist/src/yaml/steps/ui-fidelity-render.test.js +1 -57
- package/package.json +1 -1
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Android ui-fidelity render preview.
|
|
3
|
+
*
|
|
4
|
+
* The Android sibling of `renderUiFidelityPreview` (iOS). It runs the EXACT same
|
|
5
|
+
* render path the runner uses -- the generated `ui-fidelity-render-android` node
|
|
6
|
+
* script in `package_source: "inputs"` mode (archive extract/validate, one
|
|
7
|
+
* Roborazzi record task, golden location, content trim, reference alignment,
|
|
8
|
+
* dimension reporting) -- but locally, against a self-contained Gradle render
|
|
9
|
+
* project the caller already has on disk. This lets an agent eyeball the Compose
|
|
10
|
+
* wrapper in seconds before dispatching a remote run, so the remote pass becomes
|
|
11
|
+
* a one-shot authoritative render rather than the inner loop of an iterate cycle.
|
|
12
|
+
*
|
|
13
|
+
* It is a faithful mirror, not a re-implementation: the project directory is
|
|
14
|
+
* archived to a temp `package.tar.gz` and fed through the same
|
|
15
|
+
* `generateAndroidRenderScript` output, so the produced rendered/aligned images
|
|
16
|
+
* and dimensions match what a remote run would produce on the same toolchain.
|
|
17
|
+
*
|
|
18
|
+
* The result shape is identical to the iOS preview (`UiFidelityPreviewResult`),
|
|
19
|
+
* so callers (and the MCP handler) treat both platforms uniformly.
|
|
20
|
+
*/
|
|
21
|
+
import type { UiFidelityPreviewResult, UiFidelityPreviewScreen } from './ui-fidelity-preview.js';
|
|
22
|
+
export interface UiFidelityPreviewAndroidOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Local Gradle render-project directory to render. Archived to a temp
|
|
25
|
+
* package.tar.gz and rendered in inputs mode. Mutually exclusive with
|
|
26
|
+
* packageArchive.
|
|
27
|
+
*/
|
|
28
|
+
packagePath?: string;
|
|
29
|
+
/**
|
|
30
|
+
* A prebuilt package.tar.gz to render as-is (preview the exact artifact a
|
|
31
|
+
* remote run would receive). Mutually exclusive with packagePath.
|
|
32
|
+
*/
|
|
33
|
+
packageArchive?: string;
|
|
34
|
+
/** Screens to render, each mapped to a reference image path. */
|
|
35
|
+
screens: UiFidelityPreviewScreen[];
|
|
36
|
+
/**
|
|
37
|
+
* Display scale used when reporting logical points (default 2). Must match the
|
|
38
|
+
* density qualifier the render test pins (xhdpi = 2x).
|
|
39
|
+
*/
|
|
40
|
+
scale?: number | string;
|
|
41
|
+
/** Gradle record task to run (default "recordRoborazziDebug"). */
|
|
42
|
+
gradleTask?: string;
|
|
43
|
+
/** Directory to receive the artifacts; a temp dir is used when omitted. */
|
|
44
|
+
outDir?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Extra entries prepended to PATH for the render child process, so callers
|
|
47
|
+
* (and tests) can supply a specific java/gradle/swift/tar toolchain. Optional.
|
|
48
|
+
*/
|
|
49
|
+
toolchainPath?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build error returned when the local Android toolchain is absent, so the caller
|
|
53
|
+
* can fall back to a remote run instead of a confusing build failure. Generic
|
|
54
|
+
* wording on purpose -- this engine ships in the public build tooling and must
|
|
55
|
+
* not name product-specific tools.
|
|
56
|
+
*/
|
|
57
|
+
export declare const ANDROID_TOOLCHAIN_MISSING = "ANDROID_TOOLCHAIN_MISSING";
|
|
58
|
+
/**
|
|
59
|
+
* Render the requested screens locally and return the produced image paths and
|
|
60
|
+
* dimensions. Throws only on caller errors (bad options) or when the render
|
|
61
|
+
* script could not run at all; per-screen and per-build render failures (and a
|
|
62
|
+
* missing local toolchain) are reported in the returned result, mirroring the
|
|
63
|
+
* remote result document and the iOS preview engine.
|
|
64
|
+
*/
|
|
65
|
+
export declare function renderUiFidelityPreviewAndroid(options: UiFidelityPreviewAndroidOptions): UiFidelityPreviewResult;
|
|
66
|
+
//# sourceMappingURL=ui-fidelity-preview-android.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-fidelity-preview-android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsBH,OAAO,KAAK,EAEV,uBAAuB,EACvB,uBAAuB,EAExB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,+BAA+B;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AA4CrE;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,uBAAuB,CAsKzB"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Android ui-fidelity render preview.
|
|
3
|
+
*
|
|
4
|
+
* The Android sibling of `renderUiFidelityPreview` (iOS). It runs the EXACT same
|
|
5
|
+
* render path the runner uses -- the generated `ui-fidelity-render-android` node
|
|
6
|
+
* script in `package_source: "inputs"` mode (archive extract/validate, one
|
|
7
|
+
* Roborazzi record task, golden location, content trim, reference alignment,
|
|
8
|
+
* dimension reporting) -- but locally, against a self-contained Gradle render
|
|
9
|
+
* project the caller already has on disk. This lets an agent eyeball the Compose
|
|
10
|
+
* wrapper in seconds before dispatching a remote run, so the remote pass becomes
|
|
11
|
+
* a one-shot authoritative render rather than the inner loop of an iterate cycle.
|
|
12
|
+
*
|
|
13
|
+
* It is a faithful mirror, not a re-implementation: the project directory is
|
|
14
|
+
* archived to a temp `package.tar.gz` and fed through the same
|
|
15
|
+
* `generateAndroidRenderScript` output, so the produced rendered/aligned images
|
|
16
|
+
* and dimensions match what a remote run would produce on the same toolchain.
|
|
17
|
+
*
|
|
18
|
+
* The result shape is identical to the iOS preview (`UiFidelityPreviewResult`),
|
|
19
|
+
* so callers (and the MCP handler) treat both platforms uniformly.
|
|
20
|
+
*/
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync, } from 'node:fs';
|
|
23
|
+
import { tmpdir } from 'node:os';
|
|
24
|
+
import { basename, join, resolve } from 'node:path';
|
|
25
|
+
import { parseScale, DEFAULT_SCALE } from './ui-fidelity-render.js';
|
|
26
|
+
import { DEFAULT_GRADLE_TASK, PACKAGE_ARCHIVE_BASENAME, generateAndroidRenderScript, isValidGradleTask, } from './ui-fidelity-render-android.js';
|
|
27
|
+
/**
|
|
28
|
+
* Build error returned when the local Android toolchain is absent, so the caller
|
|
29
|
+
* can fall back to a remote run instead of a confusing build failure. Generic
|
|
30
|
+
* wording on purpose -- this engine ships in the public build tooling and must
|
|
31
|
+
* not name product-specific tools.
|
|
32
|
+
*/
|
|
33
|
+
export const ANDROID_TOOLCHAIN_MISSING = 'ANDROID_TOOLCHAIN_MISSING';
|
|
34
|
+
function runTar(args, pathPrepend) {
|
|
35
|
+
const env = pathPrepend
|
|
36
|
+
? { ...process.env, PATH: pathPrepend + ':' + (process.env.PATH ?? '') }
|
|
37
|
+
: process.env;
|
|
38
|
+
const result = spawnSync('tar', args, { encoding: 'utf-8', env, maxBuffer: 64 * 1024 * 1024 });
|
|
39
|
+
if (result.status !== 0) {
|
|
40
|
+
const detail = (result.stderr || result.error?.message || '').toString().trim();
|
|
41
|
+
throw new Error(`failed to archive the project (tar exited ${result.status}): ${detail}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** True if `command` resolves on the (optionally prepended) PATH. */
|
|
45
|
+
function commandExists(command, pathPrepend) {
|
|
46
|
+
const env = pathPrepend
|
|
47
|
+
? { ...process.env, PATH: pathPrepend + ':' + (process.env.PATH ?? '') }
|
|
48
|
+
: process.env;
|
|
49
|
+
const probe = process.platform === 'win32'
|
|
50
|
+
? spawnSync('where', [command], { encoding: 'utf-8', env })
|
|
51
|
+
: spawnSync(`command -v ${command}`, { encoding: 'utf-8', env, shell: true });
|
|
52
|
+
return probe.status === 0 && (probe.stdout || '').trim() !== '';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Report which env-level Android prerequisites are missing: the JVM (`java`) and
|
|
56
|
+
* the SDK (`ANDROID_HOME`/`ANDROID_SDK_ROOT`). Gradle itself ships inside the
|
|
57
|
+
* render project (gradlew), so it is not an env-level prerequisite. Returns an
|
|
58
|
+
* empty array when the toolchain is present.
|
|
59
|
+
*/
|
|
60
|
+
function missingToolchain(pathPrepend) {
|
|
61
|
+
const missing = [];
|
|
62
|
+
if (!commandExists('java', pathPrepend))
|
|
63
|
+
missing.push('a JDK (java not on PATH)');
|
|
64
|
+
const sdk = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT || '';
|
|
65
|
+
if (!sdk || !existsSync(sdk))
|
|
66
|
+
missing.push('the Android SDK (ANDROID_HOME not set)');
|
|
67
|
+
return missing;
|
|
68
|
+
}
|
|
69
|
+
/** Resolve an artifact-relative path to absolute, or null. */
|
|
70
|
+
function absOrNull(artifactsDir, relative) {
|
|
71
|
+
return typeof relative === 'string' && relative !== '' ? resolve(artifactsDir, relative) : null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Render the requested screens locally and return the produced image paths and
|
|
75
|
+
* dimensions. Throws only on caller errors (bad options) or when the render
|
|
76
|
+
* script could not run at all; per-screen and per-build render failures (and a
|
|
77
|
+
* missing local toolchain) are reported in the returned result, mirroring the
|
|
78
|
+
* remote result document and the iOS preview engine.
|
|
79
|
+
*/
|
|
80
|
+
export function renderUiFidelityPreviewAndroid(options) {
|
|
81
|
+
if (!options || typeof options !== 'object') {
|
|
82
|
+
throw new Error('renderUiFidelityPreviewAndroid: options are required');
|
|
83
|
+
}
|
|
84
|
+
const hasDir = typeof options.packagePath === 'string' && options.packagePath !== '';
|
|
85
|
+
const hasArchive = typeof options.packageArchive === 'string' && options.packageArchive !== '';
|
|
86
|
+
if (hasDir === hasArchive) {
|
|
87
|
+
throw new Error('renderUiFidelityPreviewAndroid: provide exactly one of packagePath or packageArchive');
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(options.screens) || options.screens.length === 0) {
|
|
90
|
+
throw new Error('renderUiFidelityPreviewAndroid: at least one screen is required');
|
|
91
|
+
}
|
|
92
|
+
const scale = parseScale(options.scale ?? DEFAULT_SCALE);
|
|
93
|
+
const gradleTask = String(options.gradleTask ?? DEFAULT_GRADLE_TASK);
|
|
94
|
+
if (!isValidGradleTask(gradleTask)) {
|
|
95
|
+
throw new Error(`renderUiFidelityPreviewAndroid: invalid gradleTask '${gradleTask}' ` +
|
|
96
|
+
'(expected a Gradle task path: letters, digits, and : - _)');
|
|
97
|
+
}
|
|
98
|
+
const workDir = mkdtempSync(join(tmpdir(), 'ui-fidelity-android-preview-'));
|
|
99
|
+
const inputsDir = join(workDir, '.ci', 'inputs');
|
|
100
|
+
mkdirSync(inputsDir, { recursive: true });
|
|
101
|
+
// Stage references by basename and build the screens -> basename map, exactly
|
|
102
|
+
// as a remote caller would pass params.screens.
|
|
103
|
+
const screensParam = {};
|
|
104
|
+
for (const entry of options.screens) {
|
|
105
|
+
if (!entry || typeof entry.screen !== 'string' || entry.screen === '') {
|
|
106
|
+
throw new Error('renderUiFidelityPreviewAndroid: each screen needs a non-empty screen name');
|
|
107
|
+
}
|
|
108
|
+
if (typeof entry.reference !== 'string' || entry.reference === '') {
|
|
109
|
+
throw new Error(`renderUiFidelityPreviewAndroid: screen ${entry.screen} needs a reference path`);
|
|
110
|
+
}
|
|
111
|
+
if (!existsSync(entry.reference) || !statSync(entry.reference).isFile()) {
|
|
112
|
+
throw new Error(`renderUiFidelityPreviewAndroid: reference for ${entry.screen} not found: ${entry.reference}`);
|
|
113
|
+
}
|
|
114
|
+
const base = basename(entry.reference);
|
|
115
|
+
copyFileSync(entry.reference, join(inputsDir, base));
|
|
116
|
+
screensParam[entry.screen] = base;
|
|
117
|
+
}
|
|
118
|
+
const artifactsDir = options.outDir
|
|
119
|
+
? resolve(options.outDir)
|
|
120
|
+
: join(workDir, '.ci', 'artifacts');
|
|
121
|
+
mkdirSync(artifactsDir, { recursive: true });
|
|
122
|
+
// A render is a heavy Gradle/Robolectric build; if the dev machine has no
|
|
123
|
+
// local Android toolchain at all, fall back to a remote run with a clear
|
|
124
|
+
// message instead of letting the build fail confusingly.
|
|
125
|
+
const missing = missingToolchain(options.toolchainPath);
|
|
126
|
+
if (missing.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
packageSource: 'inputs',
|
|
129
|
+
buildError: {
|
|
130
|
+
code: ANDROID_TOOLCHAIN_MISSING,
|
|
131
|
+
message: 'the local Android toolchain is unavailable (' +
|
|
132
|
+
missing.join(', ') +
|
|
133
|
+
'); render this Compose wrapper on a runner instead of locally',
|
|
134
|
+
},
|
|
135
|
+
screens: [],
|
|
136
|
+
artifactsDir,
|
|
137
|
+
ok: false,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const archivePath = join(inputsDir, PACKAGE_ARCHIVE_BASENAME);
|
|
141
|
+
if (hasArchive) {
|
|
142
|
+
const src = options.packageArchive;
|
|
143
|
+
if (!existsSync(src) || !statSync(src).isFile()) {
|
|
144
|
+
throw new Error(`renderUiFidelityPreviewAndroid: packageArchive not found: ${src}`);
|
|
145
|
+
}
|
|
146
|
+
copyFileSync(src, archivePath);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const dir = options.packagePath;
|
|
150
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory()) {
|
|
151
|
+
throw new Error(`renderUiFidelityPreviewAndroid: packagePath is not a directory: ${dir}`);
|
|
152
|
+
}
|
|
153
|
+
// Archive with the settings script at the archive root; exclude Gradle/IDE/VCS
|
|
154
|
+
// build noise so the upload stays lean and matches what a careful caller would
|
|
155
|
+
// ship. gradlew is kept so the runner uses the pinned wrapper.
|
|
156
|
+
runTar([
|
|
157
|
+
'--exclude',
|
|
158
|
+
'./build',
|
|
159
|
+
'--exclude',
|
|
160
|
+
'*/build',
|
|
161
|
+
'--exclude',
|
|
162
|
+
'./.gradle',
|
|
163
|
+
'--exclude',
|
|
164
|
+
'*/.gradle',
|
|
165
|
+
'--exclude',
|
|
166
|
+
'.git',
|
|
167
|
+
'--exclude',
|
|
168
|
+
'.idea',
|
|
169
|
+
'--exclude',
|
|
170
|
+
'.DS_Store',
|
|
171
|
+
'--exclude',
|
|
172
|
+
'local.properties',
|
|
173
|
+
'-czf',
|
|
174
|
+
archivePath,
|
|
175
|
+
'-C',
|
|
176
|
+
dir,
|
|
177
|
+
'.',
|
|
178
|
+
], options.toolchainPath);
|
|
179
|
+
}
|
|
180
|
+
// The standard params.json the render script reads: screen -> reference basename.
|
|
181
|
+
writeFileSync(join(inputsDir, 'params.json'), JSON.stringify({ screens: screensParam }, null, 2) + '\n');
|
|
182
|
+
const script = generateAndroidRenderScript({ scale, gradleTask });
|
|
183
|
+
const env = {
|
|
184
|
+
...process.env,
|
|
185
|
+
CIBUILD_ARTIFACTS_DIR: artifactsDir,
|
|
186
|
+
};
|
|
187
|
+
if (options.toolchainPath) {
|
|
188
|
+
env.PATH = options.toolchainPath + ':' + (process.env.PATH ?? '');
|
|
189
|
+
}
|
|
190
|
+
const run = spawnSync('node', ['-e', script], {
|
|
191
|
+
cwd: workDir,
|
|
192
|
+
env,
|
|
193
|
+
encoding: 'utf-8',
|
|
194
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
195
|
+
});
|
|
196
|
+
const resultPath = join(artifactsDir, 'protocol-result.json');
|
|
197
|
+
if (!existsSync(resultPath)) {
|
|
198
|
+
const detail = ((run.stdout || '') + '\n' + (run.stderr || '')).trim();
|
|
199
|
+
throw new Error(`renderUiFidelityPreviewAndroid: render script produced no result document` +
|
|
200
|
+
(detail ? `:\n${detail}` : ''));
|
|
201
|
+
}
|
|
202
|
+
const raw = JSON.parse(readFileSync(resultPath, 'utf-8'));
|
|
203
|
+
const screens = (raw.screens || []).map((s) => ({
|
|
204
|
+
screen: s.screen,
|
|
205
|
+
status: s.status,
|
|
206
|
+
error: s.error ?? null,
|
|
207
|
+
referenceImagePath: absOrNull(artifactsDir, s.reference_image_path),
|
|
208
|
+
renderedImagePath: absOrNull(artifactsDir, s.rendered_image_path),
|
|
209
|
+
alignedImagePath: absOrNull(artifactsDir, s.aligned_image_path),
|
|
210
|
+
dimensions: s.dimensions ?? null,
|
|
211
|
+
}));
|
|
212
|
+
const buildError = raw.error ?? null;
|
|
213
|
+
const ok = buildError === null && screens.length > 0 && screens.every((s) => s.status === 'rendered');
|
|
214
|
+
return { packageSource: 'inputs', buildError, screens, artifactsDir, ok };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=ui-fidelity-preview-android.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the local Android ui-fidelity render preview engine.
|
|
3
|
+
*
|
|
4
|
+
* The orchestration (archive, params, result shaping) and the toolchain-missing
|
|
5
|
+
* guard run with a PATH-shimmed fake `java`/`gradlew`/`swift` toolchain: the
|
|
6
|
+
* engine spawns the real generated Android render script, which spawns the
|
|
7
|
+
* toolchain. The actual Roborazzi/Robolectric render and the CoreGraphics trim
|
|
8
|
+
* are covered by an opt-in real-toolchain test, gated on
|
|
9
|
+
* CIBUILD_UI_FIDELITY_REAL_ANDROID, since the fake toolchain cannot exercise the
|
|
10
|
+
* real pixel render/trim.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ui-fidelity-preview-android.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-fidelity-preview-android.test.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview-android.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the local Android ui-fidelity render preview engine.
|
|
3
|
+
*
|
|
4
|
+
* The orchestration (archive, params, result shaping) and the toolchain-missing
|
|
5
|
+
* guard run with a PATH-shimmed fake `java`/`gradlew`/`swift` toolchain: the
|
|
6
|
+
* engine spawns the real generated Android render script, which spawns the
|
|
7
|
+
* toolchain. The actual Roborazzi/Robolectric render and the CoreGraphics trim
|
|
8
|
+
* are covered by an opt-in real-toolchain test, gated on
|
|
9
|
+
* CIBUILD_UI_FIDELITY_REAL_ANDROID, since the fake toolchain cannot exercise the
|
|
10
|
+
* real pixel render/trim.
|
|
11
|
+
*/
|
|
12
|
+
import { describe, expect, test, afterAll } from '@jest/globals';
|
|
13
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { renderUiFidelityPreviewAndroid } from './ui-fidelity-preview-android.js';
|
|
18
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
19
|
+
// Fake `java`: presence is all the toolchain guard checks (command -v java).
|
|
20
|
+
const FAKE_JAVA = ['#!/bin/bash', 'exit 0'].join('\n');
|
|
21
|
+
// Fake project `gradlew`: mimics a Roborazzi record by writing one PNG per
|
|
22
|
+
// screen named in FAKE_ROBORAZZI_SCREENS (comma-separated) to
|
|
23
|
+
// proof/build/outputs/roborazzi/<Screen>.png.
|
|
24
|
+
const FAKE_GRADLEW = [
|
|
25
|
+
'#!/bin/bash',
|
|
26
|
+
'out="proof/build/outputs/roborazzi"',
|
|
27
|
+
'mkdir -p "$out"',
|
|
28
|
+
'IFS=","',
|
|
29
|
+
'for s in $FAKE_ROBORAZZI_SCREENS; do',
|
|
30
|
+
' [ -z "$s" ] && continue',
|
|
31
|
+
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out/$s.png\"",
|
|
32
|
+
' printf \'roborazzi:%s\' "$s" >> "$out/$s.png"',
|
|
33
|
+
'done',
|
|
34
|
+
'exit 0',
|
|
35
|
+
].join('\n');
|
|
36
|
+
// Fake `swift`: only the script-interpreter branch the shared trim stage uses
|
|
37
|
+
// (`swift <Trim.swift> <in> <out> <aligned> <ref>`). Writes trimmed + aligned
|
|
38
|
+
// PNGs and emits a fixed TRIMDIMS line.
|
|
39
|
+
const FAKE_SWIFT = [
|
|
40
|
+
'#!/bin/bash',
|
|
41
|
+
'case "$1" in',
|
|
42
|
+
' *.swift)',
|
|
43
|
+
' in_png="$2"; out_png="$3"; aligned_png="$4"',
|
|
44
|
+
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out_png\"",
|
|
45
|
+
' printf \'trimmed:%s\' "$(basename "$in_png")" >> "$out_png"',
|
|
46
|
+
' if [ -n "$aligned_png" ]; then',
|
|
47
|
+
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$aligned_png\"",
|
|
48
|
+
' printf \'aligned:%s\' "$(basename "$in_png")" >> "$aligned_png"',
|
|
49
|
+
' fi',
|
|
50
|
+
' echo "TRIMDIMS 320 640 160 320 400 800"',
|
|
51
|
+
' exit 0 ;;',
|
|
52
|
+
' *) exit 0 ;;',
|
|
53
|
+
'esac',
|
|
54
|
+
].join('\n');
|
|
55
|
+
function fakeToolchain() {
|
|
56
|
+
const bin = tempDir('ui-fidelity-android-fakebin-');
|
|
57
|
+
for (const [name, body] of [
|
|
58
|
+
['java', FAKE_JAVA],
|
|
59
|
+
['swift', FAKE_SWIFT],
|
|
60
|
+
]) {
|
|
61
|
+
const p = join(bin, name);
|
|
62
|
+
writeFileSync(p, body + '\n');
|
|
63
|
+
chmodSync(p, 0o755);
|
|
64
|
+
}
|
|
65
|
+
return bin;
|
|
66
|
+
}
|
|
67
|
+
function isPng(filePath) {
|
|
68
|
+
return readFileSync(filePath).subarray(0, PNG_MAGIC.length).equals(PNG_MAGIC);
|
|
69
|
+
}
|
|
70
|
+
const tempDirs = [];
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
for (const dir of tempDirs) {
|
|
73
|
+
try {
|
|
74
|
+
rmSync(dir, { recursive: true, force: true });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// best effort
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
function tempDir(prefix) {
|
|
82
|
+
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
83
|
+
tempDirs.push(dir);
|
|
84
|
+
return dir;
|
|
85
|
+
}
|
|
86
|
+
function writeReference() {
|
|
87
|
+
const dir = tempDir('ui-fidelity-android-ref-');
|
|
88
|
+
const ref = join(dir, 'reference.png');
|
|
89
|
+
writeFileSync(ref, Buffer.concat([PNG_MAGIC, Buffer.from('ref')]));
|
|
90
|
+
return ref;
|
|
91
|
+
}
|
|
92
|
+
/** A minimal Gradle render-project directory with a fake gradlew. */
|
|
93
|
+
function gradleProject() {
|
|
94
|
+
const dir = tempDir('ui-fidelity-android-pkg-');
|
|
95
|
+
writeFileSync(join(dir, 'settings.gradle.kts'), 'rootProject.name = "p"\ninclude(":proof")\n');
|
|
96
|
+
mkdirSync(join(dir, 'proof'), { recursive: true });
|
|
97
|
+
writeFileSync(join(dir, 'proof', 'build.gradle.kts'), '// module\n');
|
|
98
|
+
const gradlew = join(dir, 'gradlew');
|
|
99
|
+
writeFileSync(gradlew, FAKE_GRADLEW + '\n');
|
|
100
|
+
chmodSync(gradlew, 0o755);
|
|
101
|
+
return dir;
|
|
102
|
+
}
|
|
103
|
+
/** Run `fn` with ANDROID_HOME and the named screens set for the fake gradlew. */
|
|
104
|
+
function withAndroidEnv(screens, fn) {
|
|
105
|
+
const home = tempDir('ui-fidelity-android-sdk-');
|
|
106
|
+
const saved = {
|
|
107
|
+
home: process.env.ANDROID_HOME,
|
|
108
|
+
root: process.env.ANDROID_SDK_ROOT,
|
|
109
|
+
screens: process.env.FAKE_ROBORAZZI_SCREENS,
|
|
110
|
+
};
|
|
111
|
+
process.env.ANDROID_HOME = home;
|
|
112
|
+
delete process.env.ANDROID_SDK_ROOT;
|
|
113
|
+
process.env.FAKE_ROBORAZZI_SCREENS = screens;
|
|
114
|
+
try {
|
|
115
|
+
return fn();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
for (const [k, v] of [
|
|
119
|
+
['ANDROID_HOME', saved.home],
|
|
120
|
+
['ANDROID_SDK_ROOT', saved.root],
|
|
121
|
+
['FAKE_ROBORAZZI_SCREENS', saved.screens],
|
|
122
|
+
]) {
|
|
123
|
+
if (v === undefined)
|
|
124
|
+
delete process.env[k];
|
|
125
|
+
else
|
|
126
|
+
process.env[k] = v;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
describe('renderUiFidelityPreviewAndroid — option validation', () => {
|
|
131
|
+
test('requires exactly one of packagePath or packageArchive', () => {
|
|
132
|
+
expect(() => renderUiFidelityPreviewAndroid({ screens: [] })).toThrow(/exactly one/);
|
|
133
|
+
expect(() => renderUiFidelityPreviewAndroid({
|
|
134
|
+
packagePath: gradleProject(),
|
|
135
|
+
packageArchive: '/tmp/x.tar.gz',
|
|
136
|
+
screens: [{ screen: 'XProof', reference: writeReference() }],
|
|
137
|
+
})).toThrow(/exactly one/);
|
|
138
|
+
});
|
|
139
|
+
test('requires at least one screen', () => {
|
|
140
|
+
expect(() => renderUiFidelityPreviewAndroid({ packagePath: gradleProject(), screens: [] })).toThrow(/at least one screen/);
|
|
141
|
+
});
|
|
142
|
+
test('rejects a missing reference image', () => {
|
|
143
|
+
expect(() => renderUiFidelityPreviewAndroid({
|
|
144
|
+
packagePath: gradleProject(),
|
|
145
|
+
screens: [{ screen: 'XProof', reference: '/no/such/reference.png' }],
|
|
146
|
+
})).toThrow(/reference for XProof not found/);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('renderUiFidelityPreviewAndroid — fake toolchain', () => {
|
|
150
|
+
test('archives the project, renders in inputs mode, and returns image paths + dims', () => {
|
|
151
|
+
const result = withAndroidEnv('XProof', () => renderUiFidelityPreviewAndroid({
|
|
152
|
+
packagePath: gradleProject(),
|
|
153
|
+
screens: [{ screen: 'XProof', reference: writeReference() }],
|
|
154
|
+
toolchainPath: fakeToolchain(),
|
|
155
|
+
}));
|
|
156
|
+
expect(result.packageSource).toBe('inputs');
|
|
157
|
+
expect(result.buildError).toBeNull();
|
|
158
|
+
expect(result.ok).toBe(true);
|
|
159
|
+
expect(result.screens).toHaveLength(1);
|
|
160
|
+
const [screen] = result.screens;
|
|
161
|
+
expect(screen.screen).toBe('XProof');
|
|
162
|
+
expect(screen.status).toBe('rendered');
|
|
163
|
+
expect(screen.error).toBeNull();
|
|
164
|
+
// Paths are absolute and point at real files the caller can read.
|
|
165
|
+
expect(screen.renderedImagePath && isPng(screen.renderedImagePath)).toBe(true);
|
|
166
|
+
expect(screen.alignedImagePath && isPng(screen.alignedImagePath)).toBe(true);
|
|
167
|
+
expect(screen.referenceImagePath && isPng(screen.referenceImagePath)).toBe(true);
|
|
168
|
+
// logical_pt = rendered_px / scale (default 2); reference from the fake trim.
|
|
169
|
+
expect(screen.dimensions).toEqual({
|
|
170
|
+
rendered_px: [320, 640],
|
|
171
|
+
logical_pt: [160, 320],
|
|
172
|
+
reference_px: [160, 320],
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
test('writes images under the requested outDir', () => {
|
|
176
|
+
const outDir = tempDir('ui-fidelity-android-out-');
|
|
177
|
+
const result = withAndroidEnv('XProof', () => renderUiFidelityPreviewAndroid({
|
|
178
|
+
packagePath: gradleProject(),
|
|
179
|
+
screens: [{ screen: 'XProof', reference: writeReference() }],
|
|
180
|
+
outDir,
|
|
181
|
+
toolchainPath: fakeToolchain(),
|
|
182
|
+
}));
|
|
183
|
+
expect(result.artifactsDir).toBe(outDir);
|
|
184
|
+
expect(result.screens[0].renderedImagePath?.startsWith(outDir)).toBe(true);
|
|
185
|
+
expect(existsSync(join(outDir, 'ui-fidelity', 'rendered', 'XProof.png'))).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('renderUiFidelityPreviewAndroid — missing local toolchain', () => {
|
|
189
|
+
test('reports a clear toolchain-missing build error instead of crashing', () => {
|
|
190
|
+
const savedHome = process.env.ANDROID_HOME;
|
|
191
|
+
const savedRoot = process.env.ANDROID_SDK_ROOT;
|
|
192
|
+
delete process.env.ANDROID_HOME;
|
|
193
|
+
delete process.env.ANDROID_SDK_ROOT;
|
|
194
|
+
try {
|
|
195
|
+
const result = renderUiFidelityPreviewAndroid({
|
|
196
|
+
packagePath: gradleProject(),
|
|
197
|
+
screens: [{ screen: 'XProof', reference: writeReference() }],
|
|
198
|
+
});
|
|
199
|
+
expect(result.ok).toBe(false);
|
|
200
|
+
expect(result.buildError?.code).toBe('ANDROID_TOOLCHAIN_MISSING');
|
|
201
|
+
expect(result.buildError?.message).toMatch(/Android SDK|JDK/);
|
|
202
|
+
// Degrades cleanly: it never tried to render.
|
|
203
|
+
expect(result.screens).toEqual([]);
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
if (savedHome === undefined)
|
|
207
|
+
delete process.env.ANDROID_HOME;
|
|
208
|
+
else
|
|
209
|
+
process.env.ANDROID_HOME = savedHome;
|
|
210
|
+
if (savedRoot === undefined)
|
|
211
|
+
delete process.env.ANDROID_SDK_ROOT;
|
|
212
|
+
else
|
|
213
|
+
process.env.ANDROID_SDK_ROOT = savedRoot;
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
// Opt-in: previews the committed Roborazzi fixture with the real Android
|
|
218
|
+
// toolchain and asserts a non-blank paired render at the expected size. Requires
|
|
219
|
+
// the Android toolchain (JDK + SDK) and warm Roborazzi caches; gated behind
|
|
220
|
+
// CIBUILD_UI_FIDELITY_REAL_ANDROID=1 (the Android analog of the iOS real-Swift
|
|
221
|
+
// gate). It needs ANDROID_HOME set on the dev machine.
|
|
222
|
+
describe('renderUiFidelityPreviewAndroid — real toolchain', () => {
|
|
223
|
+
const realAndroidTest = process.env.CIBUILD_UI_FIDELITY_REAL_ANDROID === '1' ? test : test.skip;
|
|
224
|
+
// A real, decodable 1x1 PNG so the trim helper's reference load/align succeeds.
|
|
225
|
+
const ONE_BY_ONE_PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', 'base64');
|
|
226
|
+
const FIXTURE_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../cibuild-runner/scripts/roborazzi-fixture');
|
|
227
|
+
realAndroidTest('previews the Roborazzi fixture and returns a paired, non-blank render', () => {
|
|
228
|
+
const refDir = tempDir('ui-fidelity-android-realref-');
|
|
229
|
+
const reference = join(refDir, 'reference.png');
|
|
230
|
+
writeFileSync(reference, ONE_BY_ONE_PNG);
|
|
231
|
+
const result = renderUiFidelityPreviewAndroid({
|
|
232
|
+
packagePath: FIXTURE_DIR,
|
|
233
|
+
screens: [{ screen: 'XProof', reference }],
|
|
234
|
+
scale: 2,
|
|
235
|
+
});
|
|
236
|
+
expect(result.buildError).toBeNull();
|
|
237
|
+
expect(result.ok).toBe(true);
|
|
238
|
+
const [screen] = result.screens;
|
|
239
|
+
expect(screen.status).toBe('rendered');
|
|
240
|
+
expect(screen.renderedImagePath && isPng(screen.renderedImagePath)).toBe(true);
|
|
241
|
+
expect(screen.alignedImagePath && isPng(screen.alignedImagePath)).toBe(true);
|
|
242
|
+
const [rw, rh] = screen.dimensions.rendered_px;
|
|
243
|
+
expect(rw).toBeGreaterThan(0);
|
|
244
|
+
expect(rh).toBeGreaterThan(0);
|
|
245
|
+
}, 600_000);
|
|
246
|
+
});
|
|
247
|
+
//# sourceMappingURL=ui-fidelity-preview-android.test.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local ui-fidelity render preview.
|
|
3
|
+
*
|
|
4
|
+
* Runs the EXACT same render path the runner uses -- the generated
|
|
5
|
+
* `ui-fidelity-render` node script in `package_source: "inputs"` mode (harness
|
|
6
|
+
* synthesis, asset-catalog compile, content trim, reference alignment, dimension
|
|
7
|
+
* reporting) -- but locally, against a SwiftPM package the caller already has on
|
|
8
|
+
* disk. This lets an agent eyeball the wrapper in seconds before dispatching a
|
|
9
|
+
* remote run, so the remote pass becomes a one-shot authoritative render rather
|
|
10
|
+
* than the inner loop of an iterate cycle.
|
|
11
|
+
*
|
|
12
|
+
* It is a faithful mirror, not a re-implementation: the package directory is
|
|
13
|
+
* archived to a temp `package.tar.gz` and fed through the same
|
|
14
|
+
* `generateRenderScript` output, so the target is auto-discovered, the archive
|
|
15
|
+
* is validated, and the produced rendered/aligned images and dimensions match
|
|
16
|
+
* what a remote run would produce on the same toolchain.
|
|
17
|
+
*/
|
|
18
|
+
/** One wrapper view to render, paired with the reference image to align to. */
|
|
19
|
+
export interface UiFidelityPreviewScreen {
|
|
20
|
+
/** Wrapper view type name (e.g. the public, parameterless `XProof`). */
|
|
21
|
+
screen: string;
|
|
22
|
+
/** Path to the reference image; copied into the inputs by basename. */
|
|
23
|
+
reference: string;
|
|
24
|
+
}
|
|
25
|
+
export interface UiFidelityPreviewOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Local SwiftPM package directory to render. Archived to a temp
|
|
28
|
+
* package.tar.gz and rendered in inputs mode. Mutually exclusive with
|
|
29
|
+
* packageArchive.
|
|
30
|
+
*/
|
|
31
|
+
packagePath?: string;
|
|
32
|
+
/**
|
|
33
|
+
* A prebuilt package.tar.gz to render as-is (preview the exact artifact a
|
|
34
|
+
* remote run would receive). Mutually exclusive with packagePath.
|
|
35
|
+
*/
|
|
36
|
+
packageArchive?: string;
|
|
37
|
+
/** Screens to render, each mapped to a reference image path. */
|
|
38
|
+
screens: UiFidelityPreviewScreen[];
|
|
39
|
+
/**
|
|
40
|
+
* SPM library target to import. Auto-discovered from the shipped manifest
|
|
41
|
+
* when omitted (the package must declare exactly one library product).
|
|
42
|
+
*/
|
|
43
|
+
target?: string;
|
|
44
|
+
/** Render size in device points, "<width>x<height>" (default 393x852). */
|
|
45
|
+
renderSize?: string;
|
|
46
|
+
/** Display scale (default 2). */
|
|
47
|
+
scale?: number | string;
|
|
48
|
+
/** Directory to receive the artifacts; a temp dir is used when omitted. */
|
|
49
|
+
outDir?: string;
|
|
50
|
+
/**
|
|
51
|
+
* Extra entries prepended to PATH for the render child process, so callers
|
|
52
|
+
* (and tests) can supply a specific swift/xcrun/tar toolchain. Optional.
|
|
53
|
+
*/
|
|
54
|
+
toolchainPath?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface UiFidelityPreviewDimensions {
|
|
57
|
+
rendered_px: [number, number];
|
|
58
|
+
logical_pt: [number, number];
|
|
59
|
+
reference_px: [number, number] | null;
|
|
60
|
+
}
|
|
61
|
+
export interface UiFidelityPreviewScreenResult {
|
|
62
|
+
screen: string;
|
|
63
|
+
/** "rendered" when the screen rendered; otherwise "render_failed". */
|
|
64
|
+
status: string;
|
|
65
|
+
error: {
|
|
66
|
+
code: string;
|
|
67
|
+
message: string;
|
|
68
|
+
} | null;
|
|
69
|
+
/** Absolute path to the (per-screen) reference copy, or null. */
|
|
70
|
+
referenceImagePath: string | null;
|
|
71
|
+
/** Absolute path to the trimmed rendered image, or null. */
|
|
72
|
+
renderedImagePath: string | null;
|
|
73
|
+
/** Absolute path to the reference-aligned image, or null. */
|
|
74
|
+
alignedImagePath: string | null;
|
|
75
|
+
dimensions: UiFidelityPreviewDimensions | null;
|
|
76
|
+
}
|
|
77
|
+
export interface UiFidelityPreviewResult {
|
|
78
|
+
/** Always "inputs": the preview mirrors a shipped-package remote run. */
|
|
79
|
+
packageSource: 'inputs';
|
|
80
|
+
/** Per-build package error (archive/target validation), or null. */
|
|
81
|
+
buildError: {
|
|
82
|
+
code: string;
|
|
83
|
+
message: string;
|
|
84
|
+
} | null;
|
|
85
|
+
/** One entry per requested screen, in request order. */
|
|
86
|
+
screens: UiFidelityPreviewScreenResult[];
|
|
87
|
+
/** Absolute path to the artifacts directory holding the images. */
|
|
88
|
+
artifactsDir: string;
|
|
89
|
+
/** True only when every screen rendered and there was no build error. */
|
|
90
|
+
ok: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Render the requested screens locally and return the produced image paths and
|
|
94
|
+
* dimensions. Throws only on caller errors (bad options) or when the render
|
|
95
|
+
* script could not run at all; per-screen and per-build render failures are
|
|
96
|
+
* reported in the returned result (mirroring the remote result document).
|
|
97
|
+
*/
|
|
98
|
+
export declare function renderUiFidelityPreview(options: UiFidelityPreviewOptions): UiFidelityPreviewResult;
|
|
99
|
+
//# sourceMappingURL=ui-fidelity-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-fidelity-preview.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-preview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAwBH,+EAA+E;AAC/E,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,4DAA4D;IAC5D,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,uBAAuB;IACtC,yEAAyE;IACzE,aAAa,EAAE,QAAQ,CAAC;IACxB,oEAAoE;IACpE,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,wDAAwD;IACxD,OAAO,EAAE,6BAA6B,EAAE,CAAC;IACzC,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,EAAE,EAAE,OAAO,CAAC;CACb;AAkCD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CAoIzB"}
|