@saeroon/cli 0.2.9 → 0.2.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.
Files changed (2) hide show
  1. package/dist/index.js +226 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2547,9 +2547,8 @@ var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
2547
2547
  "main-block",
2548
2548
  "aside-block",
2549
2549
  "article-block",
2550
- // Specialized (5)
2550
+ // Specialized (4)
2551
2551
  "site-menu",
2552
- "floating-action-widget",
2553
2552
  "sticky-cta-bar",
2554
2553
  "cookie-consent-bar",
2555
2554
  "announcement-bar"
@@ -4035,6 +4034,25 @@ var EXTRACTION_SCRIPT = `
4035
4034
  return 'card-thumbnail';
4036
4035
  }
4037
4036
 
4037
+ // \u2500\u2500 0. \uD31D\uC5C5/\uC624\uBC84\uB808\uC774 DOM \uC81C\uAC70 (\uC139\uC158 \uAC10\uC9C0 \uC804) \u2500\u2500
4038
+ document.querySelectorAll(
4039
+ 'dialog[open], [class*=popup], [class*=layerpopup], [class*=modal]:not(body), [class*=cookie], [class*=consent], [class*=overlay]:not(body), [class*=dimmed]'
4040
+ ).forEach(el => {
4041
+ const rect = el.getBoundingClientRect();
4042
+ const style = window.getComputedStyle(el);
4043
+ const pos = style.position;
4044
+ // fixed/absolute \uC704\uCE58 + \uBDF0\uD3EC\uD2B8 \uB300\uBD80\uBD84 \uCC28\uC9C0 \u2192 \uD31D\uC5C5/\uC624\uBC84\uB808\uC774
4045
+ if ((pos === 'fixed' || pos === 'absolute') && rect.width >= window.innerWidth * 0.5) {
4046
+ el.remove();
4047
+ return;
4048
+ }
4049
+ // leeko-layerpopup \uAC19\uC740 \uD31D\uC5C5: class\uC5D0 popup/modal/overlay \uD3EC\uD568 + \uB2EB\uAE30 \uBC84\uD2BC \uC874\uC7AC
4050
+ if (el.querySelector('[class*=close], [aria-label*=close], button')) {
4051
+ const isSmall = rect.width < 100 && rect.height < 100;
4052
+ if (!isSmall) el.remove();
4053
+ }
4054
+ });
4055
+
4038
4056
  // \u2500\u2500 1. Structure \u2500\u2500
4039
4057
  const sectionSelectors = 'body > header, body > footer, body > nav, body > main, body > section, body > div, body > article, body > aside';
4040
4058
  const topLevelEls = document.querySelectorAll(sectionSelectors);
@@ -4107,6 +4125,39 @@ var EXTRACTION_SCRIPT = `
4107
4125
  return !leafTags.has(tag);
4108
4126
  });
4109
4127
 
