@zalkera/client 0.21.2 → 0.21.4

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.
@@ -113,7 +113,9 @@ const printOnly = argv.includes("--print-guarantees");
113
113
  */
114
114
  const siteWideOnly = argv.includes("--site-wide-only");
115
115
  if (!printOnly && !siteUrl) {
116
- die("사용: zalkera-aeo-check <사이트URL> --category <코드> [--category …] [--code …] [--version …] [--out …]\n 무주장 사이트: zalkera-aeo-check <사이트URL> --site-wide-only\n (잣대 해석만 확인: zalkera-aeo-check --print-guarantees)");
116
+ die(
117
+ "사용: zalkera-aeo-check <사이트URL> --category <코드> [--category …] [--code …] [--version …] [--out …]\n 무주장 사이트: zalkera-aeo-check <사이트URL> --site-wide-only\n (잣대 해석만 확인: zalkera-aeo-check --print-guarantees)",
118
+ );
117
119
  }
118
120
  if (!printOnly && siteWideOnly && categories.length > 0) {
119
121
  // 둘 다 주면 어느 쪽을 원한 것인지 알 수 없다. 조용히 한쪽을 이기게 하면 "쟀다고 생각한 것"과
@@ -234,7 +236,9 @@ if (printOnly) {
234
236
  console.log(`· 경로 ${specPath}`);
235
237
  console.log(`· 보장표 rev ${spec.contractRev} · 카테고리 ${[...byCategory.keys()].join(", ")}`);
236
238
  if (carriedDrift) console.warn(`⚠️ ${carriedDrift}`);
237
- console.log(`· 사이트 축(무주장) 검사 항목 ${(spec.siteWide?.requirements ?? []).map((r) => r.id).join(", ")} — \`--site-wide-only\``);
239
+ console.log(
240
+ `· 사이트 축(무주장) 검사 항목 ${(spec.siteWide?.requirements ?? []).map((r) => r.id).join(", ")} — \`--site-wide-only\``,
241
+ );
238
242
  console.log("· 잣대 해석 성공 — 이제 사이트 URL 과 --category(또는 --site-wide-only)를 주면 실제로 잽니다.");
239
243
  process.exit(0);
240
244
  }
@@ -243,7 +247,9 @@ for (const c of categories) {
243
247
  if (!byCategory.has(c)) {
244
248
  // 표에 없는 이름은 **순수 라벨**이다(예: "미니멀"). 보장 주장이 없으므로 잴 것도 없고,
245
249
  // 억지로 통과시킨 스냅샷을 만들면 promote 게이트가 의미를 잃는다.
246
- die(`'${c}' 는 보장표에 없는 이름이라 검사 대상이 아니다(순수 라벨). 등재된 카테고리: ${[...byCategory.keys()].join(", ")}`);
250
+ die(
251
+ `'${c}' 는 보장표에 없는 이름이라 검사 대상이 아니다(순수 라벨). 등재된 카테고리: ${[...byCategory.keys()].join(", ")}`,
252
+ );
247
253
  }
248
254
  }
249
255
 
@@ -400,7 +406,9 @@ async function fetchText(path) {
400
406
  }
401
407
  }
402
408
 
403
- console.log(`· 대상 ${siteUrl} · ${siteWideOnly ? "무주장(사이트 축만)" : `카테고리 ${categories.join(", ")}`} · 보장표 rev ${spec.contractRev}`);
409
+ console.log(
410
+ `· 대상 ${siteUrl} · ${siteWideOnly ? "무주장(사이트 축만)" : `카테고리 ${categories.join(", ")}`} · 보장표 rev ${spec.contractRev}`,
411
+ );
404
412
  console.log(`· 잣대 출처 ${resolved.source} — ${specPath}`);
405
413
  if (carriedDrift) console.warn(`⚠️ ${carriedDrift}`);
406
414
 
@@ -435,7 +443,9 @@ const crawled = await crawlPages({
435
443
  });
436
444
 
437
445
  if (crawled.pages.size === 0) {
438
- die(`크롤한 페이지가 0건이다 — 사이트가 응답하지 않는다.\n${crawled.fetchFailures.map((f) => ` · ${f}`).join("\n")}`);
446
+ die(
447
+ `크롤한 페이지가 0건이다 — 사이트가 응답하지 않는다.\n${crawled.fetchFailures.map((f) => ` · ${f}`).join("\n")}`,
448
+ );
439
449
  }
440
450
 
441
451
  /** 라우트 → {status, html, ld}. 못 받은 라우트는 여기 없다(= 부재). */
@@ -474,7 +484,11 @@ function judgeSurface(surface, bucket) {
474
484
 
475
485
  // 라우트가 없다 = 표면이 없다. 스킵이 아니라 미충족이다.
476
486
  if (target.kind === "MISSING") {
477
- return {...base, status: "MISSING_ROUTE", detail: `라우트 부재 — ${target.path} 가 응답하지 않거나 실물이 0건이다`};
487
+ return {
488
+ ...base,
489
+ status: "MISSING_ROUTE",
490
+ detail: `라우트 부재 — ${target.path} 가 응답하지 않거나 실물이 0건이다`,
491
+ };
478
492
  }
479
493
 
480
494
  const types = surface.jsonLd ?? [];
@@ -489,9 +503,7 @@ function judgeSurface(surface, bucket) {
489
503
  }
490
504
  for (const child of surface.requiredChildren ?? []) {
491
505
  const carriers =
492
- target.kind === "ANY"
493
- ? [...seen.values()].flatMap((p) => p.ld.nodes)
494
- : target.page.ld.nodes;
506
+ target.kind === "ANY" ? [...seen.values()].flatMap((p) => p.ld.nodes) : target.page.ld.nodes;
495
507
  const ok = carriers.some((n) => found.some((t) => typesOf(n).includes(t)) && nodeContainsType(n, child));
496
508
  if (!ok) {
497
509
  return {...base, at, status: "MISSING_CHILD", detail: `${at} 의 ${found[0]} 안에 ${child} 가 없다`};
@@ -503,7 +515,12 @@ function judgeSurface(surface, bucket) {
503
515
  if (surface.jsonLdProperty) {
504
516
  const pool = target.kind === "ANY" ? [...seen.values()].flatMap((p) => p.ld.nodes) : target.page.ld.nodes;
505
517
  if (!pool.some((n) => n[surface.jsonLdProperty] != null)) {
506
- return {...base, at, status: "MISSING_PROPERTY", detail: `${at} 의 JSON-LD 에 ${surface.jsonLdProperty} 속성이 없다`};
518
+ return {
519
+ ...base,
520
+ at,
521
+ status: "MISSING_PROPERTY",
522
+ detail: `${at} 의 JSON-LD 에 ${surface.jsonLdProperty} 속성이 없다`,
523
+ };
507
524
  }
508
525
  }
509
526
 
@@ -515,7 +532,12 @@ function judgeSurface(surface, bucket) {
515
532
 
516
533
  // ③ 깨진 JSON-LD 는 없는 것보다 나쁘다.
517
534
  if (target.kind === "PAGE" && target.page.ld.malformed.length > 0) {
518
- return {...base, at, status: "MALFORMED_JSONLD", detail: `${at} 의 JSON-LD 파싱 실패 ${target.page.ld.malformed.length}건`};
535
+ return {
536
+ ...base,
537
+ at,
538
+ status: "MALFORMED_JSONLD",
539
+ detail: `${at} 의 JSON-LD 파싱 실패 ${target.page.ld.malformed.length}건`,
540
+ };
519
541
  }
520
542
 
521
543
  return {...base, at, status: "PASS"};
@@ -531,7 +553,11 @@ function judgeSurface(surface, bucket) {
531
553
  function judgeSiteWide(req, requiredRoutePaths) {
532
554
  const base = {id: req.id, bucket: "siteWide", check: req.check};
533
555
  if (req.check === "SITEMAP_COVERS" && requiredRoutePaths === null) {
534
- return {...base, status: "SKIPPED", detail: "보장 카테고리를 주장하지 않아 sitemap 이 덮어야 할 라우트가 정해지지 않는다"};
556
+ return {
557
+ ...base,
558
+ status: "SKIPPED",
559
+ detail: "보장 카테고리를 주장하지 않아 sitemap 이 덮어야 할 라우트가 정해지지 않는다",
560
+ };
535
561
  }
536
562
  switch (req.check) {
537
563
  case "ROBOTS_PRESENT":
@@ -581,7 +607,12 @@ function judgeNegative() {
581
607
  }
582
608
  out.push(
583
609
  hits.length
584
- ? {id: `no-${rule.type}`, bucket: "negative", status: "FORBIDDEN_PRESENT", detail: `${rule.type} 가 나왔다 — ${[...new Set(hits)].join(", ")}`}
610
+ ? {
611
+ id: `no-${rule.type}`,
612
+ bucket: "negative",
613
+ status: "FORBIDDEN_PRESENT",
614
+ detail: `${rule.type} 가 나왔다 — ${[...new Set(hits)].join(", ")}`,
615
+ }
585
616
  : {id: `no-${rule.type}`, bucket: "negative", status: "PASS"},
586
617
  );
587
618
  }
@@ -590,7 +621,18 @@ function judgeNegative() {
590
621
 
591
622
  // ── 실행 ─────────────────────────────────────────────────────────────────────
592
623
 
593
- const FAIL_STATUSES = new Set(["MISSING_ROUTE", "MISSING_JSONLD", "MISSING_CHILD", "MISSING_PROPERTY", "NOT_SSR", "MALFORMED_JSONLD", "MISSING", "RELATIVE_URL", "FORBIDDEN_PRESENT", "ERROR"]);
624
+ const FAIL_STATUSES = new Set([
625
+ "MISSING_ROUTE",
626
+ "MISSING_JSONLD",
627
+ "MISSING_CHILD",
628
+ "MISSING_PROPERTY",
629
+ "NOT_SSR",
630
+ "MALFORMED_JSONLD",
631
+ "MISSING",
632
+ "RELATIVE_URL",
633
+ "FORBIDDEN_PRESENT",
634
+ "ERROR",
635
+ ]);
594
636
 
595
637
  const results = [];
596
638
 
@@ -620,12 +662,24 @@ for (const code of categories) {
620
662
  for (const s of cat.conditional ?? []) {
621
663
  const r = judgeSurface(s, "conditional");
622
664
  // 조건부는 못 찾으면 SKIPPED — "섹션을 안 썼다"와 구별할 수 없다(§EVENT/faq-page why).
623
- surfaces.push(r.status === "PASS" ? r : {...r, status: "SKIPPED", detail: `${r.detail ?? ""} (섹션 미사용일 수 있어 실패로 세지 않는다)`.trim()});
665
+ surfaces.push(
666
+ r.status === "PASS"
667
+ ? r
668
+ : {
669
+ ...r,
670
+ status: "SKIPPED",
671
+ detail: `${r.detail ?? ""} (섹션 미사용일 수 있어 실패로 세지 않는다)`.trim(),
672
+ },
673
+ );
624
674
  }
625
675
  for (const s of cat.planned ?? []) {
626
676
  const r = judgeSurface(s, "planned");
627
677
  // planned 는 게이트가 아니라 T6 작업 지시서다.
628
- surfaces.push(r.status === "PASS" ? {...r, status: "PLANNED_PRESENT"} : {...r, status: "PLANNED_MISSING", tranche: s.tranche ?? null});
678
+ surfaces.push(
679
+ r.status === "PASS"
680
+ ? {...r, status: "PLANNED_PRESENT"}
681
+ : {...r, status: "PLANNED_MISSING", tranche: s.tranche ?? null},
682
+ );
629
683
  }
630
684
 
631
685
  // 이 카테고리가 보장하는 라우트의 실제 경로 — sitemap 커버리지 판정 입력.
@@ -634,7 +688,8 @@ for (const code of categories) {
634
688
  const t = resolveRoute(s.route ?? "any");
635
689
  if (t.kind === "PAGE") requiredRoutePaths.push(t.path);
636
690
  }
637
- for (const req of spec.siteWide?.requirements ?? []) surfaces.push(judgeSiteWide(req, [...new Set(requiredRoutePaths)]));
691
+ for (const req of spec.siteWide?.requirements ?? [])
692
+ surfaces.push(judgeSiteWide(req, [...new Set(requiredRoutePaths)]));
638
693
  surfaces.push(...judgeNegative());
639
694
 
640
695
  const gating = surfaces.filter((s) => s.bucket !== "planned" && s.bucket !== "conditional");
@@ -675,7 +730,9 @@ const snapshot = {
675
730
  const MARK = {PASS: "✅", PLANNED_PRESENT: "✅", SKIPPED: "⏭️ ", PLANNED_MISSING: "📋"};
676
731
  console.log("");
677
732
  for (const r of results) {
678
- console.log(`■ ${r.category ? `${r.category}(${r.label})` : r.label} — ${r.verdict}${r.shipState === "BLOCKED" ? " · 진열 차단(DON'T-SHIP)" : ""}`);
733
+ console.log(
734
+ `■ ${r.category ? `${r.category}(${r.label})` : r.label} — ${r.verdict}${r.shipState === "BLOCKED" ? " · 진열 차단(DON'T-SHIP)" : ""}`,
735
+ );
679
736
  for (const s of r.surfaces) {
680
737
  const mark = MARK[s.status] ?? "❌";
681
738
  const tail = s.detail ? ` — ${s.detail}` : "";