@vizzly-testing/cli 0.13.4 → 0.15.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/dist/cli.js +68 -68
- package/dist/commands/doctor.js +30 -34
- package/dist/commands/finalize.js +24 -23
- package/dist/commands/init.js +30 -28
- package/dist/commands/login.js +49 -55
- package/dist/commands/logout.js +14 -19
- package/dist/commands/project.js +83 -103
- package/dist/commands/run.js +77 -89
- package/dist/commands/status.js +48 -49
- package/dist/commands/tdd-daemon.js +90 -86
- package/dist/commands/tdd.js +59 -88
- package/dist/commands/upload.js +57 -57
- package/dist/commands/whoami.js +40 -45
- package/dist/index.js +2 -5
- package/dist/plugin-loader.js +15 -17
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +78 -32
- package/dist/sdk/index.js +36 -45
- package/dist/server/handlers/api-handler.js +14 -15
- package/dist/server/handlers/tdd-handler.js +34 -37
- package/dist/server/http-server.js +75 -869
- package/dist/server/middleware/cors.js +22 -0
- package/dist/server/middleware/json-parser.js +35 -0
- package/dist/server/middleware/response.js +79 -0
- package/dist/server/routers/assets.js +91 -0
- package/dist/server/routers/auth.js +144 -0
- package/dist/server/routers/baseline.js +163 -0
- package/dist/server/routers/cloud-proxy.js +146 -0
- package/dist/server/routers/config.js +126 -0
- package/dist/server/routers/dashboard.js +130 -0
- package/dist/server/routers/health.js +61 -0
- package/dist/server/routers/projects.js +168 -0
- package/dist/server/routers/screenshot.js +86 -0
- package/dist/services/auth-service.js +1 -1
- package/dist/services/build-manager.js +13 -40
- package/dist/services/config-service.js +2 -4
- package/dist/services/html-report-generator.js +6 -5
- package/dist/services/index.js +64 -0
- package/dist/services/project-service.js +121 -40
- package/dist/services/screenshot-server.js +9 -9
- package/dist/services/server-manager.js +11 -18
- package/dist/services/static-report-generator.js +3 -4
- package/dist/services/tdd-service.js +246 -103
- package/dist/services/test-runner.js +24 -25
- package/dist/services/uploader.js +5 -4
- package/dist/types/commands/init.d.ts +1 -2
- package/dist/types/index.d.ts +2 -3
- package/dist/types/plugin-loader.d.ts +1 -2
- package/dist/types/reporter/src/api/client.d.ts +178 -0
- package/dist/types/reporter/src/components/app-router.d.ts +1 -3
- package/dist/types/reporter/src/components/code-block.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/fullscreen-viewer.d.ts +13 -0
- package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
- package/dist/types/reporter/src/components/comparison/screenshot-list.d.ts +9 -0
- package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +1 -1
- package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
- package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
- package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
- package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
- package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
- package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
- package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
- package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
- package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
- package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
- package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
- package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +5 -0
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +5 -6
- package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
- package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
- package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
- package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
- package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
- package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
- package/dist/types/sdk/index.d.ts +2 -4
- package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/server/middleware/cors.d.ts +11 -0
- package/dist/types/server/middleware/json-parser.d.ts +10 -0
- package/dist/types/server/middleware/response.d.ts +50 -0
- package/dist/types/server/routers/assets.d.ts +6 -0
- package/dist/types/server/routers/auth.d.ts +9 -0
- package/dist/types/server/routers/baseline.d.ts +13 -0
- package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
- package/dist/types/server/routers/config.d.ts +9 -0
- package/dist/types/server/routers/dashboard.d.ts +6 -0
- package/dist/types/server/routers/health.d.ts +11 -0
- package/dist/types/server/routers/projects.d.ts +9 -0
- package/dist/types/server/routers/screenshot.d.ts +11 -0
- package/dist/types/services/build-manager.d.ts +4 -3
- package/dist/types/services/config-service.d.ts +2 -3
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/project-service.d.ts +6 -4
- package/dist/types/services/screenshot-server.d.ts +5 -5
- package/dist/types/services/server-manager.d.ts +5 -3
- package/dist/types/services/tdd-service.d.ts +12 -1
- package/dist/types/services/test-runner.d.ts +3 -3
- package/dist/types/utils/output.d.ts +84 -0
- package/dist/utils/config-loader.js +24 -48
- package/dist/utils/global-config.js +2 -17
- package/dist/utils/output.js +445 -0
- package/dist/utils/security.js +3 -4
- package/docs/api-reference.md +0 -1
- package/docs/plugins.md +22 -22
- package/package.json +3 -2
- package/dist/container/index.js +0 -215
- package/dist/services/base-service.js +0 -154
- package/dist/types/container/index.d.ts +0 -59
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
- package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
- package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
- package/dist/types/services/base-service.d.ts +0 -71
- package/dist/types/utils/console-ui.d.ts +0 -61
- package/dist/types/utils/logger-factory.d.ts +0 -26
- package/dist/types/utils/logger.d.ts +0 -79
- package/dist/utils/console-ui.js +0 -241
- package/dist/utils/logger-factory.js +0 -76
- package/dist/utils/logger.js +0 -231
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configure output settings
|
|
3
|
+
* Call this once at CLI startup with global options
|
|
4
|
+
*/
|
|
5
|
+
export function configure(options?: {}): void;
|
|
6
|
+
/**
|
|
7
|
+
* Show command header (e.g., "vizzly · tdd · local")
|
|
8
|
+
* Only shows once per command execution
|
|
9
|
+
*/
|
|
10
|
+
export function header(command: any, mode?: any): void;
|
|
11
|
+
/**
|
|
12
|
+
* Get current colors instance (for custom formatting)
|
|
13
|
+
*/
|
|
14
|
+
export function getColors(): {
|
|
15
|
+
success: any;
|
|
16
|
+
error: any;
|
|
17
|
+
warning: any;
|
|
18
|
+
info: any;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Show a success message
|
|
22
|
+
*/
|
|
23
|
+
export function success(message: any, data?: {}): void;
|
|
24
|
+
/**
|
|
25
|
+
* Show final result summary (e.g., "✓ 5 screenshots · 234ms")
|
|
26
|
+
*/
|
|
27
|
+
export function result(message: any): void;
|
|
28
|
+
/**
|
|
29
|
+
* Show an info message
|
|
30
|
+
*/
|
|
31
|
+
export function info(message: any, data?: {}): void;
|
|
32
|
+
/**
|
|
33
|
+
* Show a warning message (goes to stderr)
|
|
34
|
+
*/
|
|
35
|
+
export function warn(message: any, data?: {}): void;
|
|
36
|
+
/**
|
|
37
|
+
* Show an error message (goes to stderr)
|
|
38
|
+
* Does NOT exit - caller decides whether to exit
|
|
39
|
+
*/
|
|
40
|
+
export function error(message: any, err?: any, data?: {}): void;
|
|
41
|
+
/**
|
|
42
|
+
* Print a blank line for spacing
|
|
43
|
+
*/
|
|
44
|
+
export function blank(): void;
|
|
45
|
+
/**
|
|
46
|
+
* Print raw text without any formatting
|
|
47
|
+
*/
|
|
48
|
+
export function print(text: any): void;
|
|
49
|
+
/**
|
|
50
|
+
* Print raw text to stderr
|
|
51
|
+
*/
|
|
52
|
+
export function printErr(text: any): void;
|
|
53
|
+
/**
|
|
54
|
+
* Output structured data
|
|
55
|
+
*/
|
|
56
|
+
export function data(obj: any): void;
|
|
57
|
+
/**
|
|
58
|
+
* Start a spinner with message
|
|
59
|
+
*/
|
|
60
|
+
export function startSpinner(message: any): void;
|
|
61
|
+
/**
|
|
62
|
+
* Update spinner message
|
|
63
|
+
*/
|
|
64
|
+
export function updateSpinner(message: any, current?: number, total?: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Stop the spinner
|
|
67
|
+
*/
|
|
68
|
+
export function stopSpinner(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Show progress update
|
|
71
|
+
*/
|
|
72
|
+
export function progress(message: any, current?: number, total?: number): void;
|
|
73
|
+
/**
|
|
74
|
+
* Log debug message with component prefix (only shown in verbose mode)
|
|
75
|
+
*
|
|
76
|
+
* @param {string} component - Component name (e.g., 'server', 'config', 'build')
|
|
77
|
+
* @param {string} message - Debug message
|
|
78
|
+
* @param {Object} data - Optional data object to display inline
|
|
79
|
+
*/
|
|
80
|
+
export function debug(component: string, message: string, data?: any): void;
|
|
81
|
+
/**
|
|
82
|
+
* Clean up (stop spinner, flush logs)
|
|
83
|
+
*/
|
|
84
|
+
export function cleanup(): void;
|
|
@@ -3,7 +3,8 @@ import { resolve } from 'path';
|
|
|
3
3
|
import { getApiToken, getApiUrl, getParallelId } from './environment-config.js';
|
|
4
4
|
import { validateVizzlyConfigWithDefaults } from './config-schema.js';
|
|
5
5
|
import { getAccessToken, getProjectMapping } from './global-config.js';
|
|
6
|
-
|
|
6
|
+
import * as output from './output.js';
|
|
7
|
+
let DEFAULT_CONFIG = {
|
|
7
8
|
// API Configuration
|
|
8
9
|
apiKey: undefined,
|
|
9
10
|
// Will be set from env, global config, or CLI overrides
|
|
@@ -37,8 +38,8 @@ const DEFAULT_CONFIG = {
|
|
|
37
38
|
};
|
|
38
39
|
export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
39
40
|
// 1. Load from config file using cosmiconfig
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
let explorer = cosmiconfigSync('vizzly');
|
|
42
|
+
let result = configPath ? explorer.load(configPath) : explorer.search();
|
|
42
43
|
let fileConfig = {};
|
|
43
44
|
if (result && result.config) {
|
|
44
45
|
// Handle ESM default export (cosmiconfig wraps it in { default: {...} })
|
|
@@ -46,10 +47,10 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// 2. Validate config file using Zod schema
|
|
49
|
-
|
|
50
|
+
let validatedFileConfig = validateVizzlyConfigWithDefaults(fileConfig);
|
|
50
51
|
|
|
51
52
|
// Create a proper clone of the default config to avoid shared object references
|
|
52
|
-
|
|
53
|
+
let config = {
|
|
53
54
|
...DEFAULT_CONFIG,
|
|
54
55
|
server: {
|
|
55
56
|
...DEFAULT_CONFIG.server
|
|
@@ -74,11 +75,8 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
|
74
75
|
|
|
75
76
|
// 3. Check project mapping for current directory (if no CLI flag)
|
|
76
77
|
if (!cliOverrides.token) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.log('[CONFIG] Looking up project mapping for:', currentDir);
|
|
80
|
-
}
|
|
81
|
-
const projectMapping = await getProjectMapping(currentDir);
|
|
78
|
+
let currentDir = process.cwd();
|
|
79
|
+
let projectMapping = await getProjectMapping(currentDir);
|
|
82
80
|
if (projectMapping && projectMapping.token) {
|
|
83
81
|
// Handle both string tokens and token objects (backward compatibility)
|
|
84
82
|
let token;
|
|
@@ -93,59 +91,37 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
|
|
|
93
91
|
config.apiKey = token;
|
|
94
92
|
config.projectSlug = projectMapping.projectSlug;
|
|
95
93
|
config.organizationSlug = projectMapping.organizationSlug;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
dir: currentDir,
|
|
101
|
-
projectSlug: projectMapping.projectSlug,
|
|
102
|
-
hasToken: !!projectMapping.token,
|
|
103
|
-
tokenType: typeof projectMapping.token,
|
|
104
|
-
tokenPrefix: token ? token.substring(0, 8) + '***' : 'none'
|
|
105
|
-
});
|
|
106
|
-
console.log('[CONFIG] Set config.apiKey to:', config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE');
|
|
107
|
-
}
|
|
108
|
-
} else if (process.env.DEBUG_CONFIG) {
|
|
109
|
-
console.log('[CONFIG] No project mapping found for:', currentDir);
|
|
94
|
+
output.debug('Using project mapping', {
|
|
95
|
+
project: projectMapping.projectSlug,
|
|
96
|
+
org: projectMapping.organizationSlug
|
|
97
|
+
});
|
|
110
98
|
}
|
|
111
99
|
}
|
|
112
100
|
|
|
113
101
|
// 3.5. Check global config for user access token (if no CLI flag)
|
|
114
102
|
if (!config.apiKey && !cliOverrides.token) {
|
|
115
|
-
|
|
103
|
+
let globalToken = await getAccessToken();
|
|
116
104
|
if (globalToken) {
|
|
117
105
|
config.apiKey = globalToken;
|
|
118
106
|
}
|
|
119
107
|
}
|
|
120
108
|
|
|
121
109
|
// 4. Override with environment variables (higher priority than fallbacks)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
envApiKeyPrefix: envApiKey ? envApiKey.substring(0, 8) + '***' : 'none',
|
|
129
|
-
configApiKeyBefore: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE'
|
|
130
|
-
}));
|
|
110
|
+
let envApiKey = getApiToken();
|
|
111
|
+
let envApiUrl = getApiUrl();
|
|
112
|
+
let envParallelId = getParallelId();
|
|
113
|
+
if (envApiKey) {
|
|
114
|
+
config.apiKey = envApiKey;
|
|
115
|
+
output.debug('Using API token from environment');
|
|
131
116
|
}
|
|
132
|
-
if (envApiKey) config.apiKey = envApiKey;
|
|
133
117
|
if (envApiUrl !== 'https://app.vizzly.dev') config.apiUrl = envApiUrl;
|
|
134
118
|
if (envParallelId) config.parallelId = envParallelId;
|
|
135
119
|
|
|
136
120
|
// 5. Apply CLI overrides (highest priority)
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
configApiKey: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE',
|
|
140
|
-
cliToken: cliOverrides.token ? cliOverrides.token.substring(0, 8) + '***' : 'none'
|
|
141
|
-
});
|
|
121
|
+
if (cliOverrides.token) {
|
|
122
|
+
output.debug('Using API token from --token flag');
|
|
142
123
|
}
|
|
143
124
|
applyCLIOverrides(config, cliOverrides);
|
|
144
|
-
if (process.env.DEBUG_CONFIG) {
|
|
145
|
-
console.log('[CONFIG] Step 6 - after CLI overrides:', {
|
|
146
|
-
configApiKey: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE'
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
125
|
return config;
|
|
150
126
|
}
|
|
151
127
|
|
|
@@ -191,7 +167,7 @@ function applyCLIOverrides(config, cliOverrides = {}) {
|
|
|
191
167
|
if (cliOverrides.allowNoToken !== undefined) config.allowNoToken = cliOverrides.allowNoToken;
|
|
192
168
|
}
|
|
193
169
|
function mergeConfig(target, source) {
|
|
194
|
-
for (
|
|
170
|
+
for (let key in source) {
|
|
195
171
|
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
196
172
|
if (!target[key]) target[key] = {};
|
|
197
173
|
mergeConfig(target[key], source[key]);
|
|
@@ -201,7 +177,7 @@ function mergeConfig(target, source) {
|
|
|
201
177
|
}
|
|
202
178
|
}
|
|
203
179
|
export function getScreenshotPaths(config) {
|
|
204
|
-
|
|
205
|
-
|
|
180
|
+
let screenshotsDir = config.upload?.screenshotsDir || './screenshots';
|
|
181
|
+
let paths = Array.isArray(screenshotsDir) ? screenshotsDir : [screenshotsDir];
|
|
206
182
|
return paths.map(p => resolve(process.cwd(), p));
|
|
207
183
|
}
|
|
@@ -7,6 +7,7 @@ import { homedir } from 'os';
|
|
|
7
7
|
import { join, dirname, parse } from 'path';
|
|
8
8
|
import { readFile, writeFile, mkdir, chmod } from 'fs/promises';
|
|
9
9
|
import { existsSync } from 'fs';
|
|
10
|
+
import * as output from './output.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Get the path to the global Vizzly directory
|
|
@@ -57,7 +58,7 @@ export async function loadGlobalConfig() {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// Log warning about corrupted config but don't crash
|
|
60
|
-
|
|
61
|
+
output.warn('Global config file is corrupted, ignoring');
|
|
61
62
|
return {};
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -176,9 +177,6 @@ export async function getAccessToken() {
|
|
|
176
177
|
export async function getProjectMapping(directoryPath) {
|
|
177
178
|
let config = await loadGlobalConfig();
|
|
178
179
|
if (!config.projects) {
|
|
179
|
-
if (process.env.DEBUG_CONFIG) {
|
|
180
|
-
console.log('[MAPPING] No projects in global config');
|
|
181
|
-
}
|
|
182
180
|
return null;
|
|
183
181
|
}
|
|
184
182
|
|
|
@@ -187,18 +185,8 @@ export async function getProjectMapping(directoryPath) {
|
|
|
187
185
|
let {
|
|
188
186
|
root
|
|
189
187
|
} = parse(currentPath);
|
|
190
|
-
if (process.env.DEBUG_CONFIG) {
|
|
191
|
-
console.log('[MAPPING] Starting lookup from:', currentPath);
|
|
192
|
-
console.log('[MAPPING] Available mappings:', Object.keys(config.projects));
|
|
193
|
-
}
|
|
194
188
|
while (currentPath !== root) {
|
|
195
|
-
if (process.env.DEBUG_CONFIG) {
|
|
196
|
-
console.log('[MAPPING] Checking:', currentPath);
|
|
197
|
-
}
|
|
198
189
|
if (config.projects[currentPath]) {
|
|
199
|
-
if (process.env.DEBUG_CONFIG) {
|
|
200
|
-
console.log('[MAPPING] Found match at:', currentPath);
|
|
201
|
-
}
|
|
202
190
|
return config.projects[currentPath];
|
|
203
191
|
}
|
|
204
192
|
|
|
@@ -210,9 +198,6 @@ export async function getProjectMapping(directoryPath) {
|
|
|
210
198
|
}
|
|
211
199
|
currentPath = parentPath;
|
|
212
200
|
}
|
|
213
|
-
if (process.env.DEBUG_CONFIG) {
|
|
214
|
-
console.log('[MAPPING] No mapping found');
|
|
215
|
-
}
|
|
216
201
|
return null;
|
|
217
202
|
}
|
|
218
203
|
|