@zalkera/client 0.21.1 → 0.21.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/bin/check-aeo-surfaces.mjs +75 -18
- package/bin/validate-storefront.mjs +637 -156
- package/dist/index.cjs +12 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +12 -16
- package/dist/index.js.map +1 -1
- package/package.json +65 -60
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
-
? {
|
|
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([
|
|
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(
|
|
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(
|
|
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 ?? [])
|
|
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(
|
|
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}` : "";
|