@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.
- package/dist/index.js +1118 -239
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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((
|
|
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
|
-
|
|
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
|
-
//
|
|
2472
|
+
// Primitives (10)
|
|
2473
|
+
"container",
|
|
2430
2474
|
"text-block",
|
|
2431
2475
|
"heading-block",
|
|
2432
2476
|
"button-block",
|
|
2433
2477
|
"image-block",
|
|
2434
|
-
"
|
|
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
|
-
//
|
|
2518
|
-
"
|
|
2519
|
-
"
|
|
2520
|
-
"
|
|
2521
|
-
"
|
|
2522
|
-
"
|
|
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.
|
|
2540
|
-
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 });
|
|
2541
2506
|
}
|
|
2542
|
-
if (!s.
|
|
2543
|
-
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 });
|
|
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.
|
|
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.
|
|
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
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
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((
|
|
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", () =>
|
|
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
|
|
2937
|
-
|
|
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((
|
|
2943
|
-
queue.push(
|
|
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
|
|
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/
|
|
3827
|
+
// src/commands/analyze.ts
|
|
3866
3828
|
import chalk15 from "chalk";
|
|
3867
|
-
import { resolve as
|
|
3868
|
-
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";
|
|
3869
4483
|
async function commandCompare(options) {
|
|
3870
|
-
console.log(
|
|
4484
|
+
console.log(chalk16.bold("\n\uC2DC\uAC01 \uBE44\uAD50 (Visual Diff)\n"));
|
|
3871
4485
|
if (!options.ref || !options.preview) {
|
|
3872
|
-
console.error(
|
|
3873
|
-
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"));
|
|
3874
4488
|
process.exit(1);
|
|
3875
4489
|
}
|
|
3876
4490
|
const playwrightAvailable = checkPlaywright();
|
|
3877
4491
|
if (!playwrightAvailable) {
|
|
3878
|
-
console.log(
|
|
3879
|
-
console.log(
|
|
3880
|
-
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"));
|
|
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 =
|
|
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 =
|
|
3889
|
-
const previewScreenshot =
|
|
3890
|
-
|
|
3891
|
-
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"));
|
|
3892
4658
|
const previewSpinner = spinner("\uD504\uB9AC\uBDF0 \uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC911...");
|
|
3893
|
-
|
|
3894
|
-
previewSpinner.stop(
|
|
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
|
-
|
|
4665
|
+
execSync2(
|
|
3900
4666
|
`magick compare -metric AE "${refScreenshot}" "${previewScreenshot}" "${outputPath}" 2>&1`,
|
|
3901
4667
|
{ stdio: "pipe" }
|
|
3902
4668
|
);
|
|
3903
4669
|
} catch {
|
|
3904
|
-
|
|
4670
|
+
execSync2(
|
|
3905
4671
|
`magick composite -blend 50x50 "${refScreenshot}" "${previewScreenshot}" "${outputPath}"`,
|
|
3906
4672
|
{ stdio: "pipe" }
|
|
3907
4673
|
);
|
|
3908
4674
|
}
|
|
3909
|
-
diffSpinner.stop(
|
|
4675
|
+
diffSpinner.stop(chalk16.green(" Diff \uC774\uBBF8\uC9C0 \uC0DD\uC131 \uC644\uB8CC (ImageMagick)"));
|
|
3910
4676
|
} else {
|
|
3911
|
-
|
|
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(
|
|
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
|
-
|
|
4684
|
+
execSync2(`rm -f "${refScreenshot}" "${previewScreenshot}"`, { stdio: "pipe" });
|
|
3919
4685
|
} catch {
|
|
3920
4686
|
}
|
|
3921
4687
|
console.log("");
|
|
3922
|
-
console.log(
|
|
3923
|
-
console.log(
|
|
3924
|
-
console.log(
|
|
3925
|
-
console.log(
|
|
3926
|
-
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}`));
|
|
3927
4693
|
console.log("");
|
|
3928
4694
|
} catch (error) {
|
|
3929
|
-
console.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
|
-
|
|
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
|
-
|
|
4709
|
+
execSync2(`which ${cmd}`, { stdio: "pipe" });
|
|
3944
4710
|
return true;
|
|
3945
4711
|
} catch {
|
|
3946
4712
|
return false;
|
|
3947
4713
|
}
|
|
3948
4714
|
}
|
|
3949
|
-
function
|
|
3950
|
-
const result =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
4868
|
+
console.log(chalk17.bold.underline(" Pages"));
|
|
4103
4869
|
for (const page of result.pages.added) {
|
|
4104
|
-
console.log(` ${
|
|
4870
|
+
console.log(` ${chalk17.green("+")} ${page}`);
|
|
4105
4871
|
}
|
|
4106
4872
|
for (const page of result.pages.removed) {
|
|
4107
|
-
console.log(` ${
|
|
4873
|
+
console.log(` ${chalk17.red("-")} ${page}`);
|
|
4108
4874
|
}
|
|
4109
4875
|
for (const page of result.pages.modified) {
|
|
4110
|
-
console.log(` ${
|
|
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(
|
|
4881
|
+
console.log(chalk17.bold.underline(" Blocks"));
|
|
4116
4882
|
for (const block of result.blocks.added) {
|
|
4117
|
-
console.log(` ${
|
|
4883
|
+
console.log(` ${chalk17.green("+")} ${block}`);
|
|
4118
4884
|
}
|
|
4119
4885
|
for (const block of result.blocks.removed) {
|
|
4120
|
-
console.log(` ${
|
|
4886
|
+
console.log(` ${chalk17.red("-")} ${block}`);
|
|
4121
4887
|
}
|
|
4122
4888
|
for (const block of result.blocks.modified) {
|
|
4123
|
-
console.log(` ${
|
|
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(
|
|
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(` ${
|
|
4897
|
+
console.log(` ${chalk17.green("+")} ${key}: ${chalk17.green(formatValue(draft))}`);
|
|
4132
4898
|
} else if (draft === void 0) {
|
|
4133
|
-
console.log(` ${
|
|
4899
|
+
console.log(` ${chalk17.red("-")} ${key}: ${chalk17.red(formatValue(published))}`);
|
|
4134
4900
|
} else {
|
|
4135
|
-
console.log(` ${
|
|
4136
|
-
console.log(` ${
|
|
4137
|
-
console.log(` ${
|
|
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(
|
|
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(
|
|
4159
|
-
console.error(
|
|
4160
|
-
console.error(
|
|
4161
|
-
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'));
|
|
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(
|
|
4170
|
-
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}`));
|
|
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(
|
|
4184
|
-
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})`));
|
|
4185
4951
|
} catch (error) {
|
|
4186
4952
|
if (error instanceof ApiError) {
|
|
4187
4953
|
fetchSpinner.stop(
|
|
4188
|
-
|
|
4954
|
+
chalk17.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`)
|
|
4189
4955
|
);
|
|
4190
4956
|
} else {
|
|
4191
4957
|
fetchSpinner.stop(
|
|
4192
|
-
|
|
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
|
|
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(
|
|
4226
|
-
console.error(
|
|
4227
|
-
console.error(
|
|
4228
|
-
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'));
|
|
4229
4995
|
console.error("");
|
|
4230
4996
|
process.exit(1);
|
|
4231
4997
|
}
|
|
4232
4998
|
if (projectConfig.templateId) {
|
|
4233
|
-
console.error(
|
|
4234
|
-
console.error(
|
|
4235
|
-
console.error(
|
|
4236
|
-
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"));
|
|
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(
|
|
4244
|
-
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}`));
|
|
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(
|
|
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(
|
|
5030
|
+
console.log(chalk18.dim(" \uCE74\uD14C\uACE0\uB9AC \uBAA9\uB85D:"));
|
|
4265
5031
|
TEMPLATE_CATEGORIES2.forEach((cat, i) => {
|
|
4266
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
4317
|
-
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"));
|
|
4318
5084
|
console.log("");
|
|
4319
5085
|
}
|
|
4320
5086
|
} catch (error) {
|
|
4321
5087
|
if (error instanceof ApiError) {
|
|
4322
|
-
registerSpinner.stop(
|
|
5088
|
+
registerSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4323
5089
|
} else {
|
|
4324
5090
|
registerSpinner.stop(
|
|
4325
|
-
|
|
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(
|
|
4335
|
-
console.error(
|
|
4336
|
-
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"));
|
|
4337
5103
|
console.error("");
|
|
4338
5104
|
process.exit(1);
|
|
4339
5105
|
}
|
|
4340
5106
|
if (!projectConfig.siteId) {
|
|
4341
|
-
console.error(
|
|
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(
|
|
4348
|
-
console.log(
|
|
4349
|
-
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}`));
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
5145
|
+
diffSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4380
5146
|
} else {
|
|
4381
5147
|
diffSpinner.stop(
|
|
4382
|
-
|
|
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(
|
|
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: ${
|
|
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(
|
|
5170
|
+
syncSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4405
5171
|
} else {
|
|
4406
5172
|
syncSpinner.stop(
|
|
4407
|
-
|
|
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(
|
|
5194
|
+
fetchSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4429
5195
|
} else {
|
|
4430
5196
|
fetchSpinner.stop(
|
|
4431
|
-
|
|
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(
|
|
4441
|
-
console.error(
|
|
4442
|
-
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"));
|
|
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(
|
|
5247
|
+
currentSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4482
5248
|
} else {
|
|
4483
|
-
currentSpinner.stop(
|
|
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(
|
|
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(
|
|
5260
|
+
console.log(chalk18.bold("\n\uD604\uC7AC \uD15C\uD50C\uB9BF \uC815\uBCF4:"));
|
|
4495
5261
|
formatTemplateDetail(current);
|
|
4496
|
-
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"));
|
|
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(
|
|
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(
|
|
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(
|
|
5303
|
+
updateSpinner.stop(chalk18.red(`API \uC5D0\uB7EC (${error.statusCode}): ${error.message}`));
|
|
4538
5304
|
} else {
|
|
4539
5305
|
updateSpinner.stop(
|
|
4540
|
-
|
|
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(
|
|
4554
|
-
if (diff.pages.removed.length > 0) detail.push(
|
|
4555
|
-
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}`));
|
|
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(
|
|
4561
|
-
if (diff.blocks.removed.length > 0) detail.push(
|
|
4562
|
-
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}`));
|
|
4563
5329
|
parts.push(`\uBE14\uB85D ${detail.join(" ")}`);
|
|
4564
5330
|
}
|
|
4565
5331
|
if (settingChanges > 0) {
|
|
4566
|
-
parts.push(`\uC124\uC815 ${
|
|
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("
|
|
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);
|