@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.
Files changed (2) hide show
  1. package/dist/index.js +202 -40
  2. 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 (11)
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
- // header, footer, nav, section, article \uB4F1 \uC2DC\uB9E8\uD2F1 \uD0DC\uADF8\uB294 \uC720\uC9C0
4036
- if (['header', 'footer', 'nav', 'section', 'article', 'aside', 'main'].includes(tag)) {
4037
- // main\uC740 \uADF8 \uC548\uC758 \uC790\uC2DD\uC744 \uD3BC\uCE68
4038
- if (tag === 'main') {
4039
- const children = Array.from(el.children).filter(c => c.getBoundingClientRect().height >= 10);
4040
- if (children.length > 1) {
4041
- result.push(...children);
4042
- expanded = true;
4043
- } else {
4044
- result.push(el);
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
- // div \uB798\uD37C \uBC97\uAE30\uAE30
4052
- const children = Array.from(el.children).filter(c => c.getBoundingClientRect().height >= 10);
4053
- if (children.length === 1) {
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
- const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
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: { ...process.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: { ...process.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 result = spawnSync2("npx", [
5322
- "playwright",
5323
- "screenshot",
5324
- "--browser",
5325
- "chromium",
5326
- "--viewport-size",
5327
- `${width},${height}`,
5328
- "--wait-for-timeout",
5329
- "3000",
5330
- "--full-page",
5331
- url,
5332
- outputPath
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: 6e4
5481
+ timeout: 9e4,
5482
+ env: getNodeEnvWithGlobalModules2()
5336
5483
  });
5337
5484
  if (result.status !== 0) {
5338
5485
  const stderr = result.stderr?.toString() ?? "";
5339
- throw new Error(`\uC2A4\uD06C\uB9B0\uC0F7 \uCEA1\uCC98 \uC2E4\uD328: ${stderr || "unknown error"}`);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saeroon/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Saeroon Hosting developer CLI — preview, validate, and deploy sites & templates",
5
5
  "private": false,
6
6
  "type": "module",