@saeroon/cli 0.2.7 → 0.2.9
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 +202 -40
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2514,19 +2514,27 @@ var ELEMENT_TYPE_PROPS = {
|
|
|
2514
2514
|
"pre",
|
|
2515
2515
|
"code",
|
|
2516
2516
|
"progress",
|
|
2517
|
-
"meter"
|
|
2517
|
+
"meter",
|
|
2518
|
+
"output",
|
|
2519
|
+
"label",
|
|
2520
|
+
"search",
|
|
2521
|
+
"time",
|
|
2522
|
+
"mark",
|
|
2523
|
+
"abbr"
|
|
2518
2524
|
]),
|
|
2519
2525
|
coerce: () => null
|
|
2520
2526
|
}
|
|
2521
2527
|
};
|
|
2522
2528
|
var VALID_BLOCK_TYPES = /* @__PURE__ */ new Set([
|
|
2523
|
-
// Primitives (
|
|
2529
|
+
// Primitives (13)
|
|
2524
2530
|
"container",
|
|
2525
2531
|
"text-block",
|
|
2532
|
+
"rich-text-block",
|
|
2526
2533
|
"heading-block",
|
|
2527
2534
|
"button-block",
|
|
2528
2535
|
"image-block",
|
|
2529
2536
|
"video-block",
|
|
2537
|
+
"audio-block",
|
|
2530
2538
|
"embed-block",
|
|
2531
2539
|
"icon-block",
|
|
2532
2540
|
"input-block",
|
|
@@ -3918,9 +3926,18 @@ import { resolve as resolve13, join as join2 } from "path";
|
|
|
3918
3926
|
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
3919
3927
|
|
|
3920
3928
|
// src/scripts/analyze-reference.ts
|
|
3921
|
-
import { spawnSync } from "child_process";
|
|
3929
|
+
import { spawnSync, execSync } from "child_process";
|
|
3922
3930
|
import { writeFile as writeFile7, mkdir as mkdir3 } from "fs/promises";
|
|
3923
3931
|
import { join } from "path";
|
|
3932
|
+
function getNodeEnvWithGlobalModules() {
|
|
3933
|
+
try {
|
|
3934
|
+
const globalRoot = execSync("npm root -g", { encoding: "utf-8" }).trim();
|
|
3935
|
+
const existing = process.env.NODE_PATH || "";
|
|
3936
|
+
return { ...process.env, NODE_PATH: existing ? `${existing}:${globalRoot}` : globalRoot };
|
|
3937
|
+
} catch {
|
|
3938
|
+
return { ...process.env };
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3924
3941
|
var VIEWPORTS = [
|
|
3925
3942
|
{ name: "mobile", width: 375, height: 812 },
|
|
3926
3943
|
{ name: "tablet", width: 768, height: 1024 },
|
|
@@ -4032,32 +4049,43 @@ var EXTRACTION_SCRIPT = `
|
|
|
4032
4049
|
let expanded = false;
|
|
4033
4050
|
els.forEach(el => {
|
|
4034
4051
|
const tag = el.tagName.toLowerCase();
|
|
4035
|
-
//
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4052
|
+
// \uC2DC\uB9E8\uD2F1 \uD0DC\uADF8: header/footer/nav/section/article/aside\uB294 \uC139\uC158\uC73C\uB85C \uC720\uC9C0
|
|
4053
|
+
const keepTags = new Set(['header', 'footer', 'nav', 'section', 'article', 'aside']);
|
|
4054
|
+
// main/form\uC740 \uB798\uD37C \uC5ED\uD560\uC774 \uB9CE\uC74C \u2014 \uC790\uC2DD\uC774 \uC5EC\uB7EC \uAC1C\uBA74 \uD3BC\uCE68
|
|
4055
|
+
const unwrapTags = new Set(['main', 'form']);
|
|
4056
|
+
|
|
4057
|
+
if (keepTags.has(tag)) {
|
|
4058
|
+
result.push(el);
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
|
|
4062
|
+
const children = Array.from(el.children).filter(c => c.getBoundingClientRect().height >= 10);
|
|
4063
|
+
|
|
4064
|
+
if (unwrapTags.has(tag)) {
|
|
4065
|
+
// main/form: \uC790\uC2DD\uC774 \uC5EC\uB7EC \uAC1C\uBA74 \uD3BC\uCE68
|
|
4066
|
+
if (children.length > 1) {
|
|
4067
|
+
result.push(...children);
|
|
4068
|
+
expanded = true;
|
|
4046
4069
|
} else {
|
|
4047
4070
|
result.push(el);
|
|
4048
4071
|
}
|
|
4049
4072
|
return;
|
|
4050
4073
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
if (children.length
|
|
4054
|
-
// \uB2E8\uC77C \uC790\uC2DD wrapper \u2192 \uBC97\uAE40
|
|
4055
|
-
result.push(...children);
|
|
4056
|
-
expanded = true;
|
|
4057
|
-
} else if (children.length > 1 && depth >= 4) {
|
|
4058
|
-
// \uAE4A\uC774 \uC0C1\uC704 2\uB2E8\uACC4(depth 5\u21924)\uC5D0\uC11C\uB9CC multi-child \uD655\uC7A5
|
|
4074
|
+
|
|
4075
|
+
// div \uB4F1 \uBE44\uC2DC\uB9E8\uD2F1 \uB798\uD37C \uBC97\uAE30\uAE30
|
|
4076
|
+
if (children.length <= 1 && children.length > 0) {
|
|
4059
4077
|
result.push(...children);
|
|
4060
4078
|
expanded = true;
|
|
4079
|
+
} else if (children.length > 1) {
|
|
4080
|
+
// \uD398\uC774\uC9C0 \uB192\uC774 \uB300\uBD80\uBD84\uC744 \uCC28\uC9C0\uD558\uB294 \uB798\uD37C\uB294 \uD3BC\uCE68
|
|
4081
|
+
const elHeight = el.getBoundingClientRect().height;
|
|
4082
|
+
const pageHeight = document.body.scrollHeight || 1;
|
|
4083
|
+
if (elHeight > pageHeight * 0.5) {
|
|
4084
|
+
result.push(...children);
|
|
4085
|
+
expanded = true;
|
|
4086
|
+
} else {
|
|
4087
|
+
result.push(el);
|
|
4088
|
+
}
|
|
4061
4089
|
} else {
|
|
4062
4090
|
result.push(el);
|
|
4063
4091
|
}
|
|
@@ -4110,7 +4138,18 @@ var EXTRACTION_SCRIPT = `
|
|
|
4110
4138
|
});
|
|
4111
4139
|
|
|
4112
4140
|
// heading hierarchy (\uC2DC\uB9E8\uD2F1 + \uB300\uD615 \uD3F0\uD2B8 pseudo-heading)
|
|
4113
|
-
|
|
4141
|
+
// \uD31D\uC5C5/\uBAA8\uB2EC/dialog \uB0B4\uBD80 heading \uC81C\uC678
|
|
4142
|
+
const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')).filter(h => {
|
|
4143
|
+
const popup = h.closest('dialog, [class*=popup], [class*=modal], [class*=layer], [role=dialog], [style*="display: none"], [style*="display:none"]');
|
|
4144
|
+
if (popup) {
|
|
4145
|
+
const rect = popup.getBoundingClientRect();
|
|
4146
|
+
// \uD654\uBA74 \uBC16\uC774\uAC70\uB098 display:none\uC774\uBA74 \uD31D\uC5C5 heading
|
|
4147
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
4148
|
+
// \uD31D\uC5C5\uC774 \uD654\uBA74 \uAC00\uC6B4\uB370 \uB5A0 \uC788\uC73C\uBA74 (\uC804\uCCB4 \uB108\uBE44\uC758 90% \uBBF8\uB9CC) \uD31D\uC5C5 heading
|
|
4149
|
+
if (rect.width < window.innerWidth * 0.9) return false;
|
|
4150
|
+
}
|
|
4151
|
+
return true;
|
|
4152
|
+
});
|
|
4114
4153
|
const headingHierarchy = headings.map(h => h.tagName.toLowerCase() + ': ' + (h.textContent || '').trim().slice(0, 80));
|
|
4115
4154
|
|
|
4116
4155
|
// \uC2DC\uB9E8\uD2F1 heading\uC774 \uBD80\uC871\uD558\uBA74 \uD070 \uD3F0\uD2B8 \uC694\uC18C\uB97C pseudo-heading\uC73C\uB85C \uBCF4\uC644
|
|
@@ -4341,10 +4380,40 @@ var EXTRACTION_SCRIPT = `
|
|
|
4341
4380
|
const hasHoverEffects = transitionProps.size > 0;
|
|
4342
4381
|
|
|
4343
4382
|
// \u2500\u2500 4. Images \u2500\u2500
|
|
4383
|
+
// inline style + \uC8FC\uC694 \uCEE8\uD14C\uC774\uB108\uC758 computed background-image \uD0D0\uC0C9
|
|
4344
4384
|
const imgEls = document.querySelectorAll('img, picture source, [style*="background-image"]');
|
|
4345
4385
|
const images = [];
|
|
4346
4386
|
const seenImageSrcs = new Set();
|
|
4347
4387
|
|
|
4388
|
+
// computed background-image\uAC00 \uC788\uB294 \uCEE8\uD14C\uC774\uB108 \uC694\uC18C \uCD94\uAC00 \uD0D0\uC0C9
|
|
4389
|
+
const bgCandidates = document.querySelectorAll('div, section, header, footer, main, article, aside, figure, span, a');
|
|
4390
|
+
const bgCandidateArr = Array.from(bgCandidates).filter((_, i) => i % Math.max(1, Math.ceil(bgCandidates.length / 300)) === 0);
|
|
4391
|
+
bgCandidateArr.forEach(el => {
|
|
4392
|
+
const bg = getComputedProp(el, 'background-image');
|
|
4393
|
+
if (bg && bg !== 'none') {
|
|
4394
|
+
const match = bg.match(/url\\(["']?(.+?)["']?\\)/);
|
|
4395
|
+
if (match && match[1] && !match[1].startsWith('data:') && !match[1].includes('.svg')) {
|
|
4396
|
+
const src = match[1];
|
|
4397
|
+
if (seenImageSrcs.has(src)) return;
|
|
4398
|
+
seenImageSrcs.add(src);
|
|
4399
|
+
const rect = el.getBoundingClientRect();
|
|
4400
|
+
if (rect.width < 20 || rect.height < 20) return;
|
|
4401
|
+
const parentSection = el.closest('section, header, footer, main, [class*=hero], [class*=banner]');
|
|
4402
|
+
const parentRole = parentSection ? inferSectionRole(parentSection) : 'unknown';
|
|
4403
|
+
images.push({
|
|
4404
|
+
src: src.slice(0, 500),
|
|
4405
|
+
alt: '',
|
|
4406
|
+
width: Math.round(rect.width),
|
|
4407
|
+
height: Math.round(rect.height),
|
|
4408
|
+
aspectRatio: guessAspectRatio(Math.round(rect.width), Math.round(rect.height)),
|
|
4409
|
+
role: rect.width > window.innerWidth * 0.8 && rect.height > 300 ? 'hero-bg' : 'background',
|
|
4410
|
+
dominantColor: '',
|
|
4411
|
+
position: parentRole,
|
|
4412
|
+
});
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
});
|
|
4416
|
+
|
|
4348
4417
|
imgEls.forEach(el => {
|
|
4349
4418
|
let src = '';
|
|
4350
4419
|
let alt = '';
|
|
@@ -4634,6 +4703,22 @@ function captureScreenshot(url, outputPath, width, height, timeout) {
|
|
|
4634
4703
|
// \uD31D\uC5C5 \uC790\uB3D9 \uB2EB\uAE30
|
|
4635
4704
|
await page.evaluate(() => { ${POPUP_DISMISS_SCRIPT} });
|
|
4636
4705
|
await page.waitForTimeout(500);
|
|
4706
|
+
// scroll simulation \u2014 lazy-load/IntersectionObserver \uCF58\uD150\uCE20 \uD2B8\uB9AC\uAC70
|
|
4707
|
+
await page.evaluate(async () => {
|
|
4708
|
+
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
4709
|
+
const scrollHeight = document.body.scrollHeight;
|
|
4710
|
+
const viewportHeight = window.innerHeight;
|
|
4711
|
+
const step = Math.floor(viewportHeight * 0.7);
|
|
4712
|
+
for (let y = 0; y < scrollHeight; y += step) {
|
|
4713
|
+
window.scrollTo(0, y);
|
|
4714
|
+
await delay(300);
|
|
4715
|
+
}
|
|
4716
|
+
window.scrollTo(0, scrollHeight);
|
|
4717
|
+
await delay(500);
|
|
4718
|
+
window.scrollTo(0, 0);
|
|
4719
|
+
await delay(500);
|
|
4720
|
+
});
|
|
4721
|
+
await page.waitForTimeout(1000);
|
|
4637
4722
|
await page.screenshot({ path: ${JSON.stringify(outputPath)}, fullPage: true });
|
|
4638
4723
|
await browser.close();
|
|
4639
4724
|
})().catch(e => {
|
|
@@ -4644,7 +4729,7 @@ function captureScreenshot(url, outputPath, width, height, timeout) {
|
|
|
4644
4729
|
const result = spawnSync("node", ["-e", scriptContent], {
|
|
4645
4730
|
stdio: "pipe",
|
|
4646
4731
|
timeout: timeout + 3e4,
|
|
4647
|
-
env:
|
|
4732
|
+
env: getNodeEnvWithGlobalModules()
|
|
4648
4733
|
});
|
|
4649
4734
|
if (result.status !== 0) {
|
|
4650
4735
|
const stderr = result.stderr?.toString() ?? "";
|
|
@@ -4686,6 +4771,22 @@ async function extractPageData(url, timeout) {
|
|
|
4686
4771
|
await page.evaluate(() => { ${POPUP_DISMISS_SCRIPT} });
|
|
4687
4772
|
await page.waitForTimeout(500);
|
|
4688
4773
|
|
|
4774
|
+
// scroll simulation \u2014 lazy-load \uCF58\uD150\uCE20 \uD2B8\uB9AC\uAC70 (DOM \uCD94\uCD9C \uC804)
|
|
4775
|
+
await page.evaluate(async () => {
|
|
4776
|
+
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
4777
|
+
const scrollHeight = document.body.scrollHeight;
|
|
4778
|
+
const step = Math.floor(window.innerHeight * 0.7);
|
|
4779
|
+
for (let y = 0; y < scrollHeight; y += step) {
|
|
4780
|
+
window.scrollTo(0, y);
|
|
4781
|
+
await delay(300);
|
|
4782
|
+
}
|
|
4783
|
+
window.scrollTo(0, scrollHeight);
|
|
4784
|
+
await delay(500);
|
|
4785
|
+
window.scrollTo(0, 0);
|
|
4786
|
+
await delay(500);
|
|
4787
|
+
});
|
|
4788
|
+
await page.waitForTimeout(1000);
|
|
4789
|
+
|
|
4689
4790
|
const data = await page.evaluate(${JSON.stringify(EXTRACTION_SCRIPT)});
|
|
4690
4791
|
await browser.close();
|
|
4691
4792
|
process.stdout.write(JSON.stringify(data));
|
|
@@ -4697,7 +4798,7 @@ async function extractPageData(url, timeout) {
|
|
|
4697
4798
|
const result = spawnSync("node", ["-e", scriptContent], {
|
|
4698
4799
|
stdio: "pipe",
|
|
4699
4800
|
timeout: timeout + 3e4,
|
|
4700
|
-
env:
|
|
4801
|
+
env: getNodeEnvWithGlobalModules()
|
|
4701
4802
|
});
|
|
4702
4803
|
if (result.status !== 0) {
|
|
4703
4804
|
const stderr = result.stderr?.toString() ?? "";
|
|
@@ -5085,6 +5186,15 @@ import chalk16 from "chalk";
|
|
|
5085
5186
|
import { writeFile as writeFile9, mkdir as mkdir5 } from "fs/promises";
|
|
5086
5187
|
import { resolve as resolve14, join as join3 } from "path";
|
|
5087
5188
|
import { execSync as execSync2, spawnSync as spawnSync2 } from "child_process";
|
|
5189
|
+
function getNodeEnvWithGlobalModules2() {
|
|
5190
|
+
try {
|
|
5191
|
+
const globalRoot = execSync2("npm root -g", { encoding: "utf-8" }).trim();
|
|
5192
|
+
const existing = process.env.NODE_PATH || "";
|
|
5193
|
+
return { ...process.env, NODE_PATH: existing ? `${existing}:${globalRoot}` : globalRoot };
|
|
5194
|
+
} catch {
|
|
5195
|
+
return { ...process.env };
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5088
5198
|
async function commandCompare(options) {
|
|
5089
5199
|
console.log(chalk16.bold("\n\uC2DC\uAC01 \uBE44\uAD50 (Visual Diff)\n"));
|
|
5090
5200
|
if (!options.ref || !options.preview) {
|
|
@@ -5318,25 +5428,77 @@ function checkCommand(cmd) {
|
|
|
5318
5428
|
}
|
|
5319
5429
|
}
|
|
5320
5430
|
function captureScreenshot2(url, outputPath, width, height) {
|
|
5321
|
-
const
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5431
|
+
const scriptContent = `
|
|
5432
|
+
const { chromium } = require('playwright');
|
|
5433
|
+
(async () => {
|
|
5434
|
+
const browser = await chromium.launch({ headless: true });
|
|
5435
|
+
const context = await browser.newContext({
|
|
5436
|
+
viewport: { width: ${width}, height: ${height} },
|
|
5437
|
+
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',
|
|
5438
|
+
});
|
|
5439
|
+
const page = await context.newPage();
|
|
5440
|
+
await page.goto(${JSON.stringify(url)}, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
5441
|
+
await page.waitForTimeout(3000);
|
|
5442
|
+
// \uD31D\uC5C5/\uBAA8\uB2EC \uC790\uB3D9 \uB2EB\uAE30
|
|
5443
|
+
await page.evaluate(() => {
|
|
5444
|
+
document.querySelectorAll('dialog[open]').forEach(d => d.close());
|
|
5445
|
+
['[class*=popup] [class*=close]','[class*=modal] [class*=close]',
|
|
5446
|
+
'[aria-label*=close]','[aria-label*="\uB2EB\uAE30"]','[aria-label*=Close]',
|
|
5447
|
+
'.popup-close','.modal-close','.btn-close'].forEach(sel => {
|
|
5448
|
+
document.querySelectorAll(sel).forEach(btn => { try { btn.click(); } catch {} });
|
|
5449
|
+
});
|
|
5450
|
+
document.querySelectorAll('[class*=overlay],[class*=dimmed],.modal-backdrop').forEach(el => {
|
|
5451
|
+
if (el.getBoundingClientRect().width >= window.innerWidth * 0.8) el.style.display = 'none';
|
|
5452
|
+
});
|
|
5453
|
+
document.body.style.overflow = '';
|
|
5454
|
+
document.body.style.position = '';
|
|
5455
|
+
document.documentElement.style.overflow = '';
|
|
5456
|
+
});
|
|
5457
|
+
await page.waitForTimeout(500);
|
|
5458
|
+
// scroll simulation \u2014 lazy-load \uCF58\uD150\uCE20 \uD2B8\uB9AC\uAC70
|
|
5459
|
+
await page.evaluate(async () => {
|
|
5460
|
+
const delay = (ms) => new Promise(r => setTimeout(r, ms));
|
|
5461
|
+
const step = Math.floor(window.innerHeight * 0.7);
|
|
5462
|
+
for (let y = 0; y < document.body.scrollHeight; y += step) {
|
|
5463
|
+
window.scrollTo(0, y);
|
|
5464
|
+
await delay(300);
|
|
5465
|
+
}
|
|
5466
|
+
window.scrollTo(0, document.body.scrollHeight);
|
|
5467
|
+
await delay(500);
|
|
5468
|
+
window.scrollTo(0, 0);
|
|
5469
|
+
await delay(500);
|
|
5470
|
+
});
|
|
5471
|
+
await page.waitForTimeout(1000);
|
|
5472
|
+
await page.screenshot({ path: ${JSON.stringify(outputPath)}, fullPage: true });
|
|
5473
|
+
await browser.close();
|
|
5474
|
+
})().catch(e => {
|
|
5475
|
+
process.stderr.write(e.message);
|
|
5476
|
+
process.exit(1);
|
|
5477
|
+
});
|
|
5478
|
+
`;
|
|
5479
|
+
const result = spawnSync2("node", ["-e", scriptContent], {
|
|
5334
5480
|
stdio: "pipe",
|
|
5335
|
-
timeout:
|
|
5481
|
+
timeout: 9e4,
|
|
5482
|
+
env: getNodeEnvWithGlobalModules2()
|
|
5336
5483
|
});
|
|
5337
5484
|
if (result.status !== 0) {
|
|
5338
5485
|
const stderr = result.stderr?.toString() ?? "";
|
|
5339
|
-
|
|
5486
|
+
const fallback = spawnSync2("npx", [
|
|
5487
|
+
"playwright",
|
|
5488
|
+
"screenshot",
|
|
5489
|
+
"--browser",
|
|
5490
|
+
"chromium",
|
|
5491
|
+
"--viewport-size",
|
|
5492
|
+
`${width},${height}`,
|
|
5493
|
+
"--wait-for-timeout",
|
|
5494
|
+
"3000",
|
|
5495
|
+
"--full-page",
|
|
5496
|
+
url,
|
|
5497
|
+
outputPath
|
|
5498
|
+
], { stdio: "pipe", timeout: 6e4 });
|
|
5499
|
+
if (fallback.status !== 0) {
|
|
5500
|
+
throw new Error(`\uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC2E4\uD328: ${stderr || "unknown error"}`);
|
|
5501
|
+
}
|
|
5340
5502
|
}
|
|
5341
5503
|
}
|
|
5342
5504
|
|