4128
+ // \u2500\u2500 Fallback: \uC139\uC158 3\uAC1C \uBBF8\uB9CC\uC774\uBA74 content-based \uD0D0\uC0C9 \u2500\u2500
4129
+ if (sectionEls.length < 3) {
4130
+ const vw = window.innerWidth;
4131
+ const candidates = [];
4132
+ // \uBAA8\uB4E0 \uC694\uC18C\uB97C \uC21C\uD68C\uD558\uBA70 \uC139\uC158 \uD6C4\uBCF4 \uD0D0\uC0C9
4133
+ document.querySelectorAll('header, footer, nav, section, article, aside, div, main').forEach(el => {
4134
+ if (leafTags.has(el.tagName.toLowerCase())) return;
4135
+ const rect = el.getBoundingClientRect();
4136
+ // \uC804\uCCB4 \uD3ED\uC758 80% \uC774\uC0C1 + \uB192\uC774 100px \uC774\uC0C1
4137
+ if (rect.width < vw * 0.8 || rect.height < 100) return;
4138
+ // \uD615\uC81C\uAC00 \uC788\uB294 \uC694\uC18C\uB9CC (\uC139\uC158 = \uAC19\uC740 \uB808\uBCA8 \uD615\uC81C\uB4E4)
4139
+ const parent = el.parentElement;
4140
+ if (!parent) return;
4141
+ const siblings = Array.from(parent.children).filter(s => {
4142
+ const sr = s.getBoundingClientRect();
4143
+ return sr.width >= vw * 0.8 && sr.height >= 100 && !leafTags.has(s.tagName.toLowerCase());
4144
+ });
4145
+ if (siblings.length >= 2) {
4146
+ candidates.push({ el, siblings, parentEl: parent, sibCount: siblings.length });
4147
+ }
4148
+ });
4149
+
4150
+ // \uAC00\uC7A5 \uB9CE\uC740 \uD615\uC81C\uB97C \uAC00\uC9C4 \uADF8\uB8F9 \uC120\uD0DD
4151
+ if (candidates.length > 0) {
4152
+ candidates.sort((a, b) => b.sibCount - a.sibCount);
4153
+ const best = candidates[0];
4154
+ // \uAE30\uC874 \uACB0\uACFC\uBCF4\uB2E4 \uB098\uC740 \uACBD\uC6B0\uB9CC \uAD50\uCCB4
4155
+ if (best.sibCount > sectionEls.length) {
4156
+ sectionEls = best.siblings;
4157
+ }
4158
+ }
4159
+ }
4160
+
4110
4161
  const sections = [];
4111
4162
  let maxDepth = 0;
4112
4163
 
@@ -4265,7 +4316,22 @@ var EXTRACTION_SCRIPT = `
4265
4316
  if (ff) fontFamilySet.add(ff.split(',')[0].trim().replace(/['"]/g, ''));
4266
4317
  });
4267
4318
 
4268
- // 2c. Spacing
4319
+ // 2c. Spacing (B-2: \uC815\uBC00 \uCD94\uCD9C)
4320
+ function median(arr) {
4321
+ if (arr.length === 0) return 0;
4322
+ const s = [...arr].sort((a, b) => a - b);
4323
+ const mid = Math.floor(s.length / 2);
4324
+ return s.length % 2 ? s[mid] : Math.round((s[mid - 1] + s[mid]) / 2);
4325
+ }
4326
+ function mode(arr) {
4327
+ if (arr.length === 0) return 0;
4328
+ const freq = {};
4329
+ arr.forEach(v => { const k = Math.round(v); freq[k] = (freq[k] || 0) + 1; });
4330
+ return Number(Object.entries(freq).sort((a, b) => b[1] - a[1])[0][0]);
4331
+ }
4332
+ function roundTo4(v) { return Math.round(v / 4) * 4; } // 4px \uB2E8\uC704 \uC815\uADDC\uD654
4333
+
4334
+ // \uC139\uC158 \uAC04 \uAC04\uACA9
4269
4335
  const sectionGaps = [];
4270
4336
  for (let i = 1; i < sections.length; i++) {
4271
4337
  const prev = topLevelEls[i - 1];
@@ -4274,25 +4340,107 @@ var EXTRACTION_SCRIPT = `
4274
4340
  const prevRect = prev.getBoundingClientRect();
4275
4341
  const currRect = curr.getBoundingClientRect();
4276
4342
  const gap = currRect.top - prevRect.bottom;
4277
- if (gap > 0 && gap < 500) sectionGaps.push(gap);
4343
+ if (gap > 0 && gap < 500) sectionGaps.push(Math.round(gap));
4278
4344
  }
4279
4345
  }
4280
4346
 
4281
- const firstContent = document.querySelector('main, [class*=container], [class*=wrapper], body > div > div');
4282
- const contentPadding = firstContent ? parsePixel(getComputedProp(firstContent, 'padding-left')) : 16;
4347
+ // \uC139\uC158 padding-top / padding-bottom \uC218\uC9D1
4348
+ const sectionPaddingTops = [];
4349
+ const sectionPaddingBottoms = [];
4350
+ topLevelEls.forEach(el => {
4351
+ if (!el) return;
4352
+ const pt = parsePixel(getComputedProp(el, 'padding-top'));
4353
+ const pb = parsePixel(getComputedProp(el, 'padding-bottom'));
4354
+ if (pt > 0) sectionPaddingTops.push(Math.round(pt));
4355
+ if (pb > 0) sectionPaddingBottoms.push(Math.round(pb));
4356
+ });
4283
4357
 
4284
- // card gap \uCD94\uC815: \uCCAB \uBC88\uC9F8 grid/flex \uCEE8\uD14C\uC774\uB108\uC758 gap
4285
- let cardGap = 0;
4286
- const gridContainers = document.querySelectorAll('[style*="grid"], [class*="grid"], [style*="flex"]');
4287
- for (const gc of gridContainers) {
4288
- const gap = parsePixel(getComputedProp(gc, 'gap') || getComputedProp(gc, 'column-gap'));
4289
- if (gap > 0) { cardGap = gap; break; }
4290
- }
4358
+ // content padding: \uB2E4\uC218 \uCEE8\uD14C\uC774\uB108\uC5D0\uC11C \uC218\uC9D1
4359
+ const contentPaddingValues = [];
4360
+ const contentPaddingDetails = [];
4361
+ const containerSelectors = 'main, [class*=container], [class*=wrapper], [class*=content], [class*=inner]';
4362
+ document.querySelectorAll(containerSelectors).forEach(el => {
4363
+ const pl = parsePixel(getComputedProp(el, 'padding-left'));
4364
+ const pr = parsePixel(getComputedProp(el, 'padding-right'));
4365
+ if (pl > 0 || pr > 0) {
4366
+ contentPaddingValues.push(pl, pr);
4367
+ contentPaddingDetails.push({ left: Math.round(pl), right: Math.round(pr), count: 1 });
4368
+ }
4369
+ });
4370
+ // \uC911\uBCF5 \uD569\uC0B0
4371
+ const paddingMap = {};
4372
+ contentPaddingDetails.forEach(d => {
4373
+ const k = d.left + ',' + d.right;
4374
+ if (paddingMap[k]) paddingMap[k].count++;
4375
+ else paddingMap[k] = { ...d };
4376
+ });
4377
+ const uniquePaddings = Object.values(paddingMap).sort((a, b) => b.count - a.count);
4378
+ const contentPadding = contentPaddingValues.length > 0 ? mode(contentPaddingValues.filter(v => v > 0)) : 16;
4379
+
4380
+ // card gap: \uBAA8\uB4E0 grid/flex \uCEE8\uD14C\uC774\uB108\uC5D0\uC11C \uC218\uC9D1
4381
+ const cardGapValues = [];
4382
+ const allLayoutContainers = document.querySelectorAll('*');
4383
+ allLayoutContainers.forEach(el => {
4384
+ const display = getComputedProp(el, 'display');
4385
+ if (!display || (!display.includes('grid') && !display.includes('flex'))) return;
4386
+ const gap = parsePixel(getComputedProp(el, 'gap'));
4387
+ const colGap = parsePixel(getComputedProp(el, 'column-gap'));
4388
+ const rowGap = parsePixel(getComputedProp(el, 'row-gap'));
4389
+ const v = gap || colGap || rowGap;
4390
+ if (v > 0 && v < 200) cardGapValues.push(Math.round(v));
4391
+ });
4392
+ // margin \uAE30\uBC18 \uAC04\uACA9\uB3C4 \uAC10\uC9C0 (flex/grid \uC544\uB2CC \uB9AC\uC2A4\uD2B8)
4393
+ document.querySelectorAll('ul, ol, [class*=list], [class*=cards]').forEach(parent => {
4394
+ const children = Array.from(parent.children);
4395
+ for (let i = 1; i < Math.min(children.length, 5); i++) {
4396
+ const prevRect = children[i - 1].getBoundingClientRect();
4397
+ const currRect = children[i].getBoundingClientRect();
4398
+ const vGap = currRect.top - prevRect.bottom;
4399
+ const hGap = currRect.left - prevRect.right;
4400
+ const g = Math.abs(vGap) < 2 ? hGap : vGap; // \uC218\uD3C9 \uBC30\uCE58\uBA74 hGap
4401
+ if (g > 0 && g < 200) cardGapValues.push(Math.round(g));
4402
+ }
4403
+ });
4404
+ const cardGap = cardGapValues.length > 0 ? mode(cardGapValues) : 16;
4405
+
4406
+ // \uC139\uC158 \uB0B4 \uD615\uC81C \uC694\uC18C \uAC04 \uC218\uC9C1 \uAC04\uACA9 (elementGap)
4407
+ const elementGapValues = [];
4408
+ topLevelEls.forEach(sectionEl => {
4409
+ if (!sectionEl) return;
4410
+ const children = Array.from(sectionEl.children);
4411
+ for (let i = 1; i < Math.min(children.length, 8); i++) {
4412
+ const prevRect = children[i - 1].getBoundingClientRect();
4413
+ const currRect = children[i].getBoundingClientRect();
4414
+ const gap = currRect.top - prevRect.bottom;
4415
+ if (gap > 0 && gap < 200) elementGapValues.push(Math.round(gap));
4416
+ }
4417
+ });
4418
+ const elementGap = elementGapValues.length > 0 ? median(elementGapValues) : 16;
4419
+
4420
+ // \uBE48\uB3C4 \uBD84\uD3EC (4px \uB2E8\uC704\uB85C \uC815\uADDC\uD654)
4421
+ const spacingFrequency = {};
4422
+ [...sectionGaps, ...sectionPaddingTops, ...sectionPaddingBottoms, ...contentPaddingValues, ...cardGapValues, ...elementGapValues]
4423
+ .filter(v => v > 0)
4424
+ .forEach(v => { const k = roundTo4(v); spacingFrequency[k] = (spacingFrequency[k] || 0) + 1; });
4291
4425
 
4292
- // base unit \uCD94\uC815 (\uAC00\uC7A5 \uD754\uD55C \uAC04\uACA9\uAC12\uC758 \uCD5C\uB300\uACF5\uC57D\uC218)
4293
- const allGaps = [...sectionGaps, contentPadding, cardGap].filter(v => v > 0);
4426
+ // base unit: \uBE48\uB3C4 \uCD5C\uB2E4 \uAC12 \uAE30\uBC18, 4/8 \uCCB4\uACC4 \uC790\uB3D9 \uAC10\uC9C0
4427
+ const freqEntries = Object.entries(spacingFrequency).sort((a, b) => b[1] - a[1]);
4428
+ const topValues = freqEntries.slice(0, 6).map(e => Number(e[0]));
4294
4429
  function gcd(a, b) { return b === 0 ? a : gcd(b, a % b); }
4295
- const baseUnit = allGaps.length > 1 ? allGaps.reduce((a, b) => gcd(a, b)) : (allGaps[0] || 8);
4430
+ const baseUnit = topValues.length > 1 ? topValues.reduce((a, b) => gcd(a, b)) : (topValues[0] || 8);
4431
+
4432
+ // spacing details \uAC1D\uCCB4
4433
+ const spacingDetails = {
4434
+ sectionGaps,
4435
+ sectionPadding: {
4436
+ top: sectionPaddingTops.length > 0 ? median(sectionPaddingTops) : 0,
4437
+ bottom: sectionPaddingBottoms.length > 0 ? median(sectionPaddingBottoms) : 0,
4438
+ },
4439
+ contentPaddings: uniquePaddings.slice(0, 5),
4440
+ cardGaps: [...new Set(cardGapValues)].sort((a, b) => a - b).slice(0, 10),
4441
+ elementGap,
4442
+ spacingFrequency,
4443
+ };
4296
4444
 
4297
4445
  // 2d. BorderRadius
4298
4446
  const radiusValues = [];
@@ -4605,10 +4753,11 @@ var EXTRACTION_SCRIPT = `
4605
4753
  scale: typographyScale,
4606
4754
  },
4607
4755
  spacing: {
4608
- sectionGap: sectionGaps.length > 0 ? Math.round(sectionGaps.reduce((a, b) => a + b, 0) / sectionGaps.length) : 80,
4756
+ sectionGap: sectionGaps.length > 0 ? median(sectionGaps) : 80,
4609
4757
  contentPadding,
4610
4758
  cardGap: cardGap || 16,
4611
4759
  baseUnit: Math.max(baseUnit, 4),
4760
+ details: spacingDetails,
4612
4761
  },
4613
4762
  borderRadius: {
4614
4763
  small: uniqueRadii[0] || 0,
@@ -5042,7 +5191,17 @@ async function commandAnalyze(url, options) {
5042
5191
  console.log(chalk15.dim(` Accent: ${colors.accent}`));
5043
5192
  console.log(chalk15.dim(` \uD3F0\uD2B8: ${typography.fontFamilies.join(", ") || "(\uCD94\uCD9C \uC2E4\uD328)"}`));
5044
5193
  console.log(chalk15.dim(` \uC139\uC158 \uAC04\uACA9: ${spacing.sectionGap}px`));
5194
+ console.log(chalk15.dim(` \uCF58\uD150\uCE20 \uD328\uB529: ${spacing.contentPadding}px`));
5195
+ console.log(chalk15.dim(` \uCE74\uB4DC \uAC04\uACA9: ${spacing.cardGap}px`));
5045
5196
  console.log(chalk15.dim(` \uAE30\uBCF8 \uB2E8\uC704: ${spacing.baseUnit}px`));
5197
+ if (spacing.details) {
5198
+ const d = spacing.details;
5199
+ console.log(chalk15.dim(` \uC139\uC158 \uD328\uB529: top ${d.sectionPadding.top}px / bottom ${d.sectionPadding.bottom}px`));
5200
+ console.log(chalk15.dim(` \uC694\uC18C \uAC04\uACA9: ${d.elementGap}px`));
5201
+ if (d.cardGaps.length > 1) {
5202
+ console.log(chalk15.dim(` \uCE74\uB4DC \uAC04\uACA9 \uBD84\uD3EC: [${d.cardGaps.join(", ")}]px`));
5203
+ }
5204
+ }
5046
5205
  console.log("");
5047
5206
  console.log(chalk15.bold("\uC778\uD130\uB799\uC158"));
5048
5207
  const { interactions } = analysis;
@@ -5232,6 +5391,27 @@ async function runMultiViewportCompare(options) {
5232
5391
  console.log("");
5233
5392
  const hasMagick = checkCommand("magick");
5234
5393
  const results = [];
5394
+ const baselineMap = /* @__PURE__ */ new Map();
5395
+ if (hasMagick) {
5396
+ const blSpinner = spinner("\uAE30\uC900\uC120 \uCE21\uC815 \uC911 (\uB808\uD37C\uB7F0\uC2A4 2\uD68C \uCEA1\uCC98)...");
5397
+ for (const vp of viewports) {
5398
+ const blRef1 = join3(outputDir, `baseline-1-${vp.name}.png`);
5399
+ const blRef2 = join3(outputDir, `baseline-2-${vp.name}.png`);
5400
+ const blDiff = join3(outputDir, `baseline-diff-${vp.name}.png`);
5401
+ try {
5402
+ captureScreenshot2(options.ref, blRef1, vp.width, vp.height);
5403
+ captureScreenshot2(options.ref, blRef2, vp.width, vp.height);
5404
+ const blPct = generateDiffWithMagick(blRef1, blRef2, blDiff);
5405
+ baselineMap.set(vp.name, blPct >= 0 ? blPct : 0);
5406
+ } catch {
5407
+ baselineMap.set(vp.name, 0);
5408
+ }
5409
+ }
5410
+ const blValues = [...baselineMap.values()];
5411
+ const blAvg = blValues.reduce((a, b) => a + b, 0) / blValues.length;
5412
+ blSpinner.stop(chalk16.dim(` \uAE30\uC900\uC120 \uB178\uC774\uC988: \uD3C9\uADE0 ${blAvg.toFixed(1)}% (${blValues.map((v, i) => `${viewports[i].name} ${v.toFixed(1)}%`).join(", ")})`));
5413
+ console.log("");
5414
+ }
5235
5415
  for (const vp of viewports) {
5236
5416
  const vpSpinner = spinner(`${vp.name} (${vp.width}px) \uBE44\uAD50 \uC911...`);
5237
5417
  const refPath = join3(outputDir, `ref-${vp.name}.png`);
@@ -5247,17 +5427,23 @@ async function runMultiViewportCompare(options) {
5247
5427
  generateSideBySide(refPath, previewPath, diffPath);
5248
5428
  diffPercentage = -1;
5249
5429
  }
5430
+ const baselinePct = baselineMap.get(vp.name) ?? 0;
5431
+ const adjustedPct = diffPercentage >= 0 ? Math.round(Math.max(0, diffPercentage - baselinePct) * 10) / 10 : -1;
5250
5432
  results.push({
5251
5433
  name: vp.name,
5252
5434
  width: vp.width,
5253
5435
  referenceScreenshot: refPath,
5254
5436
  previewScreenshot: previewPath,
5255
5437
  diffScreenshot: diffPath,
5256
- diffPercentage
5438
+ diffPercentage,
5439
+ baselineDiffPercentage: baselinePct,
5440
+ adjustedDiffPercentage: adjustedPct
5257
5441
  });
5258
- const pctText = diffPercentage >= 0 ? `${diffPercentage.toFixed(1)}%` : "(\uACC4\uC0B0 \uBD88\uAC00)";
5259
- const pctColor = diffPercentage <= 10 ? chalk16.green : diffPercentage <= 25 ? chalk16.yellow : chalk16.red;
5260
- vpSpinner.stop(` ${vp.name}: diff ${pctColor(pctText)}`);
5442
+ const rawText = diffPercentage >= 0 ? `${diffPercentage.toFixed(1)}%` : "(\uACC4\uC0B0 \uBD88\uAC00)";
5443
+ const adjText = adjustedPct >= 0 ? `${adjustedPct.toFixed(1)}%` : "";
5444
+ const adjColor = adjustedPct <= 10 ? chalk16.green : adjustedPct <= 25 ? chalk16.yellow : chalk16.red;
5445
+ const label = baselinePct > 0 ? ` ${vp.name}: raw ${rawText} \u2192 ${chalk16.bold("adjusted")} ${adjColor(adjText)} ${chalk16.dim(`(baseline ${baselinePct.toFixed(1)}%)`)}` : ` ${vp.name}: diff ${adjColor(rawText)}`;
5446
+ vpSpinner.stop(label);
5261
5447
  } catch (error) {
5262
5448
  vpSpinner.stop(chalk16.red(` ${vp.name}: \uC2E4\uD328 \u2014 ${error instanceof Error ? error.message : String(error)}`));
5263
5449
  results.push({
@@ -5272,27 +5458,38 @@ async function runMultiViewportCompare(options) {
5272
5458
  }
5273
5459
  const validResults = results.filter((r) => r.diffPercentage >= 0);
5274
5460
  const overallDiff = validResults.length > 0 ? validResults.reduce((sum, r) => sum + r.diffPercentage, 0) / validResults.length : -1;
5461
+ const adjustedResults = results.filter((r) => (r.adjustedDiffPercentage ?? -1) >= 0);
5462
+ const overallAdjusted = adjustedResults.length > 0 ? adjustedResults.reduce((sum, r) => sum + r.adjustedDiffPercentage, 0) / adjustedResults.length : void 0;
5275
5463
  const report = {
5276
5464
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5277
5465
  referenceUrl: options.ref,
5278
5466
  previewUrl: options.preview,
5279
5467
  viewports: results,
5280
- overallDiffPercentage: Math.round(overallDiff * 10) / 10
5468
+ overallDiffPercentage: Math.round(overallDiff * 10) / 10,
5469
+ overallAdjustedDiffPercentage: overallAdjusted != null ? Math.round(overallAdjusted * 10) / 10 : void 0
5281
5470
  };
5282
5471
  const reportPath = join3(outputDir, "comparison-report.json");
5283
5472
  await writeFile9(reportPath, JSON.stringify(report, null, 2), "utf-8");
5284
5473
  console.log("");
5285
5474
  console.log(chalk16.bold("\uBE44\uAD50 \uC694\uC57D"));
5475
+ const hasBaseline = results.some((r) => (r.baselineDiffPercentage ?? 0) > 0);
5286
5476
  for (const r of results) {
5287
- const pct = r.diffPercentage >= 0 ? `${r.diffPercentage.toFixed(1)}%` : "N/A";
5288
- const icon = r.diffPercentage <= 10 ? chalk16.green("\u25CF") : r.diffPercentage <= 25 ? chalk16.yellow("\u25CF") : chalk16.red("\u25CF");
5289
- console.log(` ${icon} ${r.name.padEnd(8)} ${pct.padStart(6)} ${chalk16.dim(r.diffScreenshot)}`);
5477
+ const adjPct = r.adjustedDiffPercentage ?? r.diffPercentage;
5478
+ const displayPct = adjPct >= 0 ? `${adjPct.toFixed(1)}%` : "N/A";
5479
+ const icon = adjPct <= 10 ? chalk16.green("\u25CF") : adjPct <= 25 ? chalk16.yellow("\u25CF") : chalk16.red("\u25CF");
5480
+ const blNote = hasBaseline && (r.baselineDiffPercentage ?? 0) > 0 ? chalk16.dim(` (raw ${r.diffPercentage.toFixed(1)}%, baseline \u2212${r.baselineDiffPercentage.toFixed(1)}%)`) : "";
5481
+ console.log(` ${icon} ${r.name.padEnd(8)} ${displayPct.padStart(6)}${blNote}`);
5290
5482
  }
5483
+ const judgeDiff = overallAdjusted ?? overallDiff;
5291
5484
  console.log("");
5292
- if (overallDiff >= 0) {
5293
- const overallColor = overallDiff <= 10 ? chalk16.green : overallDiff <= 25 ? chalk16.yellow : chalk16.red;
5294
- console.log(chalk16.bold(`\uC804\uCCB4 \uD3C9\uADE0 diff: ${overallColor(`${overallDiff.toFixed(1)}%`)}`));
5295
- if (overallDiff <= 10) {
5485
+ if (judgeDiff >= 0) {
5486
+ const overallColor = judgeDiff <= 10 ? chalk16.green : judgeDiff <= 25 ? chalk16.yellow : chalk16.red;
5487
+ const label = overallAdjusted != null ? "\uC804\uCCB4 \uD3C9\uADE0 diff (adjusted)" : "\uC804\uCCB4 \uD3C9\uADE0 diff";
5488
+ console.log(chalk16.bold(`${label}: ${overallColor(`${judgeDiff.toFixed(1)}%`)}`));
5489
+ if (overallAdjusted != null && overallAdjusted !== overallDiff) {
5490
+ console.log(chalk16.dim(` (raw \uD3C9\uADE0: ${overallDiff.toFixed(1)}%, \uAE30\uC900\uC120 \uCC28\uAC10: \u2212${(overallDiff - overallAdjusted).toFixed(1)}%)`));
5491
+ }
5492
+ if (judgeDiff <= 10) {
5296
5493
  console.log(chalk16.green(" \u2192 \uBE44\uAD50 \uB8E8\uD504 \uC644\uB8CC! CEO \uCD5C\uC885 \uD655\uC778 \uC694\uCCAD \uAC00\uB2A5"));
5297
5494
  } else {
5298
5495
  console.log(chalk16.yellow(" \u2192 diff > 10%: Vision \uBE44\uAD50 \u2192 \uC2A4\uD0A4\uB9C8 \uC218\uC815 \uD544\uC694"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saeroon/cli",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "Saeroon Hosting developer CLI — preview, validate, and deploy sites & templates",
5
5
  "private": false,
6
6
  "type": "module",