@saeroon/cli 0.2.2 → 0.2.3

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 +982 -258
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -388,7 +388,7 @@ var SaeroonApiClient = class _SaeroonApiClient {
388
388
  await this.request(
389
389
  "PUT",
390
390
  `/api/v1/hosting/developer/sites/${encodeURIComponent(siteId)}/schema`,
391
- { schema },
391
+ { schemaJson: JSON.stringify(schema), isDraft: true },
392
392
  true
393
393
  );
394
394
  }
@@ -2095,7 +2095,7 @@ var PreviewClient = class {
2095
2095
  * POST /api/v1/developer/preview 호출하여 Preview 세션 생성.
2096
2096
  */
2097
2097
  async createSession(schema) {
2098
- const url = `${this.options.apiBaseUrl}/api/v1/developer/preview`;
2098
+ const url = `${this.options.apiBaseUrl}/api/v1/hosting/developer/preview`;
2099
2099
  const response = await fetch(url, {
2100
2100
  method: "POST",
2101
2101
  headers: {
@@ -2103,7 +2103,7 @@ var PreviewClient = class {
2103
2103
  Authorization: `Bearer ${this.options.apiKey}`
2104
2104
  },
2105
2105
  body: JSON.stringify({
2106
- schema,
2106
+ schemaJson: JSON.stringify(schema),
2107
2107
  device: this.options.device
2108
2108
  }),
2109
2109
  signal: AbortSignal.timeout(3e4)
@@ -2122,7 +2122,7 @@ var PreviewClient = class {
2122
2122
  * WebSocket 연결 수립 및 이벤트 핸들러 등록.
2123
2123
  */
2124
2124
  connectWebSocket() {
2125
- return new Promise((resolve13, reject) => {
2125
+ return new Promise((resolve15, reject) => {
2126
2126
  if (!this.session) {
2127
2127
  reject(new Error("\uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2128
2128
  return;
@@ -2136,7 +2136,7 @@ var PreviewClient = class {
2136
2136
  this.retryCount = 0;
2137
2137
  this.options.onStatusChange?.(true);
2138
2138
  this.startPingInterval();
2139
- resolve13();
2139
+ resolve15();
2140
2140
  });
2141
2141
  ws.on("message", (data) => {
2142
2142
  this.handleMessage(data);
@@ -2469,87 +2469,17 @@ import chalk8 from "chalk";
2469
2469
 
2470
2470
  // src/lib/local-validator.ts
2471
2471
  var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
2472
- // Element (9)
2472
+ // Primitives (10)
2473
+ "container",
2473
2474
  "text-block",
2474
2475
  "heading-block",
2475
2476
  "button-block",
2476
2477
  "image-block",
2477
- "video-block",
2478
+ "embed-block",
2479
+ "icon-block",
2480
+ "input-block",
2478
2481
  "divider",
2479
2482
  "spacer",
2480
- "icon-block",
2481
- "container",
2482
- // Feature (70)
2483
- "image-slider",
2484
- "map-block",
2485
- "contact-form",
2486
- "demolition-calculator",
2487
- "floating-social-widget",
2488
- "modal-block",
2489
- "sticky-cta-bar",
2490
- "product-grid",
2491
- "cart-widget",
2492
- "product-gallery",
2493
- "product-price",
2494
- "stock-badge",
2495
- "variant-selector",
2496
- "product-filter",
2497
- "product-search",
2498
- "related-products",
2499
- "add-to-cart",
2500
- "cart-contents",
2501
- "cart-summary",
2502
- "coupon-input",
2503
- "checkout-form",
2504
- "order-confirmation",
2505
- "order-history",
2506
- "order-lookup",
2507
- "review-list",
2508
- "cart-drawer",
2509
- "wishlist",
2510
- "recently-viewed",
2511
- "booking-calendar",
2512
- "medical-booking-block",
2513
- "booking-button",
2514
- "service-detail-block",
2515
- "booking-service-list",
2516
- "booking-service-detail",
2517
- "booking-checkout",
2518
- "booking-confirmation",
2519
- "booking-my-bookings",
2520
- "booking-staff-list",
2521
- "booking-guest-cancel",
2522
- "booking-class-schedule",
2523
- "booking-course-detail",
2524
- "booking-course-progress",
2525
- "booking-resource-calendar",
2526
- "booking-resource-list",
2527
- "auth-block",
2528
- "member-profile-block",
2529
- "member-only-section",
2530
- "board-block",
2531
- "board-detail-block",
2532
- "faq-accordion",
2533
- "gallery-block",
2534
- "before-after-slider",
2535
- "testimonials-section",
2536
- "tabs-section",
2537
- "countdown-timer",
2538
- "newsletter",
2539
- "marquee-block",
2540
- "before-after-gallery",
2541
- "content-showcase",
2542
- "staff-showcase",
2543
- "site-menu",
2544
- "stats-counter",
2545
- "scroll-to-top",
2546
- "anchor-nav",
2547
- "trademark-search-block",
2548
- "trademark-detail-block",
2549
- "nice-class-browser-block",
2550
- "lottie-block",
2551
- "model-block",
2552
- "image-sequence-block",
2553
2483
  // Structure (6)
2554
2484
  "header-block",
2555
2485
  "footer-block",
@@ -2557,20 +2487,12 @@ var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
2557
2487
  "main-block",
2558
2488
  "aside-block",
2559
2489
  "article-block",
2560
- // Pattern (13, legacy but valid)
2561
- "hero",
2562
- "cta-banner",
2563
- "feature-grid",
2564
- "pricing-table",
2565
- "team-profile-section",
2566
- "logo-cloud",
2567
- "social-proof",
2568
- "timeline",
2569
- "announcement-list",
2570
- "service-card-grid",
2571
- "business-hours",
2572
- "split-landing-hero",
2573
- "event-banner"
2490
+ // Specialized (5)
2491
+ "site-menu",
2492
+ "floating-action-widget",
2493
+ "sticky-cta-bar",
2494
+ "cookie-consent-bar",
2495
+ "announcement-bar"
2574
2496
  ]);
2575
2497
  function validateSchemaLocal(schema) {
2576
2498
  const errors = [];
@@ -2579,37 +2501,34 @@ function validateSchemaLocal(schema) {
2579
2501
  return errors;
2580
2502
  }
2581
2503
  const s = schema;
2582
- if (!s.schemaVersion && !s.version) {
2583
- errors.push({ severity: "error", message: '\uD544\uC218 \uD544\uB4DC \uB204\uB77D: "schemaVersion"', path: "schemaVersion", step: 1 });
2504
+ if (!s.version) {
2505
+ errors.push({ severity: "error", message: '\uD544\uC218 \uD544\uB4DC \uB204\uB77D: "version"', path: "version", step: 1 });
2584
2506
  }
2585
- if (!s.global && !s.settings) {
2586
- errors.push({ severity: "error", message: '\uD544\uC218 \uD544\uB4DC \uB204\uB77D: "global"', path: "global", step: 1 });
2507
+ if (!s.name) {
2508
+ errors.push({ severity: "warning", message: '\uD544\uB4DC \uB204\uB77D: "name" (\uC0AC\uC774\uD2B8\uBA85)', path: "name", step: 1 });
2587
2509
  }
2588
2510
  if (!s.pages) {
2589
2511
  errors.push({ severity: "error", message: '\uD544\uC218 \uD544\uB4DC \uB204\uB77D: "pages"', path: "pages", step: 1 });
2590
2512
  }
2591
- const version2 = s.schemaVersion ?? s.version;
2513
+ const version2 = s.version;
2592
2514
  if (version2 && typeof version2 === "string") {
2593
2515
  const parts = version2.split(".").map(Number);
2594
2516
  if (parts[0] !== 1 || parts[1] !== void 0 && parts[1] < 15) {
2595
2517
  errors.push({
2596
2518
  severity: "warning",
2597
- message: `\uC2A4\uD0A4\uB9C8 \uBC84\uC804 "${version2}"\uC774 \uC624\uB798\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCD5C\uC2E0: 1.20.0`,
2519
+ message: `\uC2A4\uD0A4\uB9C8 \uBC84\uC804 "${version2}"\uC774 \uC624\uB798\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCD5C\uC2E0: 1.21.0`,
2598
2520
  path: "schemaVersion",
2599
2521
  step: 1
2600
2522
  });
2601
2523
  }
2602
2524
  }
2603
- const global = s.global ?? s.settings;
2604
- if (global && typeof global === "object") {
2605
- if (!global.name && !global.title) {
2606
- errors.push({
2607
- severity: "error",
2608
- message: "global.name (\uB610\uB294 settings.title) \uC740 \uD544\uC218\uC785\uB2C8\uB2E4.",
2609
- path: "global.name",
2610
- step: 2
2611
- });
2612
- }
2525
+ if (s.pages && !Array.isArray(s.pages)) {
2526
+ errors.push({
2527
+ severity: "error",
2528
+ message: "pages\uB294 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4. (object map \uD615\uC2DD\uC740 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4)",
2529
+ path: "pages",
2530
+ step: 1
2531
+ });
2613
2532
  }
2614
2533
  if (!s.pages) return errors;
2615
2534
  const pages = s.pages;
@@ -2923,11 +2842,11 @@ async function computeFileHashes(localRefs) {
2923
2842
  return results;
2924
2843
  }
2925
2844
  async function computeSha256(filePath) {
2926
- return new Promise((resolve13, reject) => {
2845
+ return new Promise((resolve15, reject) => {
2927
2846
  const hash = createHash("sha256");
2928
2847
  const stream = createReadStream(filePath);
2929
2848
  stream.on("data", (chunk) => hash.update(chunk));
2930
- stream.on("end", () => resolve13(hash.digest("hex")));
2849
+ stream.on("end", () => resolve15(hash.digest("hex")));
2931
2850
  stream.on("error", (err) => reject(err));
2932
2851
  });
2933
2852
  }
@@ -2976,14 +2895,14 @@ function createConcurrencyLimiter(concurrency) {
2976
2895
  function next() {
2977
2896
  if (queue.length > 0 && active < concurrency) {
2978
2897
  active++;
2979
- const resolve13 = queue.shift();
2980
- resolve13?.();
2898
+ const resolve15 = queue.shift();
2899
+ resolve15?.();
2981
2900
  }
2982
2901
  }
2983
2902
  return async function limit(fn) {
2984
2903
  if (active >= concurrency) {
2985
- await new Promise((resolve13) => {
2986
- queue.push(resolve13);
2904
+ await new Promise((resolve15) => {
2905
+ queue.push(resolve15);
2987
2906
  });
2988
2907
  } else {
2989
2908
  active++;
@@ -3905,77 +3824,881 @@ async function commandGenerate(options) {
3905
3824
  }
3906
3825
  }
3907
3826
 
3908
- // src/commands/compare.ts
3827
+ // src/commands/analyze.ts
3909
3828
  import chalk15 from "chalk";
3910
- import { resolve as resolve12 } from "path";
3911
- import { execSync, spawnSync } from "child_process";
3829
+ import { resolve as resolve13, join as join2 } from "path";
3830
+ import { mkdir as mkdir4 } from "fs/promises";
3831
+
3832
+ // src/scripts/analyze-reference.ts
3833
+ import { spawnSync } from "child_process";
3834
+ import { writeFile as writeFile7, mkdir as mkdir3 } from "fs/promises";
3835
+ import { join } from "path";
3836
+ var VIEWPORTS = [
3837
+ { name: "mobile", width: 375, height: 812 },
3838
+ { name: "tablet", width: 768, height: 1024 },
3839
+ { name: "laptop", width: 1280, height: 800 },
3840
+ { name: "desktop", width: 1536, height: 900 }
3841
+ ];
3842
+ var EXTRACTION_SCRIPT = `
3843
+ (() => {
3844
+ // \u2500\u2500 Helpers \u2500\u2500
3845
+ function getComputedProp(el, prop) {
3846
+ return window.getComputedStyle(el).getPropertyValue(prop).trim();
3847
+ }
3848
+
3849
+ function parsePixel(val) {
3850
+ return Math.round(parseFloat(val) || 0);
3851
+ }
3852
+
3853
+ function rgbToHex(rgb) {
3854
+ if (!rgb || rgb === 'transparent') return 'transparent';
3855
+ if (rgb.startsWith('#')) return rgb;
3856
+ const match = rgb.match(/\\d+/g);
3857
+ if (!match || match.length < 3) return rgb;
3858
+ const [r, g, b] = match.map(Number);
3859
+ return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
3860
+ }
3861
+
3862
+ function detectLayout(el) {
3863
+ const display = getComputedProp(el, 'display');
3864
+ const flexDir = getComputedProp(el, 'flex-direction');
3865
+ const position = getComputedProp(el, 'position');
3866
+ if (display.includes('grid')) return 'grid';
3867
+ if (position === 'absolute' || position === 'fixed') return 'absolute';
3868
+ if (display.includes('flex')) {
3869
+ return flexDir === 'row' ? 'flex-row' : 'flex-column';
3870
+ }
3871
+ return 'block';
3872
+ }
3873
+
3874
+ function inferSectionRole(el) {
3875
+ const tag = el.tagName.toLowerCase();
3876
+ const cls = (el.className || '').toString().toLowerCase();
3877
+ const id = (el.id || '').toLowerCase();
3878
+ const text = (el.textContent || '').slice(0, 200).toLowerCase();
3879
+ const combined = tag + ' ' + cls + ' ' + id + ' ' + text;
3880
+
3881
+ if (tag === 'header' || tag === 'nav') return 'header';
3882
+ if (tag === 'footer') return 'footer';
3883
+ if (/hero|banner|jumbotron|splash|main-?visual/.test(combined)) return 'hero';
3884
+ if (/feature|service|benefit|what-?we/.test(combined)) return 'features';
3885
+ if (/testimonial|review|feedback|client|customer/.test(combined)) return 'testimonials';
3886
+ if (/faq|accordion|question|q-?and-?a/.test(combined)) return 'faq';
3887
+ if (/team|staff|member|about-?us|who-?we/.test(combined)) return 'team';
3888
+ if (/pricing|plan|package/.test(combined)) return 'pricing';
3889
+ if (/contact|inquiry|form|cta|call-?to-?action|get-?started/.test(combined)) return 'cta';
3890
+ if (/gallery|portfolio|work|project|showcase/.test(combined)) return 'gallery';
3891
+ if (/blog|news|article|post/.test(combined)) return 'blog';
3892
+ if (/partner|client|logo|brand|trust/.test(combined)) return 'partners';
3893
+ if (/map|location|address|direction/.test(combined)) return 'map';
3894
+ if (/stat|counter|number|achievement/.test(combined)) return 'stats';
3895
+ return 'section';
3896
+ }
3897
+
3898
+ function guessAspectRatio(w, h) {
3899
+ if (!w || !h) return 'unknown';
3900
+ const r = w / h;
3901
+ if (Math.abs(r - 16/9) < 0.15) return '16:9';
3902
+ if (Math.abs(r - 4/3) < 0.15) return '4:3';
3903
+ if (Math.abs(r - 3/2) < 0.15) return '3:2';
3904
+ if (Math.abs(r - 1) < 0.15) return '1:1';
3905
+ if (Math.abs(r - 9/16) < 0.15) return '9:16';
3906
+ if (Math.abs(r - 21/9) < 0.15) return '21:9';
3907
+ return w + ':' + h;
3908
+ }
3909
+
3910
+ function inferImageRole(img, parentRole) {
3911
+ const src = (img.src || '').toLowerCase();
3912
+ const alt = (img.alt || '').toLowerCase();
3913
+ const cls = (img.className || '').toString().toLowerCase();
3914
+ const parent = img.closest('section, header, footer, [class*=hero], [class*=banner]');
3915
+ const pRole = parentRole || inferSectionRole(parent || img.parentElement);
3916
+
3917
+ if (/logo/.test(cls + ' ' + alt + ' ' + src)) return 'logo';
3918
+ if (/icon|svg/.test(cls)) return 'icon';
3919
+ if (pRole === 'hero') return 'hero-bg';
3920
+ if (pRole === 'team') return 'team-photo';
3921
+ if (pRole === 'gallery') return 'gallery-item';
3922
+ if (pRole === 'partners') return 'partner-logo';
3923
+ if (pRole === 'testimonials') return 'avatar';
3924
+
3925
+ const rect = img.getBoundingClientRect();
3926
+ if (rect.width > window.innerWidth * 0.8 && rect.height > 300) return 'hero-bg';
3927
+ if (rect.width < 80 && rect.height < 80) return 'icon';
3928
+ return 'card-thumbnail';
3929
+ }
3930
+
3931
+ // \u2500\u2500 1. Structure \u2500\u2500
3932
+ const sectionSelectors = 'body > header, body > footer, body > nav, body > main, body > section, body > div, body > article, body > aside';
3933
+ const topLevelEls = document.querySelectorAll(sectionSelectors);
3934
+ const sections = [];
3935
+ let maxDepth = 0;
3936
+
3937
+ topLevelEls.forEach(el => {
3938
+ // \uC228\uACA8\uC9C4 \uC694\uC18C \uB610\uB294 \uB108\uBE44 0\uC778 \uC694\uC18C \uC81C\uC678
3939
+ const rect = el.getBoundingClientRect();
3940
+ if (rect.height < 10) return;
3941
+
3942
+ const tag = el.tagName.toLowerCase();
3943
+ const layout = detectLayout(el);
3944
+ const bgImg = getComputedProp(el, 'background-image');
3945
+
3946
+ sections.push({
3947
+ tag,
3948
+ role: inferSectionRole(el),
3949
+ childCount: el.children.length,
3950
+ layout,
3951
+ gridColumns: layout === 'grid' ? getComputedProp(el, 'grid-template-columns') : undefined,
3952
+ hasBackgroundImage: bgImg !== 'none' && bgImg !== '',
3953
+ });
3954
+
3955
+ // nesting depth
3956
+ let depth = 0;
3957
+ let cursor = el;
3958
+ while (cursor.firstElementChild) {
3959
+ depth++;
3960
+ cursor = cursor.firstElementChild;
3961
+ }
3962
+ if (depth > maxDepth) maxDepth = depth;
3963
+ });
3964
+
3965
+ // heading hierarchy
3966
+ const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
3967
+ const headingHierarchy = headings.map(h => h.tagName.toLowerCase() + ': ' + (h.textContent || '').trim().slice(0, 80));
3968
+
3969
+ // \u2500\u2500 2. Design Tokens \u2500\u2500
3970
+
3971
+ // 2a. Colors \u2014 \uBAA8\uB4E0 \uC694\uC18C\uC758 color, backgroundColor \uC218\uC9D1
3972
+ const colorMap = {};
3973
+ const bgColorMap = {};
3974
+ const allEls = document.querySelectorAll('*');
3975
+ const sampleEls = allEls.length > 500
3976
+ ? Array.from(allEls).filter((_, i) => i % Math.ceil(allEls.length / 500) === 0)
3977
+ : Array.from(allEls);
3978
+
3979
+ sampleEls.forEach(el => {
3980
+ const color = rgbToHex(getComputedProp(el, 'color'));
3981
+ const bg = rgbToHex(getComputedProp(el, 'background-color'));
3982
+ if (color && color !== 'transparent') colorMap[color] = (colorMap[color] || 0) + 1;
3983
+ if (bg && bg !== 'transparent' && bg !== '#000000') bgColorMap[bg] = (bgColorMap[bg] || 0) + 1;
3984
+ });
3985
+
3986
+ const sortedColors = Object.entries(colorMap).sort((a, b) => b[1] - a[1]);
3987
+ const sortedBgs = Object.entries(bgColorMap).sort((a, b) => b[1] - a[1]);
3988
+
3989
+ // primary = body\uB098 heading\uC758 \uAC00\uC7A5 \uBE48\uBC88\uD55C \uD14D\uC2A4\uD2B8 \uC0C9\uC0C1\uC774 \uC544\uB2CC \uC0C9 \uC911 1\uC704
3990
+ const bodyColor = rgbToHex(getComputedProp(document.body, 'color'));
3991
+ const bodyBg = rgbToHex(getComputedProp(document.body, 'background-color'));
3992
+ const nonBodyColors = sortedColors.filter(([c]) => c !== bodyColor && c !== '#ffffff' && c !== '#000000');
3993
+ const allPalette = [...new Set([...sortedColors.map(c => c[0]), ...sortedBgs.map(c => c[0])])].slice(0, 20);
3994
+
3995
+ // 2b. Typography
3996
+ const typographyScale = {};
3997
+ ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'button', 'span', 'li'].forEach(tag => {
3998
+ const el = document.querySelector(tag);
3999
+ if (el) {
4000
+ typographyScale[tag] = {
4001
+ size: getComputedProp(el, 'font-size'),
4002
+ weight: getComputedProp(el, 'font-weight'),
4003
+ lineHeight: getComputedProp(el, 'line-height'),
4004
+ };
4005
+ }
4006
+ });
4007
+
4008
+ const fontFamilySet = new Set();
4009
+ sampleEls.forEach(el => {
4010
+ const ff = getComputedProp(el, 'font-family');
4011
+ if (ff) fontFamilySet.add(ff.split(',')[0].trim().replace(/['"]/g, ''));
4012
+ });
4013
+
4014
+ // 2c. Spacing
4015
+ const sectionGaps = [];
4016
+ for (let i = 1; i < sections.length; i++) {
4017
+ const prev = topLevelEls[i - 1];
4018
+ const curr = topLevelEls[i];
4019
+ if (prev && curr) {
4020
+ const prevRect = prev.getBoundingClientRect();
4021
+ const currRect = curr.getBoundingClientRect();
4022
+ const gap = currRect.top - prevRect.bottom;
4023
+ if (gap > 0 && gap < 500) sectionGaps.push(gap);
4024
+ }
4025
+ }
4026
+
4027
+ const firstContent = document.querySelector('main, [class*=container], [class*=wrapper], body > div > div');
4028
+ const contentPadding = firstContent ? parsePixel(getComputedProp(firstContent, 'padding-left')) : 16;
4029
+
4030
+ // card gap \uCD94\uC815: \uCCAB \uBC88\uC9F8 grid/flex \uCEE8\uD14C\uC774\uB108\uC758 gap
4031
+ let cardGap = 0;
4032
+ const gridContainers = document.querySelectorAll('[style*="grid"], [class*="grid"], [style*="flex"]');
4033
+ for (const gc of gridContainers) {
4034
+ const gap = parsePixel(getComputedProp(gc, 'gap') || getComputedProp(gc, 'column-gap'));
4035
+ if (gap > 0) { cardGap = gap; break; }
4036
+ }
4037
+
4038
+ // base unit \uCD94\uC815 (\uAC00\uC7A5 \uD754\uD55C \uAC04\uACA9\uAC12\uC758 \uCD5C\uB300\uACF5\uC57D\uC218)
4039
+ const allGaps = [...sectionGaps, contentPadding, cardGap].filter(v => v > 0);
4040
+ function gcd(a, b) { return b === 0 ? a : gcd(b, a % b); }
4041
+ const baseUnit = allGaps.length > 1 ? allGaps.reduce((a, b) => gcd(a, b)) : (allGaps[0] || 8);
4042
+
4043
+ // 2d. BorderRadius
4044
+ const radiusValues = [];
4045
+ sampleEls.forEach(el => {
4046
+ const br = parsePixel(getComputedProp(el, 'border-radius'));
4047
+ if (br > 0) radiusValues.push(br);
4048
+ });
4049
+ const uniqueRadii = [...new Set(radiusValues)].sort((a, b) => a - b);
4050
+
4051
+ // \u2500\u2500 3. Interactions \u2500\u2500
4052
+ const styleSheets = Array.from(document.styleSheets);
4053
+ const animationNames = new Set();
4054
+ const transitionProps = new Set();
4055
+
4056
+ try {
4057
+ styleSheets.forEach(ss => {
4058
+ try {
4059
+ const rules = Array.from(ss.cssRules || []);
4060
+ rules.forEach(rule => {
4061
+ if (rule instanceof CSSKeyframesRule) {
4062
+ animationNames.add(rule.name);
4063
+ }
4064
+ if (rule instanceof CSSStyleRule) {
4065
+ const style = rule.style;
4066
+ if (style.animationName && style.animationName !== 'none') {
4067
+ animationNames.add(style.animationName);
4068
+ }
4069
+ if (style.transition && style.transition !== 'none' && style.transition !== 'all 0s ease 0s') {
4070
+ transitionProps.add(style.transition);
4071
+ }
4072
+ }
4073
+ });
4074
+ } catch { /* cross-origin sheets */ }
4075
+ });
4076
+ } catch { /* stylesheet access error */ }
4077
+
4078
+ const hasCarousel = !!(
4079
+ document.querySelector('[class*=carousel], [class*=slider], [class*=swiper], [class*=slick]') ||
4080
+ document.querySelector('[data-slick], [data-swiper]')
4081
+ );
4082
+ const hasAccordion = !!(
4083
+ document.querySelector('details, [class*=accordion], [class*=collapse], [data-toggle=collapse]')
4084
+ );
4085
+ const hasModal = !!(
4086
+ document.querySelector('dialog, [class*=modal], [class*=lightbox], [role=dialog]')
4087
+ );
4088
+ const hasStickyHeader = (() => {
4089
+ const header = document.querySelector('header, [class*=header], nav');
4090
+ if (!header) return false;
4091
+ const pos = getComputedProp(header, 'position');
4092
+ return pos === 'sticky' || pos === 'fixed';
4093
+ })();
4094
+ const hasScrollAnimations = !!(
4095
+ document.querySelector('[class*=aos], [data-aos], [class*=wow], [class*=scroll-animate], [class*=animate-on-scroll]') ||
4096
+ animationNames.size > 2
4097
+ );
4098
+ const hasParallax = !!(
4099
+ document.querySelector('[class*=parallax], [data-parallax], [class*=jarallax]') ||
4100
+ (() => {
4101
+ let found = false;
4102
+ sampleEls.forEach(el => {
4103
+ const ba = getComputedProp(el, 'background-attachment');
4104
+ if (ba === 'fixed') found = true;
4105
+ });
4106
+ return found;
4107
+ })()
4108
+ );
4109
+ const hasHoverEffects = transitionProps.size > 0;
4110
+
4111
+ // \u2500\u2500 4. Images \u2500\u2500
4112
+ const imgEls = document.querySelectorAll('img, picture source, [style*="background-image"]');
4113
+ const images = [];
4114
+
4115
+ imgEls.forEach(el => {
4116
+ let src = '';
4117
+ let alt = '';
4118
+ let width = 0;
4119
+ let height = 0;
4120
+
4121
+ if (el.tagName === 'IMG') {
4122
+ src = el.src || el.dataset.src || '';
4123
+ alt = el.alt || '';
4124
+ width = el.naturalWidth || el.width;
4125
+ height = el.naturalHeight || el.height;
4126
+ } else if (el.tagName === 'SOURCE') {
4127
+ src = el.srcset ? el.srcset.split(',')[0].trim().split(' ')[0] : '';
4128
+ } else {
4129
+ const bg = getComputedProp(el, 'background-image');
4130
+ const match = bg.match(/url\\(["']?(.+?)["']?\\)/);
4131
+ if (match) src = match[1];
4132
+ }
4133
+
4134
+ if (!src || src.startsWith('data:image/svg') || src.includes('.svg')) return;
4135
+
4136
+ const rect = el.getBoundingClientRect();
4137
+ if (!width) width = Math.round(rect.width);
4138
+ if (!height) height = Math.round(rect.height);
4139
+ if (width < 20 || height < 20) return;
4140
+
4141
+ const parentSection = el.closest('section, header, footer, main, [class*=hero], [class*=banner]');
4142
+ const parentRole = parentSection ? inferSectionRole(parentSection) : 'unknown';
4143
+
4144
+ images.push({
4145
+ src: src.slice(0, 500),
4146
+ alt: (alt || '').slice(0, 200),
4147
+ width,
4148
+ height,
4149
+ aspectRatio: guessAspectRatio(width, height),
4150
+ role: inferImageRole(el, parentRole),
4151
+ dominantColor: '', // \uC11C\uBC84 \uC0AC\uC774\uB4DC\uC5D0\uC11C \uCD94\uCD9C \uB610\uB294 Vision \uBD84\uC11D
4152
+ position: parentRole,
4153
+ });
4154
+ });
4155
+
4156
+ // \u2500\u2500 5. Gaps (V2 \uBE14\uB85D\uC73C\uB85C \uB9E4\uD551 \uBD88\uAC00\uB2A5\uD55C \uAE30\uB2A5) \u2500\u2500
4157
+ const gaps = [];
4158
+
4159
+ // video background
4160
+ if (document.querySelector('video[autoplay], video[muted]')) {
4161
+ gaps.push('video-background-autoplay: \uC790\uB3D9 \uC7AC\uC0DD \uBE44\uB514\uC624 \uBC30\uACBD');
4162
+ }
4163
+ // canvas / WebGL
4164
+ if (document.querySelector('canvas')) {
4165
+ gaps.push('canvas-webgl: Canvas \uB610\uB294 WebGL \uAE30\uBC18 \uC778\uD130\uB799\uC158');
4166
+ }
4167
+ // custom cursor
4168
+ const bodyCursor = getComputedProp(document.body, 'cursor');
4169
+ if (bodyCursor !== 'auto' && bodyCursor !== 'default') {
4170
+ gaps.push('custom-cursor: \uCEE4\uC2A4\uD140 \uB9C8\uC6B0\uC2A4 \uCEE4\uC11C');
4171
+ }
4172
+ // 3D transforms
4173
+ let has3d = false;
4174
+ sampleEls.slice(0, 100).forEach(el => {
4175
+ const tf = getComputedProp(el, 'transform');
4176
+ if (tf.includes('matrix3d') || tf.includes('perspective')) has3d = true;
4177
+ });
4178
+ if (has3d) {
4179
+ gaps.push('3d-transforms: CSS 3D \uBCC0\uD658 \uC0AC\uC6A9');
4180
+ }
4181
+ // SVG animation
4182
+ if (document.querySelector('svg animate, svg animateTransform, svg animateMotion')) {
4183
+ gaps.push('svg-animation: SVG \uC778\uB77C\uC778 \uC560\uB2C8\uBA54\uC774\uC158');
4184
+ }
4185
+ // marquee / ticker
4186
+ if (document.querySelector('marquee, [class*=marquee], [class*=ticker]')) {
4187
+ gaps.push('marquee-ticker: \uC218\uD3C9 \uC2A4\uD06C\uB864 \uD14D\uC2A4\uD2B8/\uC774\uBBF8\uC9C0');
4188
+ }
4189
+ // infinite scroll
4190
+ if (document.querySelector('[class*=infinite], [data-infinite]')) {
4191
+ gaps.push('infinite-scroll: \uBB34\uD55C \uC2A4\uD06C\uB864 \uB85C\uB529');
4192
+ }
4193
+ // chat widget (\uC678\uBD80 \uC5F0\uB3D9\uC740 embed\uC73C\uB85C \uAC00\uB2A5\uD558\uC9C0\uB9CC \uAE30\uB85D)
4194
+ if (document.querySelector('[class*=chat-widget], [id*=chat], #ch-plugin, .channel-talk')) {
4195
+ gaps.push('chat-widget: \uC678\uBD80 \uCC44\uD305 \uC704\uC82F (embed-block integration \uD544\uC694)');
4196
+ }
4197
+
4198
+ // \u2500\u2500 Return \u2500\u2500
4199
+ return {
4200
+ structure: {
4201
+ sections,
4202
+ headingHierarchy,
4203
+ totalSections: sections.length,
4204
+ nestingDepth: maxDepth,
4205
+ },
4206
+ designTokens: {
4207
+ colors: {
4208
+ primary: (nonBodyColors[0] || sortedColors[0] || ['#000000'])[0],
4209
+ secondary: (nonBodyColors[1] || sortedColors[1] || ['#666666'])[0],
4210
+ background: bodyBg || '#ffffff',
4211
+ text: bodyColor || '#000000',
4212
+ accent: (nonBodyColors[2] || sortedColors[2] || ['#0066ff'])[0],
4213
+ palette: allPalette,
4214
+ },
4215
+ typography: {
4216
+ fontFamilies: [...fontFamilySet].slice(0, 5),
4217
+ scale: typographyScale,
4218
+ },
4219
+ spacing: {
4220
+ sectionGap: sectionGaps.length > 0 ? Math.round(sectionGaps.reduce((a, b) => a + b, 0) / sectionGaps.length) : 80,
4221
+ contentPadding,
4222
+ cardGap: cardGap || 16,
4223
+ baseUnit: Math.max(baseUnit, 4),
4224
+ },
4225
+ borderRadius: {
4226
+ small: uniqueRadii[0] || 0,
4227
+ medium: uniqueRadii[Math.floor(uniqueRadii.length / 2)] || 0,
4228
+ large: uniqueRadii[uniqueRadii.length - 1] || 0,
4229
+ },
4230
+ },
4231
+ interactions: {
4232
+ hasScrollAnimations,
4233
+ hasHoverEffects,
4234
+ hasCarousel,
4235
+ hasAccordion,
4236
+ hasModal,
4237
+ hasStickyHeader,
4238
+ hasParallax,
4239
+ detectedAnimations: [...animationNames].slice(0, 20),
4240
+ detectedTransitions: [...transitionProps].slice(0, 20),
4241
+ },
4242
+ images,
4243
+ gaps,
4244
+ };
4245
+ })()
4246
+ `;
4247
+ async function analyzeReference(options) {
4248
+ const { url, outputDir, timeout = 3e4 } = options;
4249
+ const screenshotDir = join(outputDir, "screenshots");
4250
+ await mkdir3(screenshotDir, { recursive: true });
4251
+ const screenshots = {};
4252
+ for (const vp of VIEWPORTS) {
4253
+ const filePath = join(screenshotDir, `${vp.name}-${vp.width}px.png`);
4254
+ captureScreenshot(url, filePath, vp.width, vp.height, timeout);
4255
+ screenshots[vp.name] = filePath;
4256
+ }
4257
+ const extractedData = await extractPageData(url, timeout);
4258
+ const analysis = {
4259
+ url,
4260
+ analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
4261
+ screenshots: {
4262
+ mobile: screenshots.mobile,
4263
+ tablet: screenshots.tablet,
4264
+ laptop: screenshots.laptop,
4265
+ desktop: screenshots.desktop
4266
+ },
4267
+ ...extractedData
4268
+ };
4269
+ const analysisPath = join(outputDir, "analysis.json");
4270
+ await writeFile7(analysisPath, JSON.stringify(analysis, null, 2), "utf-8");
4271
+ return analysis;
4272
+ }
4273
+ function captureScreenshot(url, outputPath, width, height, timeout) {
4274
+ const result = spawnSync("npx", [
4275
+ "playwright",
4276
+ "screenshot",
4277
+ "--browser",
4278
+ "chromium",
4279
+ "--viewport-size",
4280
+ `${width},${height}`,
4281
+ "--wait-for-timeout",
4282
+ "3000",
4283
+ "--full-page",
4284
+ url,
4285
+ outputPath
4286
+ ], {
4287
+ stdio: "pipe",
4288
+ timeout: timeout + 15e3
4289
+ });
4290
+ if (result.status !== 0) {
4291
+ const stderr = result.stderr?.toString() ?? "";
4292
+ throw new Error(`\uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC2E4\uD328 (${width}px): ${stderr || "unknown error"}`);
4293
+ }
4294
+ }
4295
+ async function extractPageData(url, timeout) {
4296
+ const scriptContent = `
4297
+ const { chromium } = require('playwright');
4298
+ (async () => {
4299
+ const browser = await chromium.launch({ headless: true });
4300
+ const context = await browser.newContext({
4301
+ viewport: { width: 1280, height: 800 },
4302
+ userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
4303
+ });
4304
+ const page = await context.newPage();
4305
+ await page.goto(${JSON.stringify(url)}, { waitUntil: 'networkidle', timeout: ${timeout} });
4306
+ await page.waitForTimeout(2000);
4307
+ const data = await page.evaluate(${JSON.stringify(EXTRACTION_SCRIPT)});
4308
+ await browser.close();
4309
+ process.stdout.write(JSON.stringify(data));
4310
+ })().catch(e => {
4311
+ process.stderr.write(e.message);
4312
+ process.exit(1);
4313
+ });
4314
+ `;
4315
+ const result = spawnSync("node", ["-e", scriptContent], {
4316
+ stdio: "pipe",
4317
+ timeout: timeout + 3e4,
4318
+ env: { ...process.env }
4319
+ });
4320
+ if (result.status !== 0) {
4321
+ const stderr = result.stderr?.toString() ?? "";
4322
+ console.error(`DOM \uCD94\uCD9C \uC2E4\uD328 (fallback \uC0AC\uC6A9): ${stderr}`);
4323
+ return createFallbackData();
4324
+ }
4325
+ try {
4326
+ const output = result.stdout.toString();
4327
+ return JSON.parse(output);
4328
+ } catch {
4329
+ console.error("DOM \uCD94\uCD9C \uACB0\uACFC \uD30C\uC2F1 \uC2E4\uD328 (fallback \uC0AC\uC6A9)");
4330
+ return createFallbackData();
4331
+ }
4332
+ }
4333
+ function createFallbackData() {
4334
+ return {
4335
+ structure: {
4336
+ sections: [],
4337
+ headingHierarchy: [],
4338
+ totalSections: 0,
4339
+ nestingDepth: 0
4340
+ },
4341
+ designTokens: {
4342
+ colors: {
4343
+ primary: "#000000",
4344
+ secondary: "#666666",
4345
+ background: "#ffffff",
4346
+ text: "#000000",
4347
+ accent: "#0066ff",
4348
+ palette: []
4349
+ },
4350
+ typography: {
4351
+ fontFamilies: [],
4352
+ scale: {}
4353
+ },
4354
+ spacing: {
4355
+ sectionGap: 80,
4356
+ contentPadding: 16,
4357
+ cardGap: 16,
4358
+ baseUnit: 8
4359
+ },
4360
+ borderRadius: { small: 0, medium: 0, large: 0 }
4361
+ },
4362
+ interactions: {
4363
+ hasScrollAnimations: false,
4364
+ hasHoverEffects: false,
4365
+ hasCarousel: false,
4366
+ hasAccordion: false,
4367
+ hasModal: false,
4368
+ hasStickyHeader: false,
4369
+ hasParallax: false,
4370
+ detectedAnimations: [],
4371
+ detectedTransitions: []
4372
+ },
4373
+ images: [],
4374
+ gaps: []
4375
+ };
4376
+ }
4377
+
4378
+ // src/commands/analyze.ts
4379
+ async function commandAnalyze(url, options) {
4380
+ console.log(chalk15.bold("\n\uB808\uD37C\uB7F0\uC2A4 \uBD84\uC11D (Reference Analysis)\n"));
4381
+ if (!url) {
4382
+ console.error(chalk15.red("URL\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
4383
+ console.error(chalk15.dim(" \uC608: npx @saeroon/cli analyze https://example.com"));
4384
+ process.exit(1);
4385
+ }
4386
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
4387
+ url = "https://" + url;
4388
+ }
4389
+ const domain = new URL(url).hostname.replace(/^www\./, "");
4390
+ const timestamp2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4391
+ const defaultDir = join2(".saeroon", "analysis", `${domain}-${timestamp2}`);
4392
+ const outputDir = resolve13(process.cwd(), options.outputDir ?? defaultDir);
4393
+ const timeout = parseInt(options.timeout ?? "30000", 10);
4394
+ await mkdir4(outputDir, { recursive: true });
4395
+ console.log(chalk15.dim(` URL: ${url}`));
4396
+ console.log(chalk15.dim(` \uCD9C\uB825: ${outputDir}`));
4397
+ console.log(chalk15.dim(` \uD0C0\uC784\uC544\uC6C3: ${timeout}ms`));
4398
+ console.log("");
4399
+ const analysisSpinner = spinner("\uB808\uD37C\uB7F0\uC2A4 \uBD84\uC11D \uC911... (\uC2A4\uD06C\uB9B0\uC0F7 4\uC7A5 + DOM/CSS \uCD94\uCD9C)");
4400
+ try {
4401
+ const analysis = await analyzeReference({ url, outputDir, timeout });
4402
+ analysisSpinner.stop(chalk15.green(" \uBD84\uC11D \uC644\uB8CC!"));
4403
+ console.log("");
4404
+ console.log(chalk15.bold("\uAD6C\uC870"));
4405
+ console.log(chalk15.dim(` \uC139\uC158: ${analysis.structure.totalSections}\uAC1C`));
4406
+ console.log(chalk15.dim(` \uCD5C\uB300 \uC911\uCCA9: ${analysis.structure.nestingDepth}`));
4407
+ if (analysis.structure.headingHierarchy.length > 0) {
4408
+ console.log(chalk15.dim(" Heading hierarchy:"));
4409
+ analysis.structure.headingHierarchy.slice(0, 10).forEach((h) => {
4410
+ console.log(chalk15.dim(` ${h}`));
4411
+ });
4412
+ if (analysis.structure.headingHierarchy.length > 10) {
4413
+ console.log(chalk15.dim(` ... +${analysis.structure.headingHierarchy.length - 10}\uAC1C`));
4414
+ }
4415
+ }
4416
+ console.log("");
4417
+ console.log(chalk15.bold("\uB514\uC790\uC778 \uD1A0\uD070"));
4418
+ const { colors, typography, spacing } = analysis.designTokens;
4419
+ console.log(chalk15.dim(` Primary: ${colors.primary}`));
4420
+ console.log(chalk15.dim(` Secondary: ${colors.secondary}`));
4421
+ console.log(chalk15.dim(` Background: ${colors.background}`));
4422
+ console.log(chalk15.dim(` Text: ${colors.text}`));
4423
+ console.log(chalk15.dim(` Accent: ${colors.accent}`));
4424
+ console.log(chalk15.dim(` \uD3F0\uD2B8: ${typography.fontFamilies.join(", ") || "(\uCD94\uCD9C \uC2E4\uD328)"}`));
4425
+ console.log(chalk15.dim(` \uC139\uC158 \uAC04\uACA9: ${spacing.sectionGap}px`));
4426
+ console.log(chalk15.dim(` \uAE30\uBCF8 \uB2E8\uC704: ${spacing.baseUnit}px`));
4427
+ console.log("");
4428
+ console.log(chalk15.bold("\uC778\uD130\uB799\uC158"));
4429
+ const { interactions } = analysis;
4430
+ const detected = [
4431
+ interactions.hasStickyHeader && "Sticky Header",
4432
+ interactions.hasScrollAnimations && "Scroll Animations",
4433
+ interactions.hasCarousel && "Carousel",
4434
+ interactions.hasAccordion && "Accordion",
4435
+ interactions.hasModal && "Modal",
4436
+ interactions.hasParallax && "Parallax",
4437
+ interactions.hasHoverEffects && "Hover Effects"
4438
+ ].filter(Boolean);
4439
+ console.log(chalk15.dim(` \uAC10\uC9C0: ${detected.length > 0 ? detected.join(", ") : "(\uC5C6\uC74C)"}`));
4440
+ console.log("");
4441
+ console.log(chalk15.bold("\uC774\uBBF8\uC9C0"));
4442
+ console.log(chalk15.dim(` \uCD1D ${analysis.images.length}\uAC1C \uAC10\uC9C0`));
4443
+ const roleCount = {};
4444
+ analysis.images.forEach((img) => {
4445
+ roleCount[img.role] = (roleCount[img.role] || 0) + 1;
4446
+ });
4447
+ Object.entries(roleCount).forEach(([role, count]) => {
4448
+ console.log(chalk15.dim(` ${role}: ${count}\uAC1C`));
4449
+ });
4450
+ if (analysis.gaps.length > 0) {
4451
+ console.log("");
4452
+ console.log(chalk15.bold(chalk15.yellow("Gap \uAC10\uC9C0")));
4453
+ analysis.gaps.forEach((gap) => {
4454
+ console.log(chalk15.yellow(` \u26A0 ${gap}`));
4455
+ });
4456
+ }
4457
+ console.log("");
4458
+ console.log(chalk15.bold("\uC2A4\uD06C\uB9B0\uC0F7"));
4459
+ console.log(chalk15.dim(` mobile: ${analysis.screenshots.mobile}`));
4460
+ console.log(chalk15.dim(` tablet: ${analysis.screenshots.tablet}`));
4461
+ console.log(chalk15.dim(` laptop: ${analysis.screenshots.laptop}`));
4462
+ console.log(chalk15.dim(` desktop: ${analysis.screenshots.desktop}`));
4463
+ console.log("");
4464
+ console.log(chalk15.green.bold("\uBD84\uC11D \uC644\uB8CC!"));
4465
+ console.log(chalk15.dim(` \uACB0\uACFC: ${join2(outputDir, "analysis.json")}`));
4466
+ console.log("");
4467
+ } catch (error) {
4468
+ analysisSpinner.stop(chalk15.red(" \uBD84\uC11D \uC2E4\uD328"));
4469
+ console.error(chalk15.red(`
4470
+ ${error instanceof Error ? error.message : String(error)}`));
4471
+ console.log("");
4472
+ console.log(chalk15.dim("Playwright\uAC00 \uC124\uCE58\uB418\uC5B4 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694:"));
4473
+ console.log(chalk15.cyan(" npx playwright install chromium\n"));
4474
+ process.exit(1);
4475
+ }
4476
+ }
4477
+
4478
+ // src/commands/compare.ts
4479
+ import chalk16 from "chalk";
4480
+ import { writeFile as writeFile8, mkdir as mkdir5 } from "fs/promises";
4481
+ import { resolve as resolve14, join as join3 } from "path";
4482
+ import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
3912
4483
  async function commandCompare(options) {
3913
- console.log(chalk15.bold("\n\uC2DC\uAC01 \uBE44\uAD50 (Visual Diff)\n"));
4484
+ console.log(chalk16.bold("\n\uC2DC\uAC01 \uBE44\uAD50 (Visual Diff)\n"));
3914
4485
  if (!options.ref || !options.preview) {
3915
- console.error(chalk15.red("--ref <url> \uACFC --preview <url> \uC740 \uBAA8\uB450 \uD544\uC218\uC785\uB2C8\uB2E4."));
3916
- console.error(chalk15.dim(" \uC608: npx @saeroon/cli compare --ref https://example.com --preview https://preview.saeroon.com/abc"));
4486
+ console.error(chalk16.red("--ref <url> \uACFC --preview <url> \uC740 \uBAA8\uB450 \uD544\uC218\uC785\uB2C8\uB2E4."));
4487
+ console.error(chalk16.dim(" \uC608: npx @saeroon/cli compare --ref https://example.com --preview https://preview.saeroon.com/abc"));
3917
4488
  process.exit(1);
3918
4489
  }
3919
4490
  const playwrightAvailable = checkPlaywright();
3920
4491
  if (!playwrightAvailable) {
3921
- console.log(chalk15.yellow("Playwright\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
3922
- console.log(chalk15.dim(" \uB2E4\uC74C \uBA85\uB839\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694:"));
3923
- console.log(chalk15.cyan(" npx playwright install chromium\n"));
4492
+ console.log(chalk16.yellow("Playwright\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
4493
+ console.log(chalk16.dim(" \uB2E4\uC74C \uBA85\uB839\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694:"));
4494
+ console.log(chalk16.cyan(" npx playwright install chromium\n"));
3924
4495
  process.exit(1);
3925
4496
  }
4497
+ if (options.viewports) {
4498
+ await runMultiViewportCompare(options);
4499
+ } else {
4500
+ await runSingleViewportCompare(options);
4501
+ }
4502
+ }
4503
+ var ALL_VIEWPORTS = [
4504
+ { name: "mobile", width: 375, height: 812 },
4505
+ { name: "tablet", width: 768, height: 1024 },
4506
+ { name: "laptop", width: 1280, height: 800 },
4507
+ { name: "desktop", width: 1536, height: 900 }
4508
+ ];
4509
+ async function runMultiViewportCompare(options) {
4510
+ const viewports = parseViewports(options.viewports);
4511
+ const outputDir = resolve14(process.cwd(), options.outputDir ?? ".saeroon/compare");
4512
+ await mkdir5(outputDir, { recursive: true });
4513
+ console.log(chalk16.dim(` \uB808\uD37C\uB7F0\uC2A4: ${options.ref}`));
4514
+ console.log(chalk16.dim(` \uD504\uB9AC\uBDF0: ${options.preview}`));
4515
+ console.log(chalk16.dim(` \uBDF0\uD3EC\uD2B8: ${viewports.map((v) => `${v.name}(${v.width}px)`).join(", ")}`));
4516
+ console.log(chalk16.dim(` \uCD9C\uB825: ${outputDir}`));
4517
+ console.log("");
4518
+ const hasMagick = checkCommand("magick");
4519
+ const results = [];
4520
+ for (const vp of viewports) {
4521
+ const vpSpinner = spinner(`${vp.name} (${vp.width}px) \uBE44\uAD50 \uC911...`);
4522
+ const refPath = join3(outputDir, `ref-${vp.name}.png`);
4523
+ const previewPath = join3(outputDir, `preview-${vp.name}.png`);
4524
+ const diffPath = join3(outputDir, `diff-${vp.name}.png`);
4525
+ try {
4526
+ captureScreenshot2(options.ref, refPath, vp.width, vp.height);
4527
+ captureScreenshot2(options.preview, previewPath, vp.width, vp.height);
4528
+ let diffPercentage = 0;
4529
+ if (hasMagick) {
4530
+ diffPercentage = generateDiffWithMagick(refPath, previewPath, diffPath);
4531
+ } else {
4532
+ generateSideBySide(refPath, previewPath, diffPath);
4533
+ diffPercentage = -1;
4534
+ }
4535
+ results.push({
4536
+ name: vp.name,
4537
+ width: vp.width,
4538
+ referenceScreenshot: refPath,
4539
+ previewScreenshot: previewPath,
4540
+ diffScreenshot: diffPath,
4541
+ diffPercentage
4542
+ });
4543
+ const pctText = diffPercentage >= 0 ? `${diffPercentage.toFixed(1)}%` : "(\uACC4\uC0B0 \uBD88\uAC00)";
4544
+ const pctColor = diffPercentage <= 10 ? chalk16.green : diffPercentage <= 25 ? chalk16.yellow : chalk16.red;
4545
+ vpSpinner.stop(` ${vp.name}: diff ${pctColor(pctText)}`);
4546
+ } catch (error) {
4547
+ vpSpinner.stop(chalk16.red(` ${vp.name}: \uC2E4\uD328 \u2014 ${error instanceof Error ? error.message : String(error)}`));
4548
+ results.push({
4549
+ name: vp.name,
4550
+ width: vp.width,
4551
+ referenceScreenshot: refPath,
4552
+ previewScreenshot: previewPath,
4553
+ diffScreenshot: diffPath,
4554
+ diffPercentage: -1
4555
+ });
4556
+ }
4557
+ }
4558
+ const validResults = results.filter((r) => r.diffPercentage >= 0);
4559
+ const overallDiff = validResults.length > 0 ? validResults.reduce((sum, r) => sum + r.diffPercentage, 0) / validResults.length : -1;
4560
+ const report = {
4561
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4562
+ referenceUrl: options.ref,
4563
+ previewUrl: options.preview,
4564
+ viewports: results,
4565
+ overallDiffPercentage: Math.round(overallDiff * 10) / 10
4566
+ };
4567
+ const reportPath = join3(outputDir, "comparison-report.json");
4568
+ await writeFile8(reportPath, JSON.stringify(report, null, 2), "utf-8");
4569
+ console.log("");
4570
+ console.log(chalk16.bold("\uBE44\uAD50 \uC694\uC57D"));
4571
+ for (const r of results) {
4572
+ const pct = r.diffPercentage >= 0 ? `${r.diffPercentage.toFixed(1)}%` : "N/A";
4573
+ const icon = r.diffPercentage <= 10 ? chalk16.green("\u25CF") : r.diffPercentage <= 25 ? chalk16.yellow("\u25CF") : chalk16.red("\u25CF");
4574
+ console.log(` ${icon} ${r.name.padEnd(8)} ${pct.padStart(6)} ${chalk16.dim(r.diffScreenshot)}`);
4575
+ }
4576
+ console.log("");
4577
+ if (overallDiff >= 0) {
4578
+ const overallColor = overallDiff <= 10 ? chalk16.green : overallDiff <= 25 ? chalk16.yellow : chalk16.red;
4579
+ console.log(chalk16.bold(`\uC804\uCCB4 \uD3C9\uADE0 diff: ${overallColor(`${overallDiff.toFixed(1)}%`)}`));
4580
+ if (overallDiff <= 10) {
4581
+ console.log(chalk16.green(" \u2192 \uBE44\uAD50 \uB8E8\uD504 \uC644\uB8CC! CEO \uCD5C\uC885 \uD655\uC778 \uC694\uCCAD \uAC00\uB2A5"));
4582
+ } else {
4583
+ console.log(chalk16.yellow(" \u2192 diff > 10%: Vision \uBE44\uAD50 \u2192 \uC2A4\uD0A4\uB9C8 \uC218\uC815 \uD544\uC694"));
4584
+ }
4585
+ }
4586
+ console.log(chalk16.dim(`
4587
+ \uB9AC\uD3EC\uD2B8: ${reportPath}`));
4588
+ console.log("");
4589
+ }
4590
+ function parseViewports(input) {
4591
+ if (input === "all") return ALL_VIEWPORTS;
4592
+ const names = input.split(",").map((s) => s.trim().toLowerCase());
4593
+ return names.map((name) => ALL_VIEWPORTS.find((v) => v.name === name)).filter((v) => v !== void 0);
4594
+ }
4595
+ function generateDiffWithMagick(refPath, previewPath, diffPath) {
4596
+ try {
4597
+ execSync2(
4598
+ `magick "${previewPath}" -resize "$(magick identify -format '%wx%h' "${refPath}")" -extent "$(magick identify -format '%wx%h' "${refPath}")" "${previewPath}"`,
4599
+ { stdio: "pipe" }
4600
+ );
4601
+ } catch {
4602
+ }
4603
+ try {
4604
+ const result = spawnSync2("magick", [
4605
+ "compare",
4606
+ "-metric",
4607
+ "AE",
4608
+ refPath,
4609
+ previewPath,
4610
+ diffPath
4611
+ ], { stdio: "pipe", timeout: 3e4 });
4612
+ const stderr = result.stderr?.toString().trim() ?? "";
4613
+ const diffPixels = parseFloat(stderr) || 0;
4614
+ try {
4615
+ const sizeStr = execSync2(`magick identify -format '%w %h' "${refPath}"`, { stdio: "pipe" }).toString().trim();
4616
+ const [w, h] = sizeStr.split(" ").map(Number);
4617
+ const totalPixels = w * h;
4618
+ if (totalPixels > 0) {
4619
+ return Math.round(diffPixels / totalPixels * 1e3) / 10;
4620
+ }
4621
+ } catch {
4622
+ }
4623
+ return diffPixels > 0 ? 50 : 0;
4624
+ } catch {
4625
+ try {
4626
+ execSync2(
4627
+ `magick composite -blend 50x50 "${refPath}" "${previewPath}" "${diffPath}"`,
4628
+ { stdio: "pipe" }
4629
+ );
4630
+ } catch {
4631
+ }
4632
+ return -1;
4633
+ }
4634
+ }
4635
+ function generateSideBySide(refPath, previewPath, diffPath) {
4636
+ try {
4637
+ execSync2(
4638
+ `magick montage "${refPath}" "${previewPath}" -geometry +2+2 -tile 2x1 "${diffPath}"`,
4639
+ { stdio: "pipe" }
4640
+ );
4641
+ } catch {
4642
+ try {
4643
+ execSync2(`cp "${refPath}" "${diffPath}"`, { stdio: "pipe" });
4644
+ } catch {
4645
+ }
4646
+ }
4647
+ }
4648
+ async function runSingleViewportCompare(options) {
3926
4649
  const width = parseInt(options.width ?? "1280", 10);
3927
4650
  const height = parseInt(options.height ?? "800", 10);
3928
- const outputPath = resolve12(process.cwd(), options.output ?? "compare-result.png");
4651
+ const outputPath = resolve14(process.cwd(), options.output ?? "compare-result.png");
3929
4652
  const captureSpinner = spinner("\uB808\uD37C\uB7F0\uC2A4 \uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC911...");
3930
4653
  try {
3931
- const refScreenshot = resolve12(process.cwd(), ".compare-ref.png");
3932
- const previewScreenshot = resolve12(process.cwd(), ".compare-preview.png");
3933
- captureScreenshot(options.ref, refScreenshot, width, height);
3934
- captureSpinner.stop(chalk15.green(" \uB808\uD37C\uB7F0\uC2A4 \uCEA1\uCC98 \uC644\uB8CC"));
4654
+ const refScreenshot = resolve14(process.cwd(), ".compare-ref.png");
4655
+ const previewScreenshot = resolve14(process.cwd(), ".compare-preview.png");
4656
+ captureScreenshot2(options.ref, refScreenshot, width, height);
4657
+ captureSpinner.stop(chalk16.green(" \uB808\uD37C\uB7F0\uC2A4 \uCEA1\uCC98 \uC644\uB8CC"));
3935
4658
  const previewSpinner = spinner("\uD504\uB9AC\uBDF0 \uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC911...");
3936
- captureScreenshot(options.preview, previewScreenshot, width, height);
3937
- previewSpinner.stop(chalk15.green(" \uD504\uB9AC\uBDF0 \uCEA1\uCC98 \uC644\uB8CC"));
4659
+ captureScreenshot2(options.preview, previewScreenshot, width, height);
4660
+ previewSpinner.stop(chalk16.green(" \uD504\uB9AC\uBDF0 \uCEA1\uCC98 \uC644\uB8CC"));
3938
4661
  const diffSpinner = spinner("\uC2DC\uAC01 \uBE44\uAD50 \uC0DD\uC131 \uC911...");
3939
4662
  const hasMagick = checkCommand("magick");
3940
4663
  if (hasMagick) {
3941
4664
  try {
3942
- execSync(
4665
+ execSync2(
3943
4666
  `magick compare -metric AE "${refScreenshot}" "${previewScreenshot}" "${outputPath}" 2>&1`,
3944
4667
  { stdio: "pipe" }
3945
4668
  );
3946
4669
  } catch {
3947
- execSync(
4670
+ execSync2(
3948
4671
  `magick composite -blend 50x50 "${refScreenshot}" "${previewScreenshot}" "${outputPath}"`,
3949
4672
  { stdio: "pipe" }
3950
4673
  );
3951
4674
  }
3952
- diffSpinner.stop(chalk15.green(" Diff \uC774\uBBF8\uC9C0 \uC0DD\uC131 \uC644\uB8CC (ImageMagick)"));
4675
+ diffSpinner.stop(chalk16.green(" Diff \uC774\uBBF8\uC9C0 \uC0DD\uC131 \uC644\uB8CC (ImageMagick)"));
3953
4676
  } else {
3954
- execSync(
4677
+ execSync2(
3955
4678
  `magick montage "${refScreenshot}" "${previewScreenshot}" -geometry +2+2 -tile 2x1 "${outputPath}" 2>/dev/null || cp "${refScreenshot}" "${outputPath}"`,
3956
4679
  { stdio: "pipe" }
3957
4680
  );
3958
- diffSpinner.stop(chalk15.yellow(" \uB098\uB780\uD788 \uBE44\uAD50 \uC774\uBBF8\uC9C0 \uC0DD\uC131 (ImageMagick \uC5C6\uC74C \u2014 \uC624\uBC84\uB808\uC774 \uBE44\uAD50\uB294 magick \uC124\uCE58 \uD544\uC694)"));
4681
+ diffSpinner.stop(chalk16.yellow(" \uB098\uB780\uD788 \uBE44\uAD50 \uC774\uBBF8\uC9C0 \uC0DD\uC131 (ImageMagick \uC5C6\uC74C \u2014 \uC624\uBC84\uB808\uC774 \uBE44\uAD50\uB294 magick \uC124\uCE58 \uD544\uC694)"));
3959
4682
  }
3960
4683
  try {
3961
- execSync(`rm -f "${refScreenshot}" "${previewScreenshot}"`, { stdio: "pipe" });
4684
+ execSync2(`rm -f "${refScreenshot}" "${previewScreenshot}"`, { stdio: "pipe" });
3962
4685
  } catch {
3963
4686
  }
3964
4687
  console.log("");
3965
- console.log(chalk15.green.bold("\uBE44\uAD50 \uC644\uB8CC!"));
3966
- console.log(chalk15.dim(` \uCD9C\uB825: ${outputPath}`));
3967
- console.log(chalk15.dim(` \uD574\uC0C1\uB3C4: ${width}\xD7${height}`));
3968
- console.log(chalk15.dim(` \uB808\uD37C\uB7F0\uC2A4: ${options.ref}`));
3969
- console.log(chalk15.dim(` \uD504\uB9AC\uBDF0: ${options.preview}`));
4688
+ console.log(chalk16.green.bold("\uBE44\uAD50 \uC644\uB8CC!"));
4689
+ console.log(chalk16.dim(` \uCD9C\uB825: ${outputPath}`));
4690
+ console.log(chalk16.dim(` \uD574\uC0C1\uB3C4: ${width}\xD7${height}`));
4691
+ console.log(chalk16.dim(` \uB808\uD37C\uB7F0\uC2A4: ${options.ref}`));
4692
+ console.log(chalk16.dim(` \uD504\uB9AC\uBDF0: ${options.preview}`));
3970
4693
  console.log("");
3971
4694
  } catch (error) {
3972
- console.error(chalk15.red(`\uBE44\uAD50 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`));
4695
+ console.error(chalk16.red(`\uBE44\uAD50 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`));
3973
4696
  process.exit(1);
3974
4697
  }
3975
4698
  }
3976
4699
  function checkPlaywright() {
3977
4700
  try {
3978
- execSync("npx playwright --version", { stdio: "pipe" });
4701
+ execSync2("npx playwright --version", { stdio: "pipe" });
3979
4702
  return true;
3980
4703
  } catch {
3981
4704
  return false;
@@ -3983,14 +4706,14 @@ function checkPlaywright() {
3983
4706
  }
3984
4707
  function checkCommand(cmd) {
3985
4708
  try {
3986
- execSync(`which ${cmd}`, { stdio: "pipe" });
4709
+ execSync2(`which ${cmd}`, { stdio: "pipe" });
3987
4710
  return true;
3988
4711
  } catch {
3989
4712
  return false;
3990
4713
  }
3991
4714
  }
3992
- function captureScreenshot(url, outputPath, width, height) {
3993
- const result = spawnSync("npx", [
4715
+ function captureScreenshot2(url, outputPath, width, height) {
4716
+ const result = spawnSync2("npx", [
3994
4717
  "playwright",
3995
4718
  "screenshot",
3996
4719
  "--browser",
@@ -4013,7 +4736,7 @@ function captureScreenshot(url, outputPath, width, height) {
4013
4736
  }
4014
4737
 
4015
4738
  // src/commands/diff.ts
4016
- import chalk16 from "chalk";
4739
+ import chalk17 from "chalk";
4017
4740
  function compareSchemas(draft, published) {
4018
4741
  const draftObj = draft;
4019
4742
  const pubObj = published;
@@ -4137,53 +4860,53 @@ function compareSettings(draftSettings, pubSettings, result) {
4137
4860
  }
4138
4861
  function printDiffResult(result) {
4139
4862
  if (!result.hasDifferences) {
4140
- console.log(chalk16.green("\nDraft\uC640 Published \uC2A4\uD0A4\uB9C8\uAC00 \uB3D9\uC77C\uD569\uB2C8\uB2E4. \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C.\n"));
4863
+ console.log(chalk17.green("\nDraft\uC640 Published \uC2A4\uD0A4\uB9C8\uAC00 \uB3D9\uC77C\uD569\uB2C8\uB2E4. \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C.\n"));
4141
4864
  return;
4142
4865
  }
4143
- console.log(chalk16.bold("\nStaging vs Production \uC2A4\uD0A4\uB9C8 \uBE44\uAD50:\n"));
4866
+ console.log(chalk17.bold("\nStaging vs Production \uC2A4\uD0A4\uB9C8 \uBE44\uAD50:\n"));
4144
4867
  if (result.pages.added.length > 0 || result.pages.removed.length > 0 || result.pages.modified.length > 0) {
4145
- console.log(chalk16.bold.underline(" Pages"));
4868
+ console.log(chalk17.bold.underline(" Pages"));
4146
4869
  for (const page of result.pages.added) {
4147
- console.log(` ${chalk16.green("+")} ${page}`);
4870
+ console.log(` ${chalk17.green("+")} ${page}`);
4148
4871
  }
4149
4872
  for (const page of result.pages.removed) {
4150
- console.log(` ${chalk16.red("-")} ${page}`);
4873
+ console.log(` ${chalk17.red("-")} ${page}`);
4151
4874
  }
4152
4875
  for (const page of result.pages.modified) {
4153
- console.log(` ${chalk16.yellow("~")} ${page}`);
4876
+ console.log(` ${chalk17.yellow("~")} ${page}`);
4154
4877
  }
4155
4878
  console.log("");
4156
4879
  }
4157
4880
  if (result.blocks.added.length > 0 || result.blocks.removed.length > 0 || result.blocks.modified.length > 0) {
4158
- console.log(chalk16.bold.underline(" Blocks"));
4881
+ console.log(chalk17.bold.underline(" Blocks"));
4159
4882
  for (const block of result.blocks.added) {
4160
- console.log(` ${chalk16.green("+")} ${block}`);
4883
+ console.log(` ${chalk17.green("+")} ${block}`);
4161
4884
  }
4162
4885
  for (const block of result.blocks.removed) {
4163
- console.log(` ${chalk16.red("-")} ${block}`);
4886
+ console.log(` ${chalk17.red("-")} ${block}`);
4164
4887
  }
4165
4888
  for (const block of result.blocks.modified) {
4166
- console.log(` ${chalk16.yellow("~")} ${block}`);
4889
+ console.log(` ${chalk17.yellow("~")} ${block}`);
4167
4890
  }
4168
4891
  console.log("");
4169
4892
  }
4170
4893
  if (result.settings.changed.length > 0) {
4171
- console.log(chalk16.bold.underline(" Settings"));
4894
+ console.log(chalk17.bold.underline(" Settings"));
4172
4895
  for (const { key, draft, published } of result.settings.changed) {
4173
4896
  if (published === void 0) {
4174
- console.log(` ${chalk16.green("+")} ${key}: ${chalk16.green(formatValue(draft))}`);
4897
+ console.log(` ${chalk17.green("+")} ${key}: ${chalk17.green(formatValue(draft))}`);
4175
4898
  } else if (draft === void 0) {
4176
- console.log(` ${chalk16.red("-")} ${key}: ${chalk16.red(formatValue(published))}`);
4899
+ console.log(` ${chalk17.red("-")} ${key}: ${chalk17.red(formatValue(published))}`);
4177
4900
  } else {
4178
- console.log(` ${chalk16.yellow("~")} ${key}:`);
4179
- console.log(` ${chalk16.red(`- ${formatValue(published)}`)}`);
4180
- console.log(` ${chalk16.green(`+ ${formatValue(draft)}`)}`);
4901
+ console.log(` ${chalk17.yellow("~")} ${key}:`);
4902
+ console.log(` ${chalk17.red(`- ${formatValue(published)}`)}`);
4903
+ console.log(` ${chalk17.green(`+ ${formatValue(draft)}`)}`);
4181
4904
  }
4182
4905
  }
4183
4906
  console.log("");
4184
4907
  }
4185
4908
  const totalChanges = result.pages.added.length + result.pages.removed.length + result.pages.modified.length + result.blocks.added.length + result.blocks.removed.length + result.blocks.modified.length + result.settings.changed.length;
4186
- console.log(chalk16.dim(` \uCD1D ${totalChanges}\uAC74\uC758 \uBCC0\uACBD \uC0AC\uD56D
4909
+ console.log(chalk17.dim(` \uCD1D ${totalChanges}\uAC74\uC758 \uBCC0\uACBD \uC0AC\uD56D
4187
4910
  `));
4188
4911
  }
4189
4912
  function formatValue(value) {
@@ -4198,10 +4921,10 @@ function formatValue(value) {
4198
4921
  async function commandDiff(options) {
4199
4922
  const projectConfig = await loadProjectConfig();
4200
4923
  if (!projectConfig?.siteId) {
4201
- console.error(chalk16.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4202
- console.error(chalk16.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4203
- console.error(chalk16.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4204
- console.error(chalk16.dim(' 2. saeroon.config.json\uC5D0 "siteId" \uD544\uB4DC\uB97C \uC9C1\uC811 \uCD94\uAC00'));
4924
+ console.error(chalk17.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4925
+ console.error(chalk17.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4926
+ console.error(chalk17.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4927
+ console.error(chalk17.dim(' 2. saeroon.config.json\uC5D0 "siteId" \uD544\uB4DC\uB97C \uC9C1\uC811 \uCD94\uAC00'));
4205
4928
  console.error("");
4206
4929
  process.exit(1);
4207
4930
  }
@@ -4209,8 +4932,8 @@ async function commandDiff(options) {
4209
4932
  const apiKey = await resolveApiKey(options.apiKey);
4210
4933
  const apiBaseUrl = await getApiBaseUrl();
4211
4934
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4212
- console.log(chalk16.bold("\nStaging vs Production \uC2A4\uD0A4\uB9C8 \uBE44\uAD50\n"));
4213
- console.log(chalk16.dim(` \uC0AC\uC774\uD2B8 ID: ${siteId}`));
4935
+ console.log(chalk17.bold("\nStaging vs Production \uC2A4\uD0A4\uB9C8 \uBE44\uAD50\n"));
4936
+ console.log(chalk17.dim(` \uC0AC\uC774\uD2B8 ID: ${siteId}`));
4214
4937
  console.log("");
4215
4938
  const fetchSpinner = spinner("Draft \uBC0F Published \uC2A4\uD0A4\uB9C8\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
4216
4939
  let draftSchema;
@@ -4223,16 +4946,16 @@ async function commandDiff(options) {
4223
4946
  draftSchema = safeJsonParse(draftResult.schemaJson);
4224
4947
  publishedSchema = safeJsonParse(publishedResult.schemaJson);
4225
4948
  fetchSpinner.stop("");
4226
- console.log(chalk16.dim(` Draft: ${draftResult.isDraft ? "Draft" : "Published"} (editVersion: ${draftResult.editVersion})`));
4227
- console.log(chalk16.dim(` Published: ${publishedResult.isDraft ? "Draft" : "Published"} (editVersion: ${publishedResult.editVersion})`));
4949
+ console.log(chalk17.dim(` Draft: ${draftResult.isDraft ? "Draft" : "Published"} (editVersion: ${draftResult.editVersion})`));
4950
+ console.log(chalk17.dim(` Published: ${publishedResult.isDraft ? "Draft" : "Published"} (editVersion: ${publishedResult.editVersion})`));
4228
4951
  } catch (error) {
4229
4952
  if (error instanceof ApiError) {
4230
4953
  fetchSpinner.stop(
4231
- chalk16.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`)
4954
+ chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`)
4232
4955
  );
4233
4956
  } else {
4234
4957
  fetchSpinner.stop(
4235
- chalk16.red(
4958
+ chalk17.red(
4236
4959
  `\uC2A4\uD0A4\uB9C8 \uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`
4237
4960
  )
4238
4961
  );
@@ -4244,7 +4967,7 @@ async function commandDiff(options) {
4244
4967
  }
4245
4968
 
4246
4969
  // src/commands/template.ts
4247
- import chalk17 from "chalk";
4970
+ import chalk18 from "chalk";
4248
4971
  import { createInterface as createInterface5 } from "readline/promises";
4249
4972
  import { stdin as stdin5, stdout as stdout5 } from "process";
4250
4973
  var TEMPLATE_CATEGORIES2 = [
@@ -4265,26 +4988,26 @@ var TEMPLATE_CATEGORIES2 = [
4265
4988
  async function commandTemplateRegister(options) {
4266
4989
  const projectConfig = await loadProjectConfig();
4267
4990
  if (!projectConfig?.siteId) {
4268
- console.error(chalk17.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4269
- console.error(chalk17.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4270
- console.error(chalk17.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4271
- console.error(chalk17.dim(' 2. saeroon.config.json\uC5D0 "siteId" \uD544\uB4DC\uB97C \uC9C1\uC811 \uCD94\uAC00'));
4991
+ console.error(chalk18.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4992
+ console.error(chalk18.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4993
+ console.error(chalk18.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4994
+ console.error(chalk18.dim(' 2. saeroon.config.json\uC5D0 "siteId" \uD544\uB4DC\uB97C \uC9C1\uC811 \uCD94\uAC00'));
4272
4995
  console.error("");
4273
4996
  process.exit(1);
4274
4997
  }
4275
4998
  if (projectConfig.templateId) {
4276
- console.error(chalk17.yellow("\n\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uB294 \uC774\uBBF8 \uD15C\uD50C\uB9BF\uC774 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."));
4277
- console.error(chalk17.dim(` templateId: ${projectConfig.templateId}`));
4278
- console.error(chalk17.dim(" \uBA54\uD0C0\uB370\uC774\uD130 \uC218\uC815: saeroon template update"));
4279
- console.error(chalk17.dim(" \uBC84\uC804 \uB3D9\uAE30\uD654: saeroon template sync"));
4999
+ console.error(chalk18.yellow("\n\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uB294 \uC774\uBBF8 \uD15C\uD50C\uB9BF\uC774 \uB4F1\uB85D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."));
5000
+ console.error(chalk18.dim(` templateId: ${projectConfig.templateId}`));
5001
+ console.error(chalk18.dim(" \uBA54\uD0C0\uB370\uC774\uD130 \uC218\uC815: saeroon template update"));
5002
+ console.error(chalk18.dim(" \uBC84\uC804 \uB3D9\uAE30\uD654: saeroon template sync"));
4280
5003
  console.error("");
4281
5004
  process.exit(1);
4282
5005
  }
4283
5006
  const apiKey = await resolveApiKey(options.apiKey);
4284
5007
  const apiBaseUrl = await getApiBaseUrl();
4285
5008
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4286
- console.log(chalk17.bold("\n\uD15C\uD50C\uB9BF \uB4F1\uB85D\n"));
4287
- console.log(chalk17.dim(` \uC18C\uC2A4 \uC0AC\uC774\uD2B8: ${projectConfig.siteId}`));
5009
+ console.log(chalk18.bold("\n\uD15C\uD50C\uB9BF \uB4F1\uB85D\n"));
5010
+ console.log(chalk18.dim(` \uC18C\uC2A4 \uC0AC\uC774\uD2B8: ${projectConfig.siteId}`));
4288
5011
  console.log("");
4289
5012
  let name = options.name ?? "";
4290
5013
  let category = options.category ?? "";
@@ -4298,15 +5021,15 @@ async function commandTemplateRegister(options) {
4298
5021
  while (!name) {
4299
5022
  name = (await rl.question(" \uC774\uB984: ")).trim();
4300
5023
  if (!name) {
4301
- console.log(chalk17.red(" \uC774\uB984\uC740 \uD544\uC218\uC785\uB2C8\uB2E4."));
5024
+ console.log(chalk18.red(" \uC774\uB984\uC740 \uD544\uC218\uC785\uB2C8\uB2E4."));
4302
5025
  }
4303
5026
  }
4304
5027
  }
4305
5028
  if (!category) {
4306
5029
  console.log("");
4307
- console.log(chalk17.dim(" \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D:"));
5030
+ console.log(chalk18.dim(" \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D:"));
4308
5031
  TEMPLATE_CATEGORIES2.forEach((cat, i) => {
4309
- console.log(chalk17.dim(` ${i + 1}. ${cat}`));
5032
+ console.log(chalk18.dim(` ${i + 1}. ${cat}`));
4310
5033
  });
4311
5034
  console.log("");
4312
5035
  while (!category) {
@@ -4323,7 +5046,7 @@ async function commandTemplateRegister(options) {
4323
5046
  } else if (input) {
4324
5047
  category = input;
4325
5048
  } else {
4326
- console.log(chalk17.red(" \uCE74\uD14C\uACE0\uB9AC\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694."));
5049
+ console.log(chalk18.red(" \uCE74\uD14C\uACE0\uB9AC\uB97C \uC120\uD0DD\uD574\uC8FC\uC138\uC694."));
4327
5050
  }
4328
5051
  }
4329
5052
  }
@@ -4350,22 +5073,22 @@ async function commandTemplateRegister(options) {
4350
5073
  sourceSiteId: projectConfig.siteId
4351
5074
  };
4352
5075
  const result = await client.registerTemplate(request);
4353
- registerSpinner.stop(chalk17.green("\uD15C\uD50C\uB9BF \uB4F1\uB85D \uC644\uB8CC!"));
5076
+ registerSpinner.stop(chalk18.green("\uD15C\uD50C\uB9BF \uB4F1\uB85D \uC644\uB8CC!"));
4354
5077
  await saveProjectConfig({ templateId: result.id });
4355
5078
  if (options.json) {
4356
5079
  console.log(JSON.stringify(result, null, 2));
4357
5080
  } else {
4358
5081
  formatTemplateDetail(result);
4359
- console.log(chalk17.dim(" templateId\uAC00 saeroon.config.json\uC5D0 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
4360
- console.log(chalk17.dim(" \uBC84\uC804 \uB3D9\uAE30\uD654: saeroon template sync"));
5082
+ console.log(chalk18.dim(" templateId\uAC00 saeroon.config.json\uC5D0 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
5083
+ console.log(chalk18.dim(" \uBC84\uC804 \uB3D9\uAE30\uD654: saeroon template sync"));
4361
5084
  console.log("");
4362
5085
  }
4363
5086
  } catch (error) {
4364
5087
  if (error instanceof ApiError) {
4365
- registerSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5088
+ registerSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4366
5089
  } else {
4367
5090
  registerSpinner.stop(
4368
- chalk17.red(`\uB4F1\uB85D \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5091
+ chalk18.red(`\uB4F1\uB85D \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4369
5092
  );
4370
5093
  }
4371
5094
  process.exit(1);
@@ -4374,22 +5097,22 @@ async function commandTemplateRegister(options) {
4374
5097
  async function commandTemplateSync(options) {
4375
5098
  const projectConfig = await loadProjectConfig();
4376
5099
  if (!projectConfig?.templateId) {
4377
- console.error(chalk17.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4378
- console.error(chalk17.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
4379
- console.error(chalk17.dim(" saeroon template register"));
5100
+ console.error(chalk18.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
5101
+ console.error(chalk18.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
5102
+ console.error(chalk18.dim(" saeroon template register"));
4380
5103
  console.error("");
4381
5104
  process.exit(1);
4382
5105
  }
4383
5106
  if (!projectConfig.siteId) {
4384
- console.error(chalk17.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
5107
+ console.error(chalk18.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4385
5108
  process.exit(1);
4386
5109
  }
4387
5110
  const apiKey = await resolveApiKey(options.apiKey);
4388
5111
  const apiBaseUrl = await getApiBaseUrl();
4389
5112
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4390
- console.log(chalk17.bold("\n\uD15C\uD50C\uB9BF \uBC84\uC804 \uB3D9\uAE30\uD654\n"));
4391
- console.log(chalk17.dim(` \uC18C\uC2A4 \uC0AC\uC774\uD2B8: ${projectConfig.siteId}`));
4392
- console.log(chalk17.dim(` \uD15C\uD50C\uB9BF ID: ${projectConfig.templateId}`));
5113
+ console.log(chalk18.bold("\n\uD15C\uD50C\uB9BF \uBC84\uC804 \uB3D9\uAE30\uD654\n"));
5114
+ console.log(chalk18.dim(` \uC18C\uC2A4 \uC0AC\uC774\uD2B8: ${projectConfig.siteId}`));
5115
+ console.log(chalk18.dim(` \uD15C\uD50C\uB9BF ID: ${projectConfig.templateId}`));
4393
5116
  console.log("");
4394
5117
  if (!options.force) {
4395
5118
  const diffSpinner = spinner("\uC2A4\uD0A4\uB9C8 \uBCC0\uACBD \uC0AC\uD56D \uD655\uC778 \uC911...");
@@ -4401,17 +5124,17 @@ async function commandTemplateSync(options) {
4401
5124
  diffSpinner.stop("");
4402
5125
  const diff = compareSchemas(safeJsonParse(draftResult.schemaJson), safeJsonParse(publishedResult.schemaJson));
4403
5126
  if (!diff.hasDifferences) {
4404
- console.log(chalk17.green(" \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uC774\uBBF8 \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.\n"));
5127
+ console.log(chalk18.green(" \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C. \uC774\uBBF8 \uCD5C\uC2E0 \uC0C1\uD0DC\uC785\uB2C8\uB2E4.\n"));
4405
5128
  return;
4406
5129
  }
4407
5130
  printSyncDiffSummary(diff);
4408
5131
  const rl = createInterface5({ input: stdin5, output: stdout5 });
4409
5132
  try {
4410
5133
  const answer = await rl.question(
4411
- chalk17.yellow(" \uC774 \uBCC0\uACBD \uC0AC\uD56D\uC73C\uB85C \uC0C8 \uBC84\uC804\uC744 \uBC1C\uD589\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): ")
5134
+ chalk18.yellow(" \uC774 \uBCC0\uACBD \uC0AC\uD56D\uC73C\uB85C \uC0C8 \uBC84\uC804\uC744 \uBC1C\uD589\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? (y/N): ")
4412
5135
  );
4413
5136
  if (answer.trim().toLowerCase() !== "y") {
4414
- console.log(chalk17.dim("\n \uB3D9\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
5137
+ console.log(chalk18.dim("\n \uB3D9\uAE30\uD654\uAC00 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\n"));
4415
5138
  return;
4416
5139
  }
4417
5140
  } finally {
@@ -4419,10 +5142,10 @@ async function commandTemplateSync(options) {
4419
5142
  }
4420
5143
  } catch (error) {
4421
5144
  if (error instanceof ApiError) {
4422
- diffSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5145
+ diffSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4423
5146
  } else {
4424
5147
  diffSpinner.stop(
4425
- chalk17.red(`\uC2A4\uD0A4\uB9C8 \uBE44\uAD50 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5148
+ chalk18.red(`\uC2A4\uD0A4\uB9C8 \uBE44\uAD50 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4426
5149
  );
4427
5150
  }
4428
5151
  process.exit(1);
@@ -4432,22 +5155,22 @@ async function commandTemplateSync(options) {
4432
5155
  const syncSpinner = spinner("\uC0C8 \uBC84\uC804 \uBC1C\uD589 \uC911...");
4433
5156
  try {
4434
5157
  const result = await client.syncTemplateVersion(projectConfig.templateId);
4435
- syncSpinner.stop(chalk17.green(`\uD15C\uD50C\uB9BF v${result.version} \uBC1C\uD589 \uC644\uB8CC!`));
5158
+ syncSpinner.stop(chalk18.green(`\uD15C\uD50C\uB9BF v${result.version} \uBC1C\uD589 \uC644\uB8CC!`));
4436
5159
  if (options.json) {
4437
5160
  console.log(JSON.stringify(result, null, 2));
4438
5161
  } else {
4439
5162
  console.log("");
4440
5163
  console.log(` \uBC84\uC804: v${result.version}`);
4441
5164
  console.log(` \uC774\uB984: ${result.name}`);
4442
- console.log(` \uBC1C\uD589: ${chalk17.dim(result.updatedAt ?? "")}`);
5165
+ console.log(` \uBC1C\uD589: ${chalk18.dim(result.updatedAt ?? "")}`);
4443
5166
  console.log("");
4444
5167
  }
4445
5168
  } catch (error) {
4446
5169
  if (error instanceof ApiError) {
4447
- syncSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5170
+ syncSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4448
5171
  } else {
4449
5172
  syncSpinner.stop(
4450
- chalk17.red(`\uB3D9\uAE30\uD654 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5173
+ chalk18.red(`\uB3D9\uAE30\uD654 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4451
5174
  );
4452
5175
  }
4453
5176
  process.exit(1);
@@ -4468,10 +5191,10 @@ async function commandTemplateStatus(options) {
4468
5191
  }
4469
5192
  } catch (error) {
4470
5193
  if (error instanceof ApiError) {
4471
- fetchSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5194
+ fetchSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4472
5195
  } else {
4473
5196
  fetchSpinner.stop(
4474
- chalk17.red(`\uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5197
+ chalk18.red(`\uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4475
5198
  );
4476
5199
  }
4477
5200
  process.exit(1);
@@ -4480,9 +5203,9 @@ async function commandTemplateStatus(options) {
4480
5203
  async function commandTemplateUpdate(options) {
4481
5204
  const projectConfig = await loadProjectConfig();
4482
5205
  if (!projectConfig?.templateId) {
4483
- console.error(chalk17.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4484
- console.error(chalk17.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
4485
- console.error(chalk17.dim(" saeroon template register"));
5206
+ console.error(chalk18.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
5207
+ console.error(chalk18.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
5208
+ console.error(chalk18.dim(" saeroon template register"));
4486
5209
  console.error("");
4487
5210
  process.exit(1);
4488
5211
  }
@@ -4521,22 +5244,22 @@ async function commandTemplateUpdate(options) {
4521
5244
  currentSpinner.stop("");
4522
5245
  } catch (error) {
4523
5246
  if (error instanceof ApiError) {
4524
- currentSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5247
+ currentSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4525
5248
  } else {
4526
- currentSpinner.stop(chalk17.red("\uC870\uD68C \uC2E4\uD328"));
5249
+ currentSpinner.stop(chalk18.red("\uC870\uD68C \uC2E4\uD328"));
4527
5250
  }
4528
5251
  process.exit(1);
4529
5252
  }
4530
5253
  const current = templates.find((t) => t.id === projectConfig.templateId);
4531
5254
  if (!current) {
4532
- console.error(chalk17.red(`
5255
+ console.error(chalk18.red(`
4533
5256
  \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${projectConfig.templateId}
4534
5257
  `));
4535
5258
  process.exit(1);
4536
5259
  }
4537
- console.log(chalk17.bold("\n\uD604\uC7AC \uD15C\uD50C\uB9BF \uC815\uBCF4:"));
5260
+ console.log(chalk18.bold("\n\uD604\uC7AC \uD15C\uD50C\uB9BF \uC815\uBCF4:"));
4538
5261
  formatTemplateDetail(current);
4539
- console.log(chalk17.bold("\uC218\uC815\uD560 \uD56D\uBAA9\uC744 \uC785\uB825\uD558\uC138\uC694 (\uBE48 \uAC12 = \uBCC0\uACBD \uC5C6\uC74C):\n"));
5262
+ console.log(chalk18.bold("\uC218\uC815\uD560 \uD56D\uBAA9\uC744 \uC785\uB825\uD558\uC138\uC694 (\uBE48 \uAC12 = \uBCC0\uACBD \uC5C6\uC74C):\n"));
4540
5263
  const newName = (await rl.question(` \uC774\uB984 [${current.name}]: `)).trim();
4541
5264
  if (newName) {
4542
5265
  request.name = newName;
@@ -4562,14 +5285,14 @@ async function commandTemplateUpdate(options) {
4562
5285
  }
4563
5286
  }
4564
5287
  if (!hasChanges) {
4565
- console.log(chalk17.dim("\n \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C.\n"));
5288
+ console.log(chalk18.dim("\n \uBCC0\uACBD \uC0AC\uD56D \uC5C6\uC74C.\n"));
4566
5289
  return;
4567
5290
  }
4568
5291
  console.log("");
4569
5292
  const updateSpinner = spinner("\uD15C\uD50C\uB9BF \uBA54\uD0C0\uB370\uC774\uD130 \uC218\uC815 \uC911...");
4570
5293
  try {
4571
5294
  const result = await client.updateTemplate(projectConfig.templateId, request);
4572
- updateSpinner.stop(chalk17.green("\uD15C\uD50C\uB9BF \uC218\uC815 \uC644\uB8CC!"));
5295
+ updateSpinner.stop(chalk18.green("\uD15C\uD50C\uB9BF \uC218\uC815 \uC644\uB8CC!"));
4573
5296
  if (options.json) {
4574
5297
  console.log(JSON.stringify(result, null, 2));
4575
5298
  } else {
@@ -4577,10 +5300,10 @@ async function commandTemplateUpdate(options) {
4577
5300
  }
4578
5301
  } catch (error) {
4579
5302
  if (error instanceof ApiError) {
4580
- updateSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5303
+ updateSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4581
5304
  } else {
4582
5305
  updateSpinner.stop(
4583
- chalk17.red(`\uC218\uC815 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5306
+ chalk18.red(`\uC218\uC815 \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4584
5307
  );
4585
5308
  }
4586
5309
  process.exit(1);
@@ -4593,27 +5316,27 @@ function printSyncDiffSummary(diff) {
4593
5316
  const settingChanges = diff.settings.changed.length;
4594
5317
  if (pageChanges > 0) {
4595
5318
  const detail = [];
4596
- if (diff.pages.added.length > 0) detail.push(chalk17.green(`+${diff.pages.added.length}`));
4597
- if (diff.pages.removed.length > 0) detail.push(chalk17.red(`-${diff.pages.removed.length}`));
4598
- if (diff.pages.modified.length > 0) detail.push(chalk17.yellow(`~${diff.pages.modified.length}`));
5319
+ if (diff.pages.added.length > 0) detail.push(chalk18.green(`+${diff.pages.added.length}`));
5320
+ if (diff.pages.removed.length > 0) detail.push(chalk18.red(`-${diff.pages.removed.length}`));
5321
+ if (diff.pages.modified.length > 0) detail.push(chalk18.yellow(`~${diff.pages.modified.length}`));
4599
5322
  parts.push(`\uD398\uC774\uC9C0 ${detail.join(" ")}`);
4600
5323
  }
4601
5324
  if (blockChanges > 0) {
4602
5325
  const detail = [];
4603
- if (diff.blocks.added.length > 0) detail.push(chalk17.green(`+${diff.blocks.added.length}`));
4604
- if (diff.blocks.removed.length > 0) detail.push(chalk17.red(`-${diff.blocks.removed.length}`));
4605
- if (diff.blocks.modified.length > 0) detail.push(chalk17.yellow(`~${diff.blocks.modified.length}`));
5326
+ if (diff.blocks.added.length > 0) detail.push(chalk18.green(`+${diff.blocks.added.length}`));
5327
+ if (diff.blocks.removed.length > 0) detail.push(chalk18.red(`-${diff.blocks.removed.length}`));
5328
+ if (diff.blocks.modified.length > 0) detail.push(chalk18.yellow(`~${diff.blocks.modified.length}`));
4606
5329
  parts.push(`\uBE14\uB85D ${detail.join(" ")}`);
4607
5330
  }
4608
5331
  if (settingChanges > 0) {
4609
- parts.push(`\uC124\uC815 ${chalk17.yellow(`~${settingChanges}`)}`);
5332
+ parts.push(`\uC124\uC815 ${chalk18.yellow(`~${settingChanges}`)}`);
4610
5333
  }
4611
5334
  console.log(` \uBCC0\uACBD \uAC10\uC9C0: ${parts.join(", ")}`);
4612
5335
  console.log("");
4613
5336
  }
4614
5337
 
4615
5338
  // src/commands/pattern.ts
4616
- import chalk18 from "chalk";
5339
+ import chalk19 from "chalk";
4617
5340
  var V2_PATTERNS = [
4618
5341
  { id: "v2-faq-accordion", name: "FAQ \uC544\uCF54\uB514\uC5B8", nameEn: "FAQ Accordion", category: "interactive", description: "\uB124\uC774\uD2F0\uBE0C details/summary \uAE30\uBC18 \uC811\uAE30/\uD3BC\uCE58\uAE30" },
4619
5342
  { id: "v2-card-grid", name: "\uCE74\uB4DC \uADF8\uB9AC\uB4DC", nameEn: "Card Grid", category: "content", description: "repeat \uAE30\uBC18 \uCE74\uB4DC \uB808\uC774\uC544\uC6C3" },
@@ -4646,64 +5369,64 @@ async function commandPatterns(options) {
4646
5369
  sort: "popular",
4647
5370
  pageSize: 50
4648
5371
  });
4649
- fetchSpinner.stop(chalk18.green(`${result.total}\uAC1C\uC758 \uACF5\uAC1C \uD328\uD134\uC744 \uC870\uD68C\uD588\uC2B5\uB2C8\uB2E4.`));
5372
+ fetchSpinner.stop(chalk19.green(`${result.total}\uAC1C\uC758 \uACF5\uAC1C \uD328\uD134\uC744 \uC870\uD68C\uD588\uC2B5\uB2C8\uB2E4.`));
4650
5373
  if (result.data.length === 0) {
4651
- console.log(chalk18.gray("\n \uACF5\uAC1C \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
5374
+ console.log(chalk19.gray("\n \uACF5\uAC1C \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
4652
5375
  return;
4653
5376
  }
4654
5377
  console.log("");
4655
5378
  console.log(
4656
- ` ${chalk18.gray("ID".padEnd(10))} ${"\uC774\uB984".padEnd(18)} ${chalk18.gray("\uCE74\uD14C\uACE0\uB9AC".padEnd(12))} ${chalk18.gray("\uC0AC\uC6A9")} ${chalk18.gray("\uC791\uC131\uC790")}`
5379
+ ` ${chalk19.gray("ID".padEnd(10))} ${"\uC774\uB984".padEnd(18)} ${chalk19.gray("\uCE74\uD14C\uACE0\uB9AC".padEnd(12))} ${chalk19.gray("\uC0AC\uC6A9")} ${chalk19.gray("\uC791\uC131\uC790")}`
4657
5380
  );
4658
- console.log(chalk18.gray(" " + "-".repeat(70)));
5381
+ console.log(chalk19.gray(" " + "-".repeat(70)));
4659
5382
  for (const p of result.data) {
4660
5383
  const shortId = p.id.substring(0, 8);
4661
5384
  console.log(
4662
- ` ${chalk18.cyan(shortId.padEnd(10))} ${p.name.padEnd(18)} ${chalk18.gray(p.category.padEnd(12))} ${chalk18.yellow(String(p.usageCount).padEnd(4))} ${chalk18.gray(p.authorName ?? "")}`
5385
+ ` ${chalk19.cyan(shortId.padEnd(10))} ${p.name.padEnd(18)} ${chalk19.gray(p.category.padEnd(12))} ${chalk19.yellow(String(p.usageCount).padEnd(4))} ${chalk19.gray(p.authorName ?? "")}`
4663
5386
  );
4664
5387
  }
4665
- console.log(chalk18.gray(`
5388
+ console.log(chalk19.gray(`
4666
5389
  Fork: npx @saeroon/cli fork-pattern <pattern-id> --site-id <site-id>
4667
5390
  `));
4668
5391
  } catch (error) {
4669
5392
  fetchSpinner.stop(
4670
- chalk18.red(`\uACF5\uAC1C \uD328\uD134 \uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5393
+ chalk19.red(`\uACF5\uAC1C \uD328\uD134 \uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4671
5394
  );
4672
5395
  process.exit(1);
4673
5396
  }
4674
5397
  return;
4675
5398
  }
4676
- console.log(chalk18.bold("\n V2 Patterns:\n"));
5399
+ console.log(chalk19.bold("\n V2 Patterns:\n"));
4677
5400
  const patterns = options?.role ? V2_PATTERNS.filter((p) => p.category === options.role) : V2_PATTERNS;
4678
5401
  for (const p of patterns) {
4679
- console.log(` ${chalk18.cyan(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk18.gray(p.description)}`);
5402
+ console.log(` ${chalk19.cyan(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk19.gray(p.description)}`);
4680
5403
  }
4681
- console.log(chalk18.bold("\n V1 Patterns (legacy):\n"));
5404
+ console.log(chalk19.bold("\n V1 Patterns (legacy):\n"));
4682
5405
  for (const p of V1_PATTERNS) {
4683
- console.log(` ${chalk18.gray(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk18.gray(p.description)}`);
5406
+ console.log(` ${chalk19.gray(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk19.gray(p.description)}`);
4684
5407
  }
4685
- console.log(chalk18.gray(`
5408
+ console.log(chalk19.gray(`
4686
5409
  \uCD1D ${V2_PATTERNS.length + V1_PATTERNS.length}\uAC1C \uB0B4\uC7A5 \uD328\uD134`));
4687
- console.log(chalk18.gray(` \uACF5\uAC1C \uD328\uD134 \uC870\uD68C: npx @saeroon/cli patterns --public
5410
+ console.log(chalk19.gray(` \uACF5\uAC1C \uD328\uD134 \uC870\uD68C: npx @saeroon/cli patterns --public
4688
5411
  `));
4689
5412
  }
4690
5413
  async function commandAddPattern(patternId) {
4691
5414
  const pattern = V2_PATTERNS.find((p) => p.id === patternId) || V1_PATTERNS.find((p) => p.id === patternId);
4692
5415
  if (!pattern) {
4693
- console.error(chalk18.red(`Pattern "${patternId}" not found.`));
4694
- console.log(chalk18.gray("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD328\uD134: " + [...V2_PATTERNS, ...V1_PATTERNS].map((p) => p.id).join(", ")));
5416
+ console.error(chalk19.red(`Pattern "${patternId}" not found.`));
5417
+ console.log(chalk19.gray("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD328\uD134: " + [...V2_PATTERNS, ...V1_PATTERNS].map((p) => p.id).join(", ")));
4695
5418
  process.exit(1);
4696
5419
  }
4697
- console.log(chalk18.cyan(`
5420
+ console.log(chalk19.cyan(`
4698
5421
  Pattern: ${pattern.name} (${pattern.nameEn})`));
4699
- console.log(chalk18.gray(` Category: ${pattern.category}`));
4700
- console.log(chalk18.gray(` ${pattern.description}`));
4701
- console.log(chalk18.yellow("\n \uD328\uD134 \uC0BD\uC785\uC740 \uC5D0\uB514\uD130 \uB610\uB294 MCP\uC5D0\uC11C \uC2E4\uD589\uD558\uC138\uC694."));
4702
- console.log(chalk18.gray(" MCP: get_pattern \u2192 \uC2A4\uD0A4\uB9C8 JSON \uBC18\uD658\n"));
5422
+ console.log(chalk19.gray(` Category: ${pattern.category}`));
5423
+ console.log(chalk19.gray(` ${pattern.description}`));
5424
+ console.log(chalk19.yellow("\n \uD328\uD134 \uC0BD\uC785\uC740 \uC5D0\uB514\uD130 \uB610\uB294 MCP\uC5D0\uC11C \uC2E4\uD589\uD558\uC138\uC694."));
5425
+ console.log(chalk19.gray(" MCP: get_pattern \u2192 \uC2A4\uD0A4\uB9C8 JSON \uBC18\uD658\n"));
4703
5426
  }
4704
5427
  async function commandForkPattern(patternId, options) {
4705
5428
  if (!options.siteId) {
4706
- console.error(chalk18.red("--site-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
5429
+ console.error(chalk19.red("--site-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
4707
5430
  process.exit(1);
4708
5431
  }
4709
5432
  const apiBaseUrl = await getApiBaseUrl();
@@ -4712,10 +5435,10 @@ async function commandForkPattern(patternId, options) {
4712
5435
  const forkSpinner = spinner("\uACF5\uAC1C \uD328\uD134\uC744 Fork\uD558\uB294 \uC911...");
4713
5436
  try {
4714
5437
  await client.forkPublicPattern(patternId, options.siteId);
4715
- forkSpinner.stop(chalk18.green(`\uD328\uD134\uC774 \uC0AC\uC774\uD2B8 ${options.siteId}\uB85C Fork\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
5438
+ forkSpinner.stop(chalk19.green(`\uD328\uD134\uC774 \uC0AC\uC774\uD2B8 ${options.siteId}\uB85C Fork\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
4716
5439
  } catch (error) {
4717
5440
  forkSpinner.stop(
4718
- chalk18.red(`Fork \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5441
+ chalk19.red(`Fork \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
4719
5442
  );
4720
5443
  process.exit(1);
4721
5444
  }
@@ -4737,7 +5460,8 @@ program.command("add-pattern").description("\uC2A4\uD0A4\uB9C8\uC5D0 \uD328\uD13
4737
5460
  program.command("fork-pattern").description("\uACF5\uAC1C \uD328\uD134\uC744 \uB0B4 \uC0AC\uC774\uD2B8\uB85C \uD3EC\uD06C").argument("<pattern-id>", "\uD3EC\uD06C\uD560 \uACF5\uAC1C \uD328\uD134 ID").requiredOption("--site-id <id>", "\uB300\uC0C1 \uC0AC\uC774\uD2B8 ID").action(commandForkPattern);
4738
5461
  program.command("add").description("schema.json\uC5D0 \uBE14\uB85D \uCD94\uAC00 (\uAE30\uBCF8 props \uD3EC\uD568)").argument("<block-type>", "\uCD94\uAC00\uD560 \uBE14\uB85D \uD0C0\uC785 (e.g., heading-block, image-block)").option("--id <id>", "\uBE14\uB85D ID (\uBBF8\uC9C0\uC815 \uC2DC \uC790\uB3D9 \uC0DD\uC131)").option("--parent <parentId>", "\uBD80\uBAA8 \uBE14\uB85D ID").option("--after <siblingId>", "\uC0BD\uC785 \uC704\uCE58 (\uD615\uC81C \uBE14\uB85D \uB4A4)").option("--page <pageId>", "\uB300\uC0C1 \uD398\uC774\uC9C0 ID").action(commandAdd);
4739
5462
  program.command("generate").description("AI\uB85C \uC0AC\uC774\uD2B8 \uC2A4\uD0A4\uB9C8 \uC0DD\uC131").option("--ref <url>", "\uB808\uD37C\uB7F0\uC2A4 URL (\uC0AC\uC774\uD2B8\uB97C \uBD84\uC11D\uD558\uC5EC \uC720\uC0AC\uD55C \uC2A4\uD0A4\uB9C8 \uC0DD\uC131)").option("--prompt <text>", "\uD504\uB86C\uD504\uD2B8 \uD14D\uC2A4\uD2B8 (\uC124\uBA85 \uAE30\uBC18 \uC0DD\uC131)").option("--output <file>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C", "schema.json").option("--api-key <key>", "API Key").action(commandGenerate);
4740
- program.command("compare").description("\uB808\uD37C\uB7F0\uC2A4 \u2194 \uD504\uB9AC\uBDF0 \uC2DC\uAC01 \uBE44\uAD50 (Playwright \uC2A4\uD06C\uB9B0\uC0F7)").option("--ref <url>", "\uB808\uD37C\uB7F0\uC2A4 URL").option("--preview <url>", "\uD504\uB9AC\uBDF0 URL").option("--output <file>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C", "compare-result.png").option("--width <px>", "\uBDF0\uD3EC\uD2B8 \uB108\uBE44", "1280").option("--height <px>", "\uBDF0\uD3EC\uD2B8 \uB192\uC774", "800").action(commandCompare);
5463
+ program.command("analyze").description("\uB808\uD37C\uB7F0\uC2A4 URL \uBD84\uC11D (\uC2A4\uD06C\uB9B0\uC0F7 4\uC7A5 + DOM/CSS \uCD94\uCD9C + \uC778\uD130\uB799\uC158 \uAC10\uC9C0)").argument("<url>", "\uBD84\uC11D\uD560 \uB808\uD37C\uB7F0\uC2A4 URL").option("--output-dir <dir>", "\uBD84\uC11D \uACB0\uACFC \uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC").option("--timeout <ms>", "\uD398\uC774\uC9C0 \uB85C\uB4DC \uD0C0\uC784\uC544\uC6C3 (ms)", "30000").action(commandAnalyze);
5464
+ program.command("compare").description("\uB808\uD37C\uB7F0\uC2A4 \u2194 \uD504\uB9AC\uBDF0 \uC2DC\uAC01 \uBE44\uAD50 (Playwright \uC2A4\uD06C\uB9B0\uC0F7)").option("--ref <url>", "\uB808\uD37C\uB7F0\uC2A4 URL").option("--preview <url>", "\uD504\uB9AC\uBDF0 URL").option("--output <file>", "\uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C (\uB2E8\uC77C \uBDF0\uD3EC\uD2B8)", "compare-result.png").option("--output-dir <dir>", "\uCD9C\uB825 \uB514\uB809\uD1A0\uB9AC (\uBA40\uD2F0 \uBDF0\uD3EC\uD2B8)").option("--width <px>", "\uBDF0\uD3EC\uD2B8 \uB108\uBE44 (\uB2E8\uC77C \uBAA8\uB4DC)", "1280").option("--height <px>", "\uBDF0\uD3EC\uD2B8 \uB192\uC774 (\uB2E8\uC77C \uBAA8\uB4DC)", "800").option("--viewports <list>", "\uBE44\uAD50 \uBDF0\uD3EC\uD2B8: all | mobile,tablet,laptop,desktop").action(commandCompare);
4741
5465
  program.command("upload").description("\uC774\uBBF8\uC9C0\uB97C Saeroon CDN\uC5D0 \uC5C5\uB85C\uB4DC").argument("<path>", "\uC5C5\uB85C\uB4DC\uD560 \uD30C\uC77C \uB610\uB294 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C").option("--replace-in <file>", "\uC5C5\uB85C\uB4DC \uD6C4 \uD30C\uC77C \uB0B4 \uB85C\uCEEC \uACBD\uB85C\uB97C CDN URL\uB85C \uAD50\uCCB4").option("--site-id <id>", "\uC0AC\uC774\uD2B8 ID").option("--api-key <key>", "API Key").action(commandUpload);
4742
5466
  program.command("deploy").description("\uC0AC\uC774\uD2B8\uC5D0 \uC2A4\uD0A4\uB9C8 \uBC30\uD3EC").option("--api-key <key>", "API Key").option("--target <target>", "\uBC30\uD3EC \uB300\uC0C1: staging|production", "staging").option("--dry-run", "\uC2E4\uC81C \uC5C5\uB85C\uB4DC/\uBC30\uD3EC \uC5C6\uC774 \uC5D0\uC14B \uB9AC\uD3EC\uD2B8\uB9CC \uCD9C\uB825").option("--sync-template", "Production \uBC30\uD3EC \uD6C4 \uB9C8\uCF13\uD50C\uB808\uC774\uC2A4 \uD15C\uD50C\uB9BF \uBC84\uC804 \uC790\uB3D9 \uB3D9\uAE30\uD654").action(commandDeploy);
4743
5467
  program.command("publish").description("[deprecated] \uB9C8\uCF13\uD50C\uB808\uC774\uC2A4\uC5D0 \uD15C\uD50C\uB9BF \uB4F1\uB85D \u2192 saeroon template register \uC0AC\uC6A9").argument("[schema-path]", "\uC81C\uCD9C\uD560 \uC2A4\uD0A4\uB9C8 JSON \uD30C\uC77C \uACBD\uB85C", "schema.json").option("--api-key <key>", "API Key").action(commandPublish);