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