@saeroon/cli 0.2.1 → 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 +1118 -239
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -249,6 +249,49 @@ var SaeroonApiClient = class _SaeroonApiClient {
249
249
  false
250
250
  );
251
251
  }
252
+ // --- Public Patterns ---
253
+ /**
254
+ * 공개 패턴 목록 조회. 인증 필요.
255
+ * GET /api/v1/hosting/public/patterns
256
+ */
257
+ async getPublicPatterns(options) {
258
+ const params = new URLSearchParams();
259
+ if (options?.category) params.set("category", options.category);
260
+ if (options?.search) params.set("search", options.search);
261
+ params.set("sort", options?.sort ?? "popular");
262
+ params.set("page", String(options?.page ?? 1));
263
+ params.set("pageSize", String(options?.pageSize ?? 50));
264
+ return this.request(
265
+ "GET",
266
+ `/api/v1/hosting/public/patterns?${params.toString()}`,
267
+ void 0,
268
+ true
269
+ );
270
+ }
271
+ /**
272
+ * 공개 패턴 상세 조회. 인증 필요.
273
+ * GET /api/v1/hosting/public/patterns/{patternId}
274
+ */
275
+ async getPublicPatternDetail(patternId) {
276
+ return this.request(
277
+ "GET",
278
+ `/api/v1/hosting/public/patterns/${encodeURIComponent(patternId)}`,
279
+ void 0,
280
+ true
281
+ );
282
+ }
283
+ /**
284
+ * 공개 패턴을 내 사이트로 Fork. 인증 필요.
285
+ * POST /api/v1/hosting/public/patterns/{patternId}/fork
286
+ */
287
+ async forkPublicPattern(patternId, targetSiteId) {
288
+ return this.request(
289
+ "POST",
290
+ `/api/v1/hosting/public/patterns/${encodeURIComponent(patternId)}/fork`,
291
+ { targetSiteId },
292
+ true
293
+ );
294
+ }
252
295
  // --- Template Submission ---
253
296
  /**
254
297
  * 템플릿을 마켓플레이스에 제출.
@@ -345,7 +388,7 @@ var SaeroonApiClient = class _SaeroonApiClient {
345
388
  await this.request(
346
389
  "PUT",
347
390
  `/api/v1/hosting/developer/sites/${encodeURIComponent(siteId)}/schema`,
348
- { schema },
391
+ { schemaJson: JSON.stringify(schema), isDraft: true },
349
392
  true
350
393
  );
351
394
  }
@@ -1166,7 +1209,7 @@ https://hosting.saeroon.com/llms-full.txt
1166
1209
 
1167
1210
  // src/templates/starters/restaurant.ts
1168
1211
  var RESTAURANT_STARTER = {
1169
- version: "1.16.0",
1212
+ version: "1.21.0",
1170
1213
  settings: {
1171
1214
  title: "\uB9DB\uC788\uB294 \uD55C\uC2DD\uB2F9",
1172
1215
  description: "\uC815\uC131\uC744 \uB2F4\uC740 \uD55C\uC2DD \uC694\uB9AC \uC804\uBB38\uC810. \uC2E0\uC120\uD55C \uC7AC\uB8CC\uB85C \uB9CC\uB4E0 \uAC74\uAC15\uD55C \uD55C \uB07C\uB97C \uC81C\uACF5\uD569\uB2C8\uB2E4."
@@ -1244,7 +1287,7 @@ var RESTAURANT_STARTER = {
1244
1287
 
1245
1288
  // src/templates/starters/portfolio.ts
1246
1289
  var PORTFOLIO_STARTER = {
1247
- version: "1.16.0",
1290
+ version: "1.21.0",
1248
1291
  settings: {
1249
1292
  title: "Jane Doe \u2014 Designer & Developer",
1250
1293
  description: "Creative portfolio showcasing design and development work."
@@ -1317,7 +1360,7 @@ var PORTFOLIO_STARTER = {
1317
1360
 
1318
1361
  // src/templates/starters/business.ts
1319
1362
  var BUSINESS_STARTER = {
1320
- version: "1.16.0",
1363
+ version: "1.21.0",
1321
1364
  settings: {
1322
1365
  title: "Apex Consulting",
1323
1366
  description: "Strategic consulting for growing businesses. We help you scale smarter."
@@ -1421,7 +1464,7 @@ var BUSINESS_STARTER = {
1421
1464
 
1422
1465
  // src/templates/starters/saas.ts
1423
1466
  var SAAS_STARTER = {
1424
- version: "1.16.0",
1467
+ version: "1.21.0",
1425
1468
  settings: {
1426
1469
  title: "Flowboard \u2014 Project Management Made Simple",
1427
1470
  description: "The all-in-one project management tool for modern teams. Plan, track, and ship faster."
@@ -2052,7 +2095,7 @@ var PreviewClient = class {
2052
2095
  * POST /api/v1/developer/preview 호출하여 Preview 세션 생성.
2053
2096
  */
2054
2097
  async createSession(schema) {
2055
- const url = `${this.options.apiBaseUrl}/api/v1/developer/preview`;
2098
+ const url = `${this.options.apiBaseUrl}/api/v1/hosting/developer/preview`;
2056
2099
  const response = await fetch(url, {
2057
2100
  method: "POST",
2058
2101
  headers: {
@@ -2060,7 +2103,7 @@ var PreviewClient = class {
2060
2103
  Authorization: `Bearer ${this.options.apiKey}`
2061
2104
  },
2062
2105
  body: JSON.stringify({
2063
- schema,
2106
+ schemaJson: JSON.stringify(schema),
2064
2107
  device: this.options.device
2065
2108
  }),
2066
2109
  signal: AbortSignal.timeout(3e4)
@@ -2079,7 +2122,7 @@ var PreviewClient = class {
2079
2122
  * WebSocket 연결 수립 및 이벤트 핸들러 등록.
2080
2123
  */
2081
2124
  connectWebSocket() {
2082
- return new Promise((resolve13, reject) => {
2125
+ return new Promise((resolve15, reject) => {
2083
2126
  if (!this.session) {
2084
2127
  reject(new Error("\uC138\uC158\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2085
2128
  return;
@@ -2093,7 +2136,7 @@ var PreviewClient = class {
2093
2136
  this.retryCount = 0;
2094
2137
  this.options.onStatusChange?.(true);
2095
2138
  this.startPingInterval();
2096
- resolve13();
2139
+ resolve15();
2097
2140
  });
2098
2141
  ws.on("message", (data) => {
2099
2142
  this.handleMessage(data);
@@ -2426,87 +2469,17 @@ import chalk8 from "chalk";
2426
2469
 
2427
2470
  // src/lib/local-validator.ts
2428
2471
  var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
2429
- // Element (9)
2472
+ // Primitives (10)
2473
+ "container",
2430
2474
  "text-block",
2431
2475
  "heading-block",
2432
2476
  "button-block",
2433
2477
  "image-block",
2434
- "video-block",
2478
+ "embed-block",
2479
+ "icon-block",
2480
+ "input-block",
2435
2481
  "divider",
2436
2482
  "spacer",
2437
- "icon-block",
2438
- "container",
2439
- // Feature (70)
2440
- "image-slider",
2441
- "map-block",
2442
- "contact-form",
2443
- "demolition-calculator",
2444
- "floating-social-widget",
2445
- "modal-block",
2446
- "sticky-cta-bar",
2447
- "product-grid",
2448
- "cart-widget",
2449
- "product-gallery",
2450
- "product-price",
2451
- "stock-badge",
2452
- "variant-selector",
2453
- "product-filter",
2454
- "product-search",
2455
- "related-products",
2456
- "add-to-cart",
2457
- "cart-contents",
2458
- "cart-summary",
2459
- "coupon-input",
2460
- "checkout-form",
2461
- "order-confirmation",
2462
- "order-history",
2463
- "order-lookup",
2464
- "review-list",
2465
- "cart-drawer",
2466
- "wishlist",
2467
- "recently-viewed",
2468
- "booking-calendar",
2469
- "medical-booking-block",
2470
- "booking-button",
2471
- "service-detail-block",
2472
- "booking-service-list",
2473
- "booking-service-detail",
2474
- "booking-checkout",
2475
- "booking-confirmation",
2476
- "booking-my-bookings",
2477
- "booking-staff-list",
2478
- "booking-guest-cancel",
2479
- "booking-class-schedule",
2480
- "booking-course-detail",
2481
- "booking-course-progress",
2482
- "booking-resource-calendar",
2483
- "booking-resource-list",
2484
- "auth-block",
2485
- "member-profile-block",
2486
- "member-only-section",
2487
- "board-block",
2488
- "board-detail-block",
2489
- "faq-accordion",
2490
- "gallery-block",
2491
- "before-after-slider",
2492
- "testimonials-section",
2493
- "tabs-section",
2494
- "countdown-timer",
2495
- "newsletter",
2496
- "marquee-block",
2497
- "before-after-gallery",
2498
- "content-showcase",
2499
- "staff-showcase",
2500
- "site-menu",
2501
- "stats-counter",
2502
- "scroll-to-top",
2503
- "anchor-nav",
2504
- "trademark-search-block",
2505
- "trademark-detail-block",
2506
- "nice-class-browser-block",
2507
- "lottie-block",
2508
- "model-block",
2509
- "image-sequence-block",
2510
2483
  // Structure (6)
2511
2484
  "header-block",
2512
2485
  "footer-block",
@@ -2514,20 +2487,12 @@ var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
2514
2487
  "main-block",
2515
2488
  "aside-block",
2516
2489
  "article-block",
2517
- // Pattern (13, legacy but valid)
2518
- "hero",
2519
- "cta-banner",
2520
- "feature-grid",
2521
- "pricing-table",
2522
- "team-profile-section",
2523
- "logo-cloud",
2524
- "social-proof",
2525
- "timeline",
2526
- "announcement-list",
2527
- "service-card-grid",
2528
- "business-hours",
2529
- "split-landing-hero",
2530
- "event-banner"
2490
+ // Specialized (5)
2491
+ "site-menu",
2492
+ "floating-action-widget",
2493
+ "sticky-cta-bar",
2494
+ "cookie-consent-bar",
2495
+ "announcement-bar"
2531
2496
  ]);
2532
2497
  function validateSchemaLocal(schema) {
2533
2498
  const errors = [];
@@ -2536,37 +2501,34 @@ function validateSchemaLocal(schema) {
2536
2501
  return errors;
2537
2502
  }
2538
2503
  const s = schema;
2539
- if (!s.schemaVersion && !s.version) {
2540
- 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 });
2541
2506
  }
2542
- if (!s.global && !s.settings) {
2543
- 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 });
2544
2509
  }
2545
2510
  if (!s.pages) {
2546
2511
  errors.push({ severity: "error", message: '\uD544\uC218 \uD544\uB4DC \uB204\uB77D: "pages"', path: "pages", step: 1 });
2547
2512
  }
2548
- const version2 = s.schemaVersion ?? s.version;
2513
+ const version2 = s.version;
2549
2514
  if (version2 && typeof version2 === "string") {
2550
2515
  const parts = version2.split(".").map(Number);
2551
2516
  if (parts[0] !== 1 || parts[1] !== void 0 && parts[1] < 15) {
2552
2517
  errors.push({
2553
2518
  severity: "warning",
2554
- 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`,
2555
2520
  path: "schemaVersion",
2556
2521
  step: 1
2557
2522
  });
2558
2523
  }
2559
2524
  }
2560
- const global = s.global ?? s.settings;
2561
- if (global && typeof global === "object") {
2562
- if (!global.name && !global.title) {
2563
- errors.push({
2564
- severity: "error",
2565
- message: "global.name (\uB610\uB294 settings.title) \uC740 \uD544\uC218\uC785\uB2C8\uB2E4.",
2566
- path: "global.name",
2567
- step: 2
2568
- });
2569
- }
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
+ });
2570
2532
  }
2571
2533
  if (!s.pages) return errors;
2572
2534
  const pages = s.pages;
@@ -2880,11 +2842,11 @@ async function computeFileHashes(localRefs) {
2880
2842
  return results;
2881
2843
  }
2882
2844
  async function computeSha256(filePath) {
2883
- return new Promise((resolve13, reject) => {
2845
+ return new Promise((resolve15, reject) => {
2884
2846
  const hash = createHash("sha256");
2885
2847
  const stream = createReadStream(filePath);
2886
2848
  stream.on("data", (chunk) => hash.update(chunk));
2887
- stream.on("end", () => resolve13(hash.digest("hex")));
2849
+ stream.on("end", () => resolve15(hash.digest("hex")));
2888
2850
  stream.on("error", (err) => reject(err));
2889
2851
  });
2890
2852
  }
@@ -2933,14 +2895,14 @@ function createConcurrencyLimiter(concurrency) {
2933
2895
  function next() {
2934
2896
  if (queue.length > 0 && active < concurrency) {
2935
2897
  active++;
2936
- const resolve13 = queue.shift();
2937
- resolve13?.();
2898
+ const resolve15 = queue.shift();
2899
+ resolve15?.();
2938
2900
  }
2939
2901
  }
2940
2902
  return async function limit(fn) {
2941
2903
  if (active >= concurrency) {
2942
- await new Promise((resolve13) => {
2943
- queue.push(resolve13);
2904
+ await new Promise((resolve15) => {
2905
+ queue.push(resolve15);
2944
2906
  });
2945
2907
  } else {
2946
2908
  active++;
@@ -3174,7 +3136,7 @@ async function deployToStaging(client, siteId, schema, _syncTemplate, templateId
3174
3136
  await client.updateSiteSchema(siteId, schema);
3175
3137
  deploySpinner.stop(chalk9.green("Staging \uBC30\uD3EC \uC644\uB8CC!"));
3176
3138
  console.log("");
3177
- console.log(chalk9.cyan(`Staging URL: https://${siteId}.staging.hosting.saeroon.com`));
3139
+ console.log(chalk9.cyan(`Staging URL: https://hosting.saeroon.com/staging-sites/${siteId}`));
3178
3140
  console.log(chalk9.dim(" \u2192 Draft \uC2A4\uD0A4\uB9C8\uAC00 \uC5C5\uB370\uC774\uD2B8\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC2A4\uD14C\uC774\uC9D5\uC5D0\uC11C \uD655\uC778\uD558\uC138\uC694."));
3179
3141
  console.log("");
3180
3142
  console.log(chalk9.dim("\uD504\uB85C\uB355\uC158 \uBC30\uD3EC: npx @saeroon/cli deploy --target production"));
@@ -3862,77 +3824,881 @@ async function commandGenerate(options) {
3862
3824
  }
3863
3825
  }
3864
3826
 
3865
- // src/commands/compare.ts
3827
+ // src/commands/analyze.ts
3866
3828
  import chalk15 from "chalk";
3867
- import { resolve as resolve12 } from "path";
3868
- 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";
3869
4483
  async function commandCompare(options) {
3870
- 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"));
3871
4485
  if (!options.ref || !options.preview) {
3872
- console.error(chalk15.red("--ref <url> \uACFC --preview <url> \uC740 \uBAA8\uB450 \uD544\uC218\uC785\uB2C8\uB2E4."));
3873
- 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"));
3874
4488
  process.exit(1);
3875
4489
  }
3876
4490
  const playwrightAvailable = checkPlaywright();
3877
4491
  if (!playwrightAvailable) {
3878
- console.log(chalk15.yellow("Playwright\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
3879
- console.log(chalk15.dim(" \uB2E4\uC74C \uBA85\uB839\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694:"));
3880
- 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"));
3881
4495
  process.exit(1);
3882
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) {
3883
4649
  const width = parseInt(options.width ?? "1280", 10);
3884
4650
  const height = parseInt(options.height ?? "800", 10);
3885
- const outputPath = resolve12(process.cwd(), options.output ?? "compare-result.png");
4651
+ const outputPath = resolve14(process.cwd(), options.output ?? "compare-result.png");
3886
4652
  const captureSpinner = spinner("\uB808\uD37C\uB7F0\uC2A4 \uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC911...");
3887
4653
  try {
3888
- const refScreenshot = resolve12(process.cwd(), ".compare-ref.png");
3889
- const previewScreenshot = resolve12(process.cwd(), ".compare-preview.png");
3890
- captureScreenshot(options.ref, refScreenshot, width, height);
3891
- 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"));
3892
4658
  const previewSpinner = spinner("\uD504\uB9AC\uBDF0 \uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC911...");
3893
- captureScreenshot(options.preview, previewScreenshot, width, height);
3894
- 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"));
3895
4661
  const diffSpinner = spinner("\uC2DC\uAC01 \uBE44\uAD50 \uC0DD\uC131 \uC911...");
3896
4662
  const hasMagick = checkCommand("magick");
3897
4663
  if (hasMagick) {
3898
4664
  try {
3899
- execSync(
4665
+ execSync2(
3900
4666
  `magick compare -metric AE "${refScreenshot}" "${previewScreenshot}" "${outputPath}" 2>&1`,
3901
4667
  { stdio: "pipe" }
3902
4668
  );
3903
4669
  } catch {
3904
- execSync(
4670
+ execSync2(
3905
4671
  `magick composite -blend 50x50 "${refScreenshot}" "${previewScreenshot}" "${outputPath}"`,
3906
4672
  { stdio: "pipe" }
3907
4673
  );
3908
4674
  }
3909
- 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)"));
3910
4676
  } else {
3911
- execSync(
4677
+ execSync2(
3912
4678
  `magick montage "${refScreenshot}" "${previewScreenshot}" -geometry +2+2 -tile 2x1 "${outputPath}" 2>/dev/null || cp "${refScreenshot}" "${outputPath}"`,
3913
4679
  { stdio: "pipe" }
3914
4680
  );
3915
- 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)"));
3916
4682
  }
3917
4683
  try {
3918
- execSync(`rm -f "${refScreenshot}" "${previewScreenshot}"`, { stdio: "pipe" });
4684
+ execSync2(`rm -f "${refScreenshot}" "${previewScreenshot}"`, { stdio: "pipe" });
3919
4685
  } catch {
3920
4686
  }
3921
4687
  console.log("");
3922
- console.log(chalk15.green.bold("\uBE44\uAD50 \uC644\uB8CC!"));
3923
- console.log(chalk15.dim(` \uCD9C\uB825: ${outputPath}`));
3924
- console.log(chalk15.dim(` \uD574\uC0C1\uB3C4: ${width}\xD7${height}`));
3925
- console.log(chalk15.dim(` \uB808\uD37C\uB7F0\uC2A4: ${options.ref}`));
3926
- 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}`));
3927
4693
  console.log("");
3928
4694
  } catch (error) {
3929
- 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)}`));
3930
4696
  process.exit(1);
3931
4697
  }
3932
4698
  }
3933
4699
  function checkPlaywright() {
3934
4700
  try {
3935
- execSync("npx playwright --version", { stdio: "pipe" });
4701
+ execSync2("npx playwright --version", { stdio: "pipe" });
3936
4702
  return true;
3937
4703
  } catch {
3938
4704
  return false;
@@ -3940,14 +4706,14 @@ function checkPlaywright() {
3940
4706
  }
3941
4707
  function checkCommand(cmd) {
3942
4708
  try {
3943
- execSync(`which ${cmd}`, { stdio: "pipe" });
4709
+ execSync2(`which ${cmd}`, { stdio: "pipe" });
3944
4710
  return true;
3945
4711
  } catch {
3946
4712
  return false;
3947
4713
  }
3948
4714
  }
3949
- function captureScreenshot(url, outputPath, width, height) {
3950
- const result = spawnSync("npx", [
4715
+ function captureScreenshot2(url, outputPath, width, height) {
4716
+ const result = spawnSync2("npx", [
3951
4717
  "playwright",
3952
4718
  "screenshot",
3953
4719
  "--browser",
@@ -3970,7 +4736,7 @@ function captureScreenshot(url, outputPath, width, height) {
3970
4736
  }
3971
4737
 
3972
4738
  // src/commands/diff.ts
3973
- import chalk16 from "chalk";
4739
+ import chalk17 from "chalk";
3974
4740
  function compareSchemas(draft, published) {
3975
4741
  const draftObj = draft;
3976
4742
  const pubObj = published;
@@ -4094,53 +4860,53 @@ function compareSettings(draftSettings, pubSettings, result) {
4094
4860
  }
4095
4861
  function printDiffResult(result) {
4096
4862
  if (!result.hasDifferences) {
4097
- 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"));
4098
4864
  return;
4099
4865
  }
4100
- 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"));
4101
4867
  if (result.pages.added.length > 0 || result.pages.removed.length > 0 || result.pages.modified.length > 0) {
4102
- console.log(chalk16.bold.underline(" Pages"));
4868
+ console.log(chalk17.bold.underline(" Pages"));
4103
4869
  for (const page of result.pages.added) {
4104
- console.log(` ${chalk16.green("+")} ${page}`);
4870
+ console.log(` ${chalk17.green("+")} ${page}`);
4105
4871
  }
4106
4872
  for (const page of result.pages.removed) {
4107
- console.log(` ${chalk16.red("-")} ${page}`);
4873
+ console.log(` ${chalk17.red("-")} ${page}`);
4108
4874
  }
4109
4875
  for (const page of result.pages.modified) {
4110
- console.log(` ${chalk16.yellow("~")} ${page}`);
4876
+ console.log(` ${chalk17.yellow("~")} ${page}`);
4111
4877
  }
4112
4878
  console.log("");
4113
4879
  }
4114
4880
  if (result.blocks.added.length > 0 || result.blocks.removed.length > 0 || result.blocks.modified.length > 0) {
4115
- console.log(chalk16.bold.underline(" Blocks"));
4881
+ console.log(chalk17.bold.underline(" Blocks"));
4116
4882
  for (const block of result.blocks.added) {
4117
- console.log(` ${chalk16.green("+")} ${block}`);
4883
+ console.log(` ${chalk17.green("+")} ${block}`);
4118
4884
  }
4119
4885
  for (const block of result.blocks.removed) {
4120
- console.log(` ${chalk16.red("-")} ${block}`);
4886
+ console.log(` ${chalk17.red("-")} ${block}`);
4121
4887
  }
4122
4888
  for (const block of result.blocks.modified) {
4123
- console.log(` ${chalk16.yellow("~")} ${block}`);
4889
+ console.log(` ${chalk17.yellow("~")} ${block}`);
4124
4890
  }
4125
4891
  console.log("");
4126
4892
  }
4127
4893
  if (result.settings.changed.length > 0) {
4128
- console.log(chalk16.bold.underline(" Settings"));
4894
+ console.log(chalk17.bold.underline(" Settings"));
4129
4895
  for (const { key, draft, published } of result.settings.changed) {
4130
4896
  if (published === void 0) {
4131
- console.log(` ${chalk16.green("+")} ${key}: ${chalk16.green(formatValue(draft))}`);
4897
+ console.log(` ${chalk17.green("+")} ${key}: ${chalk17.green(formatValue(draft))}`);
4132
4898
  } else if (draft === void 0) {
4133
- console.log(` ${chalk16.red("-")} ${key}: ${chalk16.red(formatValue(published))}`);
4899
+ console.log(` ${chalk17.red("-")} ${key}: ${chalk17.red(formatValue(published))}`);
4134
4900
  } else {
4135
- console.log(` ${chalk16.yellow("~")} ${key}:`);
4136
- console.log(` ${chalk16.red(`- ${formatValue(published)}`)}`);
4137
- 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)}`)}`);
4138
4904
  }
4139
4905
  }
4140
4906
  console.log("");
4141
4907
  }
4142
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;
4143
- 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
4144
4910
  `));
4145
4911
  }
4146
4912
  function formatValue(value) {
@@ -4155,10 +4921,10 @@ function formatValue(value) {
4155
4921
  async function commandDiff(options) {
4156
4922
  const projectConfig = await loadProjectConfig();
4157
4923
  if (!projectConfig?.siteId) {
4158
- console.error(chalk16.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4159
- console.error(chalk16.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4160
- console.error(chalk16.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4161
- 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'));
4162
4928
  console.error("");
4163
4929
  process.exit(1);
4164
4930
  }
@@ -4166,8 +4932,8 @@ async function commandDiff(options) {
4166
4932
  const apiKey = await resolveApiKey(options.apiKey);
4167
4933
  const apiBaseUrl = await getApiBaseUrl();
4168
4934
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4169
- console.log(chalk16.bold("\nStaging vs Production \uC2A4\uD0A4\uB9C8 \uBE44\uAD50\n"));
4170
- 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}`));
4171
4937
  console.log("");
4172
4938
  const fetchSpinner = spinner("Draft \uBC0F Published \uC2A4\uD0A4\uB9C8\uB97C \uAC00\uC838\uC624\uB294 \uC911...");
4173
4939
  let draftSchema;
@@ -4180,16 +4946,16 @@ async function commandDiff(options) {
4180
4946
  draftSchema = safeJsonParse(draftResult.schemaJson);
4181
4947
  publishedSchema = safeJsonParse(publishedResult.schemaJson);
4182
4948
  fetchSpinner.stop("");
4183
- console.log(chalk16.dim(` Draft: ${draftResult.isDraft ? "Draft" : "Published"} (editVersion: ${draftResult.editVersion})`));
4184
- 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})`));
4185
4951
  } catch (error) {
4186
4952
  if (error instanceof ApiError) {
4187
4953
  fetchSpinner.stop(
4188
- chalk16.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`)
4954
+ chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`)
4189
4955
  );
4190
4956
  } else {
4191
4957
  fetchSpinner.stop(
4192
- chalk16.red(
4958
+ chalk17.red(
4193
4959
  `\uC2A4\uD0A4\uB9C8 \uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`
4194
4960
  )
4195
4961
  );
@@ -4201,7 +4967,7 @@ async function commandDiff(options) {
4201
4967
  }
4202
4968
 
4203
4969
  // src/commands/template.ts
4204
- import chalk17 from "chalk";
4970
+ import chalk18 from "chalk";
4205
4971
  import { createInterface as createInterface5 } from "readline/promises";
4206
4972
  import { stdin as stdin5, stdout as stdout5 } from "process";
4207
4973
  var TEMPLATE_CATEGORIES2 = [
@@ -4222,26 +4988,26 @@ var TEMPLATE_CATEGORIES2 = [
4222
4988
  async function commandTemplateRegister(options) {
4223
4989
  const projectConfig = await loadProjectConfig();
4224
4990
  if (!projectConfig?.siteId) {
4225
- console.error(chalk17.red("\nsiteId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4226
- console.error(chalk17.yellow("\uB2E4\uC74C \uC911 \uD558\uB098\uB97C \uC2E4\uD589\uD558\uC138\uC694:"));
4227
- console.error(chalk17.dim(" 1. npx @saeroon/cli init --from-site <siteId> \uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uCD08\uAE30\uD654"));
4228
- 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'));
4229
4995
  console.error("");
4230
4996
  process.exit(1);
4231
4997
  }
4232
4998
  if (projectConfig.templateId) {
4233
- 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."));
4234
- console.error(chalk17.dim(` templateId: ${projectConfig.templateId}`));
4235
- console.error(chalk17.dim(" \uBA54\uD0C0\uB370\uC774\uD130 \uC218\uC815: saeroon template update"));
4236
- 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"));
4237
5003
  console.error("");
4238
5004
  process.exit(1);
4239
5005
  }
4240
5006
  const apiKey = await resolveApiKey(options.apiKey);
4241
5007
  const apiBaseUrl = await getApiBaseUrl();
4242
5008
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4243
- console.log(chalk17.bold("\n\uD15C\uD50C\uB9BF \uB4F1\uB85D\n"));
4244
- 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}`));
4245
5011
  console.log("");
4246
5012
  let name = options.name ?? "";
4247
5013
  let category = options.category ?? "";
@@ -4255,15 +5021,15 @@ async function commandTemplateRegister(options) {
4255
5021
  while (!name) {
4256
5022
  name = (await rl.question(" \uC774\uB984: ")).trim();
4257
5023
  if (!name) {
4258
- 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."));
4259
5025
  }
4260
5026
  }
4261
5027
  }
4262
5028
  if (!category) {
4263
5029
  console.log("");
4264
- console.log(chalk17.dim(" \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D:"));
5030
+ console.log(chalk18.dim(" \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D:"));
4265
5031
  TEMPLATE_CATEGORIES2.forEach((cat, i) => {
4266
- console.log(chalk17.dim(` ${i + 1}. ${cat}`));
5032
+ console.log(chalk18.dim(` ${i + 1}. ${cat}`));
4267
5033
  });
4268
5034
  console.log("");
4269
5035
  while (!category) {
@@ -4280,7 +5046,7 @@ async function commandTemplateRegister(options) {
4280
5046
  } else if (input) {
4281
5047
  category = input;
4282
5048
  } else {
4283
- 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."));
4284
5050
  }
4285
5051
  }
4286
5052
  }
@@ -4307,22 +5073,22 @@ async function commandTemplateRegister(options) {
4307
5073
  sourceSiteId: projectConfig.siteId
4308
5074
  };
4309
5075
  const result = await client.registerTemplate(request);
4310
- registerSpinner.stop(chalk17.green("\uD15C\uD50C\uB9BF \uB4F1\uB85D \uC644\uB8CC!"));
5076
+ registerSpinner.stop(chalk18.green("\uD15C\uD50C\uB9BF \uB4F1\uB85D \uC644\uB8CC!"));
4311
5077
  await saveProjectConfig({ templateId: result.id });
4312
5078
  if (options.json) {
4313
5079
  console.log(JSON.stringify(result, null, 2));
4314
5080
  } else {
4315
5081
  formatTemplateDetail(result);
4316
- console.log(chalk17.dim(" templateId\uAC00 saeroon.config.json\uC5D0 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
4317
- 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"));
4318
5084
  console.log("");
4319
5085
  }
4320
5086
  } catch (error) {
4321
5087
  if (error instanceof ApiError) {
4322
- registerSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5088
+ registerSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4323
5089
  } else {
4324
5090
  registerSpinner.stop(
4325
- 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)}`)
4326
5092
  );
4327
5093
  }
4328
5094
  process.exit(1);
@@ -4331,22 +5097,22 @@ async function commandTemplateRegister(options) {
4331
5097
  async function commandTemplateSync(options) {
4332
5098
  const projectConfig = await loadProjectConfig();
4333
5099
  if (!projectConfig?.templateId) {
4334
- console.error(chalk17.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4335
- console.error(chalk17.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
4336
- 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"));
4337
5103
  console.error("");
4338
5104
  process.exit(1);
4339
5105
  }
4340
5106
  if (!projectConfig.siteId) {
4341
- 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"));
4342
5108
  process.exit(1);
4343
5109
  }
4344
5110
  const apiKey = await resolveApiKey(options.apiKey);
4345
5111
  const apiBaseUrl = await getApiBaseUrl();
4346
5112
  const client = new SaeroonApiClient(apiKey, apiBaseUrl);
4347
- console.log(chalk17.bold("\n\uD15C\uD50C\uB9BF \uBC84\uC804 \uB3D9\uAE30\uD654\n"));
4348
- console.log(chalk17.dim(` \uC18C\uC2A4 \uC0AC\uC774\uD2B8: ${projectConfig.siteId}`));
4349
- 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}`));
4350
5116
  console.log("");
4351
5117
  if (!options.force) {
4352
5118
  const diffSpinner = spinner("\uC2A4\uD0A4\uB9C8 \uBCC0\uACBD \uC0AC\uD56D \uD655\uC778 \uC911...");
@@ -4358,17 +5124,17 @@ async function commandTemplateSync(options) {
4358
5124
  diffSpinner.stop("");
4359
5125
  const diff = compareSchemas(safeJsonParse(draftResult.schemaJson), safeJsonParse(publishedResult.schemaJson));
4360
5126
  if (!diff.hasDifferences) {
4361
- 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"));
4362
5128
  return;
4363
5129
  }
4364
5130
  printSyncDiffSummary(diff);
4365
5131
  const rl = createInterface5({ input: stdin5, output: stdout5 });
4366
5132
  try {
4367
5133
  const answer = await rl.question(
4368
- 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): ")
4369
5135
  );
4370
5136
  if (answer.trim().toLowerCase() !== "y") {
4371
- 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"));
4372
5138
  return;
4373
5139
  }
4374
5140
  } finally {
@@ -4376,10 +5142,10 @@ async function commandTemplateSync(options) {
4376
5142
  }
4377
5143
  } catch (error) {
4378
5144
  if (error instanceof ApiError) {
4379
- diffSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5145
+ diffSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4380
5146
  } else {
4381
5147
  diffSpinner.stop(
4382
- 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)}`)
4383
5149
  );
4384
5150
  }
4385
5151
  process.exit(1);
@@ -4389,22 +5155,22 @@ async function commandTemplateSync(options) {
4389
5155
  const syncSpinner = spinner("\uC0C8 \uBC84\uC804 \uBC1C\uD589 \uC911...");
4390
5156
  try {
4391
5157
  const result = await client.syncTemplateVersion(projectConfig.templateId);
4392
- 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!`));
4393
5159
  if (options.json) {
4394
5160
  console.log(JSON.stringify(result, null, 2));
4395
5161
  } else {
4396
5162
  console.log("");
4397
5163
  console.log(` \uBC84\uC804: v${result.version}`);
4398
5164
  console.log(` \uC774\uB984: ${result.name}`);
4399
- console.log(` \uBC1C\uD589: ${chalk17.dim(result.updatedAt ?? "")}`);
5165
+ console.log(` \uBC1C\uD589: ${chalk18.dim(result.updatedAt ?? "")}`);
4400
5166
  console.log("");
4401
5167
  }
4402
5168
  } catch (error) {
4403
5169
  if (error instanceof ApiError) {
4404
- syncSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5170
+ syncSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4405
5171
  } else {
4406
5172
  syncSpinner.stop(
4407
- 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)}`)
4408
5174
  );
4409
5175
  }
4410
5176
  process.exit(1);
@@ -4425,10 +5191,10 @@ async function commandTemplateStatus(options) {
4425
5191
  }
4426
5192
  } catch (error) {
4427
5193
  if (error instanceof ApiError) {
4428
- fetchSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5194
+ fetchSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4429
5195
  } else {
4430
5196
  fetchSpinner.stop(
4431
- 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)}`)
4432
5198
  );
4433
5199
  }
4434
5200
  process.exit(1);
@@ -4437,9 +5203,9 @@ async function commandTemplateStatus(options) {
4437
5203
  async function commandTemplateUpdate(options) {
4438
5204
  const projectConfig = await loadProjectConfig();
4439
5205
  if (!projectConfig?.templateId) {
4440
- console.error(chalk17.red("\ntemplateId\uAC00 \uC124\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n"));
4441
- console.error(chalk17.yellow("\uBA3C\uC800 \uD15C\uD50C\uB9BF\uC744 \uB4F1\uB85D\uD558\uC138\uC694:"));
4442
- 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"));
4443
5209
  console.error("");
4444
5210
  process.exit(1);
4445
5211
  }
@@ -4478,22 +5244,22 @@ async function commandTemplateUpdate(options) {
4478
5244
  currentSpinner.stop("");
4479
5245
  } catch (error) {
4480
5246
  if (error instanceof ApiError) {
4481
- currentSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5247
+ currentSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4482
5248
  } else {
4483
- currentSpinner.stop(chalk17.red("\uC870\uD68C \uC2E4\uD328"));
5249
+ currentSpinner.stop(chalk18.red("\uC870\uD68C \uC2E4\uD328"));
4484
5250
  }
4485
5251
  process.exit(1);
4486
5252
  }
4487
5253
  const current = templates.find((t) => t.id === projectConfig.templateId);
4488
5254
  if (!current) {
4489
- console.error(chalk17.red(`
5255
+ console.error(chalk18.red(`
4490
5256
  \uD15C\uD50C\uB9BF\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${projectConfig.templateId}
4491
5257
  `));
4492
5258
  process.exit(1);
4493
5259
  }
4494
- 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:"));
4495
5261
  formatTemplateDetail(current);
4496
- 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"));
4497
5263
  const newName = (await rl.question(` \uC774\uB984 [${current.name}]: `)).trim();
4498
5264
  if (newName) {
4499
5265
  request.name = newName;
@@ -4519,14 +5285,14 @@ async function commandTemplateUpdate(options) {
4519
5285
  }
4520
5286
  }
4521
5287
  if (!hasChanges) {
4522
- 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"));
4523
5289
  return;
4524
5290
  }
4525
5291
  console.log("");
4526
5292
  const updateSpinner = spinner("\uD15C\uD50C\uB9BF \uBA54\uD0C0\uB370\uC774\uD130 \uC218\uC815 \uC911...");
4527
5293
  try {
4528
5294
  const result = await client.updateTemplate(projectConfig.templateId, request);
4529
- updateSpinner.stop(chalk17.green("\uD15C\uD50C\uB9BF \uC218\uC815 \uC644\uB8CC!"));
5295
+ updateSpinner.stop(chalk18.green("\uD15C\uD50C\uB9BF \uC218\uC815 \uC644\uB8CC!"));
4530
5296
  if (options.json) {
4531
5297
  console.log(JSON.stringify(result, null, 2));
4532
5298
  } else {
@@ -4534,10 +5300,10 @@ async function commandTemplateUpdate(options) {
4534
5300
  }
4535
5301
  } catch (error) {
4536
5302
  if (error instanceof ApiError) {
4537
- updateSpinner.stop(chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
5303
+ updateSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
4538
5304
  } else {
4539
5305
  updateSpinner.stop(
4540
- 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)}`)
4541
5307
  );
4542
5308
  }
4543
5309
  process.exit(1);
@@ -4550,25 +5316,134 @@ function printSyncDiffSummary(diff) {
4550
5316
  const settingChanges = diff.settings.changed.length;
4551
5317
  if (pageChanges > 0) {
4552
5318
  const detail = [];
4553
- if (diff.pages.added.length > 0) detail.push(chalk17.green(`+${diff.pages.added.length}`));
4554
- if (diff.pages.removed.length > 0) detail.push(chalk17.red(`-${diff.pages.removed.length}`));
4555
- 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}`));
4556
5322
  parts.push(`\uD398\uC774\uC9C0 ${detail.join(" ")}`);
4557
5323
  }
4558
5324
  if (blockChanges > 0) {
4559
5325
  const detail = [];
4560
- if (diff.blocks.added.length > 0) detail.push(chalk17.green(`+${diff.blocks.added.length}`));
4561
- if (diff.blocks.removed.length > 0) detail.push(chalk17.red(`-${diff.blocks.removed.length}`));
4562
- 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}`));
4563
5329
  parts.push(`\uBE14\uB85D ${detail.join(" ")}`);
4564
5330
  }
4565
5331
  if (settingChanges > 0) {
4566
- parts.push(`\uC124\uC815 ${chalk17.yellow(`~${settingChanges}`)}`);
5332
+ parts.push(`\uC124\uC815 ${chalk18.yellow(`~${settingChanges}`)}`);
4567
5333
  }
4568
5334
  console.log(` \uBCC0\uACBD \uAC10\uC9C0: ${parts.join(", ")}`);
4569
5335
  console.log("");
4570
5336
  }
4571
5337
 
5338
+ // src/commands/pattern.ts
5339
+ import chalk19 from "chalk";
5340
+ var V2_PATTERNS = [
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" },
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" },
5343
+ { id: "v2-image-gallery", name: "\uC774\uBBF8\uC9C0 \uAC24\uB7EC\uB9AC", nameEn: "Image Gallery", category: "content", description: "\uADF8\uB9AC\uB4DC + \uB77C\uC774\uD2B8\uBC15\uC2A4" },
5344
+ { id: "v2-tabs", name: "\uD0ED", nameEn: "Tabs", category: "interactive", description: "exclusive \uD328\uB110 \uC804\uD658" },
5345
+ { id: "v2-contact-form", name: "\uC5F0\uB77D\uCC98 \uD3FC", nameEn: "Contact Form", category: "form", description: "input-block + form behavior" },
5346
+ { id: "v2-image-slider", name: "\uC774\uBBF8\uC9C0 \uC2AC\uB77C\uC774\uB354", nameEn: "Image Slider", category: "hero", description: "CSS scroll-snap \uCE90\uB7EC\uC140" },
5347
+ { id: "v2-testimonials", name: "\uACE0\uAC1D \uD6C4\uAE30", nameEn: "Testimonials", category: "social", description: "\uCE74\uB4DC \uADF8\uB9AC\uB4DC \uCD94\uCC9C\uC0AC" },
5348
+ { id: "v2-newsletter", name: "\uB274\uC2A4\uB808\uD130 \uAD6C\uB3C5", nameEn: "Newsletter", category: "marketing", description: "\uC774\uBA54\uC77C \uC785\uB825 + \uAD6C\uB3C5 \uBC84\uD2BC" },
5349
+ { id: "v2-stats-counter", name: "\uD1B5\uACC4 \uCE74\uC6B4\uD130", nameEn: "Stats Counter", category: "social", description: "\uC22B\uC790 \uD1B5\uACC4 \uAC15\uC870" },
5350
+ { id: "v2-naver-map", name: "\uC9C0\uB3C4", nameEn: "Map", category: "content", description: "embed-block \uAE30\uBC18 \uC9C0\uB3C4" }
5351
+ ];
5352
+ var V1_PATTERNS = [
5353
+ { id: "hero", name: "\uD788\uC5B4\uB85C", nameEn: "Hero", category: "hero", description: "\uD788\uC5B4\uB85C \uC139\uC158 (3 \uB808\uC774\uC544\uC6C3)" },
5354
+ { id: "cta-banner", name: "CTA \uBC30\uB108", nameEn: "CTA Banner", category: "marketing", description: "\uC561\uC158 \uC720\uB3C4 \uBC30\uB108" },
5355
+ { id: "feature-grid", name: "\uAE30\uB2A5 \uADF8\uB9AC\uB4DC", nameEn: "Feature Grid", category: "content", description: "\uAE30\uB2A5 \uC18C\uAC1C \uCE74\uB4DC" },
5356
+ { id: "pricing-table", name: "\uAC00\uACA9\uD45C", nameEn: "Pricing Table", category: "marketing", description: "\uAC00\uACA9 \uBE44\uAD50 \uD14C\uC774\uBE14" },
5357
+ { id: "team-profile", name: "\uD300 \uC18C\uAC1C", nameEn: "Team Profile", category: "team", description: "\uD300\uC6D0 \uD504\uB85C\uD544 \uCE74\uB4DC" }
5358
+ ];
5359
+ async function commandPatterns(options) {
5360
+ if (options?.public) {
5361
+ const apiBaseUrl = await getApiBaseUrl();
5362
+ const apiKey = await resolveApiKey();
5363
+ const client = new SaeroonApiClient(apiKey, apiBaseUrl);
5364
+ const fetchSpinner = spinner("\uACF5\uAC1C \uD328\uD134\uC744 \uC870\uD68C\uD558\uB294 \uC911...");
5365
+ try {
5366
+ const result = await client.getPublicPatterns({
5367
+ category: options.role,
5368
+ search: options.search,
5369
+ sort: "popular",
5370
+ pageSize: 50
5371
+ });
5372
+ fetchSpinner.stop(chalk19.green(`${result.total}\uAC1C\uC758 \uACF5\uAC1C \uD328\uD134\uC744 \uC870\uD68C\uD588\uC2B5\uB2C8\uB2E4.`));
5373
+ if (result.data.length === 0) {
5374
+ console.log(chalk19.gray("\n \uACF5\uAC1C \uD328\uD134\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\n"));
5375
+ return;
5376
+ }
5377
+ console.log("");
5378
+ console.log(
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")}`
5380
+ );
5381
+ console.log(chalk19.gray(" " + "-".repeat(70)));
5382
+ for (const p of result.data) {
5383
+ const shortId = p.id.substring(0, 8);
5384
+ console.log(
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 ?? "")}`
5386
+ );
5387
+ }
5388
+ console.log(chalk19.gray(`
5389
+ Fork: npx @saeroon/cli fork-pattern <pattern-id> --site-id <site-id>
5390
+ `));
5391
+ } catch (error) {
5392
+ fetchSpinner.stop(
5393
+ chalk19.red(`\uACF5\uAC1C \uD328\uD134 \uC870\uD68C \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5394
+ );
5395
+ process.exit(1);
5396
+ }
5397
+ return;
5398
+ }
5399
+ console.log(chalk19.bold("\n V2 Patterns:\n"));
5400
+ const patterns = options?.role ? V2_PATTERNS.filter((p) => p.category === options.role) : V2_PATTERNS;
5401
+ for (const p of patterns) {
5402
+ console.log(` ${chalk19.cyan(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk19.gray(p.description)}`);
5403
+ }
5404
+ console.log(chalk19.bold("\n V1 Patterns (legacy):\n"));
5405
+ for (const p of V1_PATTERNS) {
5406
+ console.log(` ${chalk19.gray(p.id.padEnd(24))} ${p.name.padEnd(12)} ${chalk19.gray(p.description)}`);
5407
+ }
5408
+ console.log(chalk19.gray(`
5409
+ \uCD1D ${V2_PATTERNS.length + V1_PATTERNS.length}\uAC1C \uB0B4\uC7A5 \uD328\uD134`));
5410
+ console.log(chalk19.gray(` \uACF5\uAC1C \uD328\uD134 \uC870\uD68C: npx @saeroon/cli patterns --public
5411
+ `));
5412
+ }
5413
+ async function commandAddPattern(patternId) {
5414
+ const pattern = V2_PATTERNS.find((p) => p.id === patternId) || V1_PATTERNS.find((p) => p.id === patternId);
5415
+ if (!pattern) {
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(", ")));
5418
+ process.exit(1);
5419
+ }
5420
+ console.log(chalk19.cyan(`
5421
+ Pattern: ${pattern.name} (${pattern.nameEn})`));
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"));
5426
+ }
5427
+ async function commandForkPattern(patternId, options) {
5428
+ if (!options.siteId) {
5429
+ console.error(chalk19.red("--site-id \uC635\uC158\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
5430
+ process.exit(1);
5431
+ }
5432
+ const apiBaseUrl = await getApiBaseUrl();
5433
+ const apiKey = await resolveApiKey();
5434
+ const client = new SaeroonApiClient(apiKey, apiBaseUrl);
5435
+ const forkSpinner = spinner("\uACF5\uAC1C \uD328\uD134\uC744 Fork\uD558\uB294 \uC911...");
5436
+ try {
5437
+ await client.forkPublicPattern(patternId, options.siteId);
5438
+ forkSpinner.stop(chalk19.green(`\uD328\uD134\uC774 \uC0AC\uC774\uD2B8 ${options.siteId}\uB85C Fork\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
5439
+ } catch (error) {
5440
+ forkSpinner.stop(
5441
+ chalk19.red(`Fork \uC2E4\uD328: ${error instanceof Error ? error.message : String(error)}`)
5442
+ );
5443
+ process.exit(1);
5444
+ }
5445
+ }
5446
+
4572
5447
  // src/index.ts
4573
5448
  var require2 = createRequire(import.meta.url);
4574
5449
  var { version } = require2("../package.json");
@@ -4580,9 +5455,13 @@ program.command("init").description("\uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654
4580
5455
  program.command("preview").description("\uC2A4\uD0A4\uB9C8 \uD30C\uC77C\uC744 \uAC10\uC2DC\uD558\uACE0 \uC2E4\uC2DC\uAC04 \uD504\uB9AC\uBDF0 \uC81C\uACF5").argument("[schema-path]", "\uC2A4\uD0A4\uB9C8 JSON \uD30C\uC77C \uACBD\uB85C", "schema.json").option("--api-key <key>", "API Key").option("--device <device>", "\uD504\uB9AC\uBDF0 \uB514\uBC14\uC774\uC2A4: mobile|tablet|desktop", "desktop").option("--mode <mode>", "\uD504\uB9AC\uBDF0 \uBAA8\uB4DC: rest|ws", "rest").action(commandPreview);
4581
5456
  program.command("validate").description("\uC2A4\uD0A4\uB9C8 \uC720\uD6A8\uC131 \uAC80\uC99D + \uD488\uC9C8 \uC810\uC218 \uD655\uC778").argument("[schema-path]", "\uAC80\uC99D\uD560 \uC2A4\uD0A4\uB9C8 JSON \uD30C\uC77C \uACBD\uB85C", "schema.json").option("--api-key <key>", "API Key").option("--local", "\uB85C\uCEEC \uAC80\uC99D\uB9CC \uC218\uD589 (\uB124\uD2B8\uC6CC\uD06C \uBD88\uD544\uC694)").action(commandValidate);
4582
5457
  program.command("blocks").description("\uBE14\uB85D \uCE74\uD0C8\uB85C\uADF8 \uC870\uD68C").argument("[block-type]", "\uD2B9\uC815 \uBE14\uB85D \uD0C0\uC785 \uC0C1\uC138 \uC870\uD68C").action(commandBlocks);
5458
+ program.command("patterns").description("\uD328\uD134 \uCE74\uD0C8\uB85C\uADF8 \uC870\uD68C").option("--role <category>", "\uCE74\uD14C\uACE0\uB9AC \uD544\uD130").option("--public", "\uACF5\uAC1C \uD328\uD134 \uBAA9\uB85D \uC870\uD68C (API)").option("--search <query>", "\uD328\uD134 \uAC80\uC0C9 (--public\uACFC \uD568\uAED8 \uC0AC\uC6A9)").action(commandPatterns);
5459
+ program.command("add-pattern").description("\uC2A4\uD0A4\uB9C8\uC5D0 \uD328\uD134 \uC0BD\uC785 (\uC5D0\uB514\uD130/MCP \uC5F0\uB3D9)").argument("<pattern-id>", "\uC0BD\uC785\uD560 \uD328\uD134 ID").action(commandAddPattern);
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);
4583
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);
4584
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);
4585
- 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);
4586
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);
4587
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);
4588
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);