@vizzly-testing/cli 0.26.2 → 0.27.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.
@@ -28,6 +28,8 @@ const VALID_LOG_LEVELS = Object.keys(LOG_LEVELS);
28
28
  // Module state
29
29
  const config = {
30
30
  json: false,
31
+ jsonFields: null,
32
+ // null = all fields, array = selected fields
31
33
  logLevel: null,
32
34
  // null = not yet initialized, will check env var on first configure
33
35
  color: undefined,
@@ -74,12 +76,96 @@ function normalizeLogLevel(level) {
74
76
  return VALID_LOG_LEVELS.includes(normalized) ? normalized : 'info';
75
77
  }
76
78
 
79
+ /**
80
+ * Parse --json flag value into field list
81
+ * Supports: true (all fields), "field1,field2" (selected fields)
82
+ * @param {boolean|string} jsonArg - The --json flag value
83
+ * @returns {string[]|null} Array of field names, or null for all fields
84
+ */
85
+ export function parseJsonFields(jsonArg) {
86
+ if (jsonArg === true || jsonArg === 'true') return null; // All fields
87
+ if (typeof jsonArg === 'string' && jsonArg.length > 0) {
88
+ return jsonArg.split(',').map(f => f.trim()).filter(f => f.length > 0);
89
+ }
90
+ return null;
91
+ }
92
+
93
+ /**
94
+ * Get a nested value from an object using dot notation
95
+ * @param {Object} obj - Source object
96
+ * @param {string} path - Dot-separated path (e.g., "comparisons.total")
97
+ * @returns {*} The value at the path, or undefined if not found
98
+ */
99
+ function getNestedValue(obj, path) {
100
+ let parts = path.split('.');
101
+ let current = obj;
102
+ for (let part of parts) {
103
+ if (current === null || current === undefined) return undefined;
104
+ current = current[part];
105
+ }
106
+ return current;
107
+ }
108
+
109
+ /**
110
+ * Set a nested value in an object using dot notation
111
+ * Creates intermediate objects as needed
112
+ * @param {Object} obj - Target object
113
+ * @param {string} path - Dot-separated path (e.g., "comparisons.total")
114
+ * @param {*} value - Value to set
115
+ */
116
+ function setNestedValue(obj, path, value) {
117
+ let parts = path.split('.');
118
+ let current = obj;
119
+ for (let i = 0; i < parts.length - 1; i++) {
120
+ let part = parts[i];
121
+ if (!(part in current)) {
122
+ current[part] = {};
123
+ }
124
+ current = current[part];
125
+ }
126
+ current[parts[parts.length - 1]] = value;
127
+ }
128
+
129
+ /**
130
+ * Select specific fields from an object
131
+ * Supports dot notation for nested fields (e.g., "comparisons.total")
132
+ * Warns in debug mode when requested fields aren't found
133
+ * @param {Object|Array} obj - Source object or array
134
+ * @param {string[]} fields - Fields to select
135
+ * @returns {Object|Array} Object with only selected fields
136
+ */
137
+ function selectFields(obj, fields) {
138
+ if (Array.isArray(obj)) {
139
+ return obj.map(item => selectFields(item, fields));
140
+ }
141
+ if (obj === null || typeof obj !== 'object') {
142
+ return obj;
143
+ }
144
+ let result = {};
145
+ let missingFields = [];
146
+ for (let field of fields) {
147
+ let value = getNestedValue(obj, field);
148
+ if (value !== undefined) {
149
+ setNestedValue(result, field, value);
150
+ } else {
151
+ missingFields.push(field);
152
+ }
153
+ }
154
+
155
+ // Warn about missing fields in verbose mode
156
+ if (missingFields.length > 0 && shouldLog('debug')) {
157
+ console.error(colors.dim(`Note: Requested field(s) not found: ${missingFields.join(', ')}`));
158
+ }
159
+ return result;
160
+ }
161
+
77
162
  /**
78
163
  * Configure output settings
79
164
  * Call this once at CLI startup with global options
80
165
  *
81
166
  * @param {Object} options - Configuration options
82
- * @param {boolean} [options.json] - Enable JSON output mode
167
+ * @param {boolean|string} [options.json] - Enable JSON output mode, optionally with field selection
168
+ * @param {string[]} [options.jsonFields] - Fields to include in JSON output (null = all)
83
169
  * @param {string} [options.logLevel] - Log level (debug, info, warn, error)
84
170
  * @param {boolean} [options.verbose] - Shorthand for logLevel='debug' (backwards compatible)
85
171
  * @param {boolean} [options.color] - Enable colored output
@@ -88,7 +174,12 @@ function normalizeLogLevel(level) {
88
174
  * @param {boolean} [options.resetTimer] - Reset the start timer (default: true)
89
175
  */
90
176
  export function configure(options = {}) {
91
- if (options.json !== undefined) config.json = options.json;
177
+ // Handle --json flag: can be boolean or string of fields
178
+ if (options.json !== undefined) {
179
+ config.json = !!options.json; // Truthy check for JSON mode
180
+ config.jsonFields = parseJsonFields(options.json);
181
+ }
182
+ if (options.jsonFields !== undefined) config.jsonFields = options.jsonFields;
92
183
  if (options.color !== undefined) config.color = options.color;
93
184
  if (options.silent !== undefined) config.silent = options.silent;
94
185
  if (options.logFile !== undefined) config.logFile = options.logFile;
@@ -336,18 +427,29 @@ export function printErr(text) {
336
427
 
337
428
  /**
338
429
  * Output structured data
430
+ * When field selection is active, only specified fields are included
431
+ * @param {Object|Array} obj - Data to output
339
432
  */
340
433
  export function data(obj) {
434
+ let output = config.jsonFields ? selectFields(obj, config.jsonFields) : obj;
341
435
  if (config.json) {
342
436
  console.log(JSON.stringify({
343
437
  status: 'data',
344
- data: obj
438
+ data: output
345
439
  }));
346
440
  } else {
347
- console.log(JSON.stringify(obj, null, 2));
441
+ console.log(JSON.stringify(output, null, 2));
348
442
  }
349
443
  }
350
444
 
445
+ /**
446
+ * Get configured JSON fields (for commands that need direct access)
447
+ * @returns {string[]|null} Array of field names, or null for all fields
448
+ */
449
+ export function getJsonFields() {
450
+ return config.jsonFields;
451
+ }
452
+
351
453
  // ============================================================================
352
454
  // Spinner / Progress (stderr so it doesn't pollute piped output)
353
455
  // ============================================================================
@@ -982,6 +1084,7 @@ export function cleanup() {
982
1084
  export function reset() {
983
1085
  stopSpinner();
984
1086
  config.json = false;
1087
+ config.jsonFields = null;
985
1088
  config.logLevel = null;
986
1089
  config.color = undefined; // Reset to auto-detect
987
1090
  config.silent = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.26.2",
3
+ "version": "0.27.0",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",
@@ -56,6 +56,7 @@
56
56
  "LICENSE"
57
57
  ],
58
58
  "scripts": {
59
+ "cli": "source .envrc && node bin/vizzly.js",
59
60
  "start": "node src/index.js",
60
61
  "build": "npm run clean && npm run compile && npm run build:reporter && npm run build:reporter-ssr && npm run copy-types",
61
62
  "clean": "rimraf dist",