@goflag/cli 0.2.10 → 0.2.12
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/README.md +123 -43
- package/dist/{chunk-DKGBLJ4H.js → chunk-QKXUKHXB.js} +1087 -82
- package/dist/chunk-QKXUKHXB.js.map +1 -0
- package/dist/cli.js +118 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +132 -2
- package/dist/index.js +3 -1
- package/flags.json +1 -1
- package/package.json +2 -2
- package/rules.json +105 -10
- package/dist/chunk-DKGBLJ4H.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -379,6 +379,43 @@ interface ExtractionAsset {
|
|
|
379
379
|
*/
|
|
380
380
|
sizes?: ExtractionAssetSize[];
|
|
381
381
|
}
|
|
382
|
+
/** One tag, named the way the page named it. */
|
|
383
|
+
interface ExtractionHydratedMeta {
|
|
384
|
+
name?: string;
|
|
385
|
+
property?: string;
|
|
386
|
+
httpEquiv?: string;
|
|
387
|
+
content?: string;
|
|
388
|
+
}
|
|
389
|
+
/** One link, named the way the page named it. */
|
|
390
|
+
interface ExtractionHydratedLink {
|
|
391
|
+
rel: string;
|
|
392
|
+
href?: string;
|
|
393
|
+
hreflang?: string;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* What client JavaScript added to, or took from, the `<head>`.
|
|
397
|
+
*
|
|
398
|
+
* The gap between the two passes, at tag granularity — not per `Fact`, which
|
|
399
|
+
* carries which *tag* produced a value and never which *pass*. A value present
|
|
400
|
+
* here is a value the browser shows and a non-JS crawler never sees: the
|
|
401
|
+
* failure mode every `og.*` rule is blind to, because each of them judges the
|
|
402
|
+
* declaration it is given and this is about which declaration that was.
|
|
403
|
+
*
|
|
404
|
+
* **Absent on most runs, and that is not "nothing changed".** The delta only
|
|
405
|
+
* exists where both bodies do: the auto-escalation path, where a static fetch
|
|
406
|
+
* looked empty and a headless pass followed. A `--static` run never renders,
|
|
407
|
+
* and an explicit `--headless` run keeps no static body to compare against.
|
|
408
|
+
*/
|
|
409
|
+
interface ExtractionHydration {
|
|
410
|
+
titleChanged: boolean;
|
|
411
|
+
htmlLangChanged: boolean;
|
|
412
|
+
injectedMetas: ExtractionHydratedMeta[];
|
|
413
|
+
removedMetas: ExtractionHydratedMeta[];
|
|
414
|
+
injectedLinks: ExtractionHydratedLink[];
|
|
415
|
+
removedLinks: ExtractionHydratedLink[];
|
|
416
|
+
/** JSON-LD blocks that exist only after hydration. */
|
|
417
|
+
jsonLdBlocksAdded: number;
|
|
418
|
+
}
|
|
382
419
|
/**
|
|
383
420
|
* The per-page observation. Everything a page rule (or an agent judging a
|
|
384
421
|
* prose rule) may read; nothing else.
|
|
@@ -402,6 +439,11 @@ interface Extraction {
|
|
|
402
439
|
* manifest gets.
|
|
403
440
|
*/
|
|
404
441
|
assets?: Record<string, ExtractionAsset>;
|
|
442
|
+
/**
|
|
443
|
+
* What hydration changed in the `<head>`, when both passes ran and can be
|
|
444
|
+
* compared. Absent means "not established" — never "nothing moved".
|
|
445
|
+
*/
|
|
446
|
+
hydration?: ExtractionHydration;
|
|
405
447
|
}
|
|
406
448
|
|
|
407
449
|
/**
|
|
@@ -517,7 +559,17 @@ interface AdvisoryFinding {
|
|
|
517
559
|
ruleId: string;
|
|
518
560
|
kind: "prose";
|
|
519
561
|
prose: string;
|
|
520
|
-
|
|
562
|
+
/**
|
|
563
|
+
* `null` where no document supports the question.
|
|
564
|
+
*
|
|
565
|
+
* Every page prose rule carries one. The cross-page ones (`./site-prose.ts`)
|
|
566
|
+
* may not: `hreflang.sitemap-mismatch` asks about a disagreement no
|
|
567
|
+
* specification addresses, and inventing a rigor for it would put back the
|
|
568
|
+
* dishonesty that moving it out of the verdict-bearing registry removed.
|
|
569
|
+
* An agent reads this to weight the question; `null` says "weight it on the
|
|
570
|
+
* evidence alone".
|
|
571
|
+
*/
|
|
572
|
+
rigor: Rigor | null;
|
|
521
573
|
sources: string[];
|
|
522
574
|
/**
|
|
523
575
|
* Extraction path → the observed value there, one entry per declared
|
|
@@ -864,6 +916,26 @@ interface GoflagReport {
|
|
|
864
916
|
* touch the summary counts, the verdict, or the exit code.
|
|
865
917
|
*/
|
|
866
918
|
advisories?: ReportAdvisory[];
|
|
919
|
+
/**
|
|
920
|
+
* What each page declared in its `<head>`, as the rules saw it. Present
|
|
921
|
+
* only when asked for (`AuditOptions.extractions`, which `goflag preview`
|
|
922
|
+
* sets).
|
|
923
|
+
*
|
|
924
|
+
* Every other section of this report is a *judgment*: a rule fired, or it
|
|
925
|
+
* did not. This one is the observation the judgments were made from, and it
|
|
926
|
+
* is here because a share card cannot be drawn from a violations list — a
|
|
927
|
+
* page that passes every `og.*` rule still has a title, an image and a
|
|
928
|
+
* description that somebody wants to look at, and none of them appear
|
|
929
|
+
* anywhere else in this file.
|
|
930
|
+
*
|
|
931
|
+
* Keyed by nothing: each entry carries its own `http.finalUrl`, which is the
|
|
932
|
+
* same string `ReportPage.url` and `SeoIssue.pageUrl` use.
|
|
933
|
+
*
|
|
934
|
+
* Covers fewer pages than `pages`, for the same reason `conformance` does:
|
|
935
|
+
* only healthy HTML documents reach the rule layer, so an unreachable page,
|
|
936
|
+
* a non-HTML resource and a canonical-declared duplicate have no entry here.
|
|
937
|
+
*/
|
|
938
|
+
extractions?: Extraction[];
|
|
867
939
|
/** Comparison against a stored baseline, when `--baseline` was given. */
|
|
868
940
|
diff?: ReportDiff;
|
|
869
941
|
diagnostics: {
|
|
@@ -1044,6 +1116,20 @@ interface AuditOptions {
|
|
|
1044
1116
|
* carry no verdict and never affect the gate.
|
|
1045
1117
|
*/
|
|
1046
1118
|
advisories?: boolean;
|
|
1119
|
+
/**
|
|
1120
|
+
* Emit each page's extraction — what its `<head>` declared, as the rules
|
|
1121
|
+
* read it. Off by default: it is the observation, not a finding, and on a
|
|
1122
|
+
* large crawl it is the biggest section of the report.
|
|
1123
|
+
*
|
|
1124
|
+
* `goflag preview` sets it, because a share card is drawn from the
|
|
1125
|
+
* declarations and not from the verdicts about them.
|
|
1126
|
+
*
|
|
1127
|
+
* Unlike `conformance` and `advisories`, this section stops at the report:
|
|
1128
|
+
* `summarize()` does not carry it — a summary of an observation is the
|
|
1129
|
+
* observation with the useful parts removed — and no terminal renderer
|
|
1130
|
+
* prints it. Its one reader is `renderPreview`.
|
|
1131
|
+
*/
|
|
1132
|
+
extractions?: boolean;
|
|
1047
1133
|
}
|
|
1048
1134
|
/**
|
|
1049
1135
|
* Routes present in some locales but absent in others. A "hole" is exactly
|
|
@@ -1215,6 +1301,50 @@ interface RenderOptions {
|
|
|
1215
1301
|
}
|
|
1216
1302
|
declare function renderSummaryTerminal(summary: GoflagSummary, options?: RenderOptions): string;
|
|
1217
1303
|
|
|
1304
|
+
/**
|
|
1305
|
+
* The share-card preview — one self-contained HTML document.
|
|
1306
|
+
*
|
|
1307
|
+
* `renderPreview` is a pure function of a `GoflagReport`, exactly like the
|
|
1308
|
+
* three terminal renderers beside it. It reads `report.extractions`, which is
|
|
1309
|
+
* the only section that carries what a page *declared* rather than what a rule
|
|
1310
|
+
* *decided*: a page can pass every `og.*` rule and still unfurl badly, and no
|
|
1311
|
+
* violations list can show that.
|
|
1312
|
+
*
|
|
1313
|
+
* Two things this file refuses to do, and they are the point.
|
|
1314
|
+
*
|
|
1315
|
+
* **It draws no geometry it cannot source.** Every surface carries the same
|
|
1316
|
+
* rigor scale the rule catalogue uses, because the surfaces are not equally
|
|
1317
|
+
* documented: Facebook, LinkedIn and WhatsApp publish real numbers; Slack calls
|
|
1318
|
+
* its own rendering a "micro-approximation"; Discord publishes nothing at all;
|
|
1319
|
+
* X's card documentation is not reachable, and its shape changed twice since
|
|
1320
|
+
* 2023. A preview that renders all seven identically states seven things with
|
|
1321
|
+
* one confidence, and six of them are not earned.
|
|
1322
|
+
*
|
|
1323
|
+
* **It escapes everything.** The input is a site under audit, which is to say
|
|
1324
|
+
* untrusted text, and the output is a file a human opens in a browser. Values
|
|
1325
|
+
* go through `esc`; URLs that are not `http(s)` never become an `src`.
|
|
1326
|
+
*
|
|
1327
|
+
* Opening the document asks for the image URLs the pages declared, which is
|
|
1328
|
+
* what makes it a preview rather than a table. Those URLs are the site's own
|
|
1329
|
+
* choice and need not point at the audited origin, so the document ships a CSP
|
|
1330
|
+
* that allows images and nothing else — no script, no frame, no fetch — and
|
|
1331
|
+
* every image is `referrerpolicy="no-referrer"`. Rendering itself never touches
|
|
1332
|
+
* the network.
|
|
1333
|
+
*/
|
|
1334
|
+
|
|
1335
|
+
interface RenderPreviewOptions {
|
|
1336
|
+
/** Document `<title>`. Defaults to the audited origin. */
|
|
1337
|
+
title?: string;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Render the whole preview.
|
|
1341
|
+
*
|
|
1342
|
+
* A report without `extractions` renders a document that says so rather than an
|
|
1343
|
+
* empty one: the caller asked for a preview and got a report that cannot make
|
|
1344
|
+
* one, and that is a fact about the run, not an error to swallow.
|
|
1345
|
+
*/
|
|
1346
|
+
declare function renderPreview(report: GoflagReport, options?: RenderPreviewOptions): string;
|
|
1347
|
+
|
|
1218
1348
|
/**
|
|
1219
1349
|
* Stable finding fingerprints.
|
|
1220
1350
|
*
|
|
@@ -1318,4 +1448,4 @@ declare class Logger {
|
|
|
1318
1448
|
private truncate;
|
|
1319
1449
|
}
|
|
1320
1450
|
|
|
1321
|
-
export { type AuditOptions, type AuditPhase, type BrokenLink, type GoflagReport, type GoflagSummary, type LogMode, Logger, type LoggerOptions, type ProgressEvent, type ReportPage, type ReportReciprocityIssue, type RollupLink, type RollupReciprocity, type RollupSeo, SAMPLE_LIMIT, type SeoIssue, type TranslationHole, type UnreachablePage, type Verdict, deriveTranslationHoles, exitCode, fingerprint, renderSummaryTerminal, renderTerminal, routeKey, runAudit, summarize, targetKey };
|
|
1451
|
+
export { type AuditOptions, type AuditPhase, type BrokenLink, type GoflagReport, type GoflagSummary, type LogMode, Logger, type LoggerOptions, type ProgressEvent, type RenderPreviewOptions, type ReportPage, type ReportReciprocityIssue, type RollupLink, type RollupReciprocity, type RollupSeo, SAMPLE_LIMIT, type SeoIssue, type TranslationHole, type UnreachablePage, type Verdict, deriveTranslationHoles, exitCode, fingerprint, renderPreview, renderSummaryTerminal, renderTerminal, routeKey, runAudit, summarize, targetKey };
|
package/dist/index.js
CHANGED
|
@@ -5,19 +5,21 @@ import {
|
|
|
5
5
|
deriveTranslationHoles,
|
|
6
6
|
exitCode,
|
|
7
7
|
fingerprint,
|
|
8
|
+
renderPreview,
|
|
8
9
|
renderSummaryTerminal,
|
|
9
10
|
renderTerminal,
|
|
10
11
|
routeKey,
|
|
11
12
|
runAudit,
|
|
12
13
|
summarize,
|
|
13
14
|
targetKey
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-QKXUKHXB.js";
|
|
15
16
|
export {
|
|
16
17
|
Logger,
|
|
17
18
|
SAMPLE_LIMIT,
|
|
18
19
|
deriveTranslationHoles,
|
|
19
20
|
exitCode,
|
|
20
21
|
fingerprint,
|
|
22
|
+
renderPreview,
|
|
21
23
|
renderSummaryTerminal,
|
|
22
24
|
renderTerminal,
|
|
23
25
|
routeKey,
|
package/flags.json
CHANGED
|
@@ -241,7 +241,7 @@
|
|
|
241
241
|
"default": null,
|
|
242
242
|
"requires": null,
|
|
243
243
|
"group": "output",
|
|
244
|
-
"description": "Roll findings up (dedup by link/rule/code). Pairs with --json for a compact, agent-friendly payload."
|
|
244
|
+
"description": "Roll findings up (dedup by link/rule/code). Pairs with --json for a compact, agent-friendly payload. Not available with --baseline: there the diff is the answer, and --report writes the full report."
|
|
245
245
|
},
|
|
246
246
|
{
|
|
247
247
|
"name": "--timeout",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goflag/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Audits a site for broken links, missing translation pages, a robots.txt that contradicts your pages, and missing or misconfigured SEO metadata. JSON-first.",
|
|
6
6
|
"keywords": [
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"generate:help-fixture": "tsx scripts/generate-help-fixture.ts",
|
|
67
67
|
"prepack": "node ../../scripts/stage-package-docs.mjs",
|
|
68
68
|
"prepublishOnly": "pnpm build",
|
|
69
|
-
"release": "commit-and-tag-version --path . --skip.tag"
|
|
69
|
+
"release": "commit-and-tag-version --path . --skip.tag --tag-prefix v"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"cheerio": "^1.2.0"
|
package/rules.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"counts": {
|
|
3
|
-
"page":
|
|
4
|
-
"site":
|
|
5
|
-
"prose":
|
|
3
|
+
"page": 25,
|
|
4
|
+
"site": 28,
|
|
5
|
+
"prose": 5
|
|
6
6
|
},
|
|
7
7
|
"sources": {
|
|
8
8
|
"apple-web-apps": {
|
|
@@ -319,23 +319,39 @@
|
|
|
319
319
|
"language": "tsx"
|
|
320
320
|
}
|
|
321
321
|
},
|
|
322
|
+
{
|
|
323
|
+
"id": "hreflang.cluster-incomplete",
|
|
324
|
+
"scope": "site",
|
|
325
|
+
"severity": "warning",
|
|
326
|
+
"kind": "boolean",
|
|
327
|
+
"summary": "Every locale the sitemap publishes must appear in the route's `hreflang` cluster",
|
|
328
|
+
"rigor": "vendor-spec",
|
|
329
|
+
"sources": [
|
|
330
|
+
"google-hreflang"
|
|
331
|
+
]
|
|
332
|
+
},
|
|
322
333
|
{
|
|
323
334
|
"id": "hreflang.missing",
|
|
324
335
|
"scope": "site",
|
|
325
336
|
"severity": "error",
|
|
326
337
|
"kind": "boolean",
|
|
327
338
|
"summary": "Pages on a multilingual site must advertise their locale alternates",
|
|
328
|
-
"rigor":
|
|
329
|
-
"sources": [
|
|
339
|
+
"rigor": "vendor-spec",
|
|
340
|
+
"sources": [
|
|
341
|
+
"google-hreflang",
|
|
342
|
+
"whatwg-html-link-types"
|
|
343
|
+
]
|
|
330
344
|
},
|
|
331
345
|
{
|
|
332
346
|
"id": "hreflang.sitemap-mismatch",
|
|
333
|
-
"scope": "
|
|
334
|
-
"severity":
|
|
335
|
-
"kind": "
|
|
336
|
-
"summary": "`<head>` alternates
|
|
347
|
+
"scope": "prose",
|
|
348
|
+
"severity": null,
|
|
349
|
+
"kind": "prose",
|
|
350
|
+
"summary": "`<head>` alternates advertise a locale the sitemap does not list",
|
|
351
|
+
"why": "The two are derived from one intent by different code paths, so a disagreement means one of them is wrong. No specification says which: the declaration methods are equivalent, and a page can legitimately be cross-linked and kept out of the sitemap.",
|
|
337
352
|
"rigor": null,
|
|
338
|
-
"sources": []
|
|
353
|
+
"sources": [],
|
|
354
|
+
"prose": "Your `<head>` advertises a translation your sitemap does not list. Is the sitemap missing an entry, or is the alternate pointing at a page you did not mean to publish?"
|
|
339
355
|
},
|
|
340
356
|
{
|
|
341
357
|
"id": "icons.apple-touch.missing",
|
|
@@ -563,6 +579,34 @@
|
|
|
563
579
|
"language": "tsx"
|
|
564
580
|
}
|
|
565
581
|
},
|
|
582
|
+
{
|
|
583
|
+
"id": "og.image.alt.caption",
|
|
584
|
+
"scope": "page",
|
|
585
|
+
"category": "opengraph",
|
|
586
|
+
"severity": "info",
|
|
587
|
+
"kind": "boolean",
|
|
588
|
+
"summary": "`og:image:alt` describes the image, it does not repeat the title",
|
|
589
|
+
"why": "The protocol asks for a description of what is in the image, and says in the same breath that it is not a caption. A copy of the title is the one value that is always available and never describes anything: a reader who cannot see the card is handed the same words twice and learns nothing about the picture. It also satisfies `og.image.alt` forever, which is why it needs an id of its own.",
|
|
590
|
+
"rigor": "guideline",
|
|
591
|
+
"sources": [
|
|
592
|
+
"ogp"
|
|
593
|
+
],
|
|
594
|
+
"reads": [
|
|
595
|
+
"openGraph.images",
|
|
596
|
+
"openGraph.title",
|
|
597
|
+
"document.title"
|
|
598
|
+
],
|
|
599
|
+
"relates": [
|
|
600
|
+
"og.image.alt",
|
|
601
|
+
"og.image.representative"
|
|
602
|
+
],
|
|
603
|
+
"expected": "an `og:image:alt` that says something the title does not",
|
|
604
|
+
"fix": {
|
|
605
|
+
"title": "Say what the card shows, not what the page is called",
|
|
606
|
+
"snippet": "// The card is a title on a background. The alt says so — once, from a\n// template, so every page gets one and none of them is the title alone.\nconst ogAlt = (title: string) => `The title “${title}” on a dark preview card.`;",
|
|
607
|
+
"language": "ts"
|
|
608
|
+
}
|
|
609
|
+
},
|
|
566
610
|
{
|
|
567
611
|
"id": "og.image.dimensions",
|
|
568
612
|
"scope": "page",
|
|
@@ -695,6 +739,35 @@
|
|
|
695
739
|
],
|
|
696
740
|
"prose": "Does the og:image represent what this page is about, rather than being a site-wide default, and does its subject survive being cropped to the 1.91:1 aspect ratio consumers render?"
|
|
697
741
|
},
|
|
742
|
+
{
|
|
743
|
+
"id": "og.image.sizes-mismatch",
|
|
744
|
+
"scope": "page",
|
|
745
|
+
"category": "opengraph",
|
|
746
|
+
"severity": "warning",
|
|
747
|
+
"kind": "boolean",
|
|
748
|
+
"summary": "`og:image:width` and `og:image:height` must be the image's real size",
|
|
749
|
+
"why": "The declared size is what a crawler lays the card out with before it has the file, which is the whole reason `og.image.dimensions` asks for it. A wrong one is therefore worse than none: the crawler reserves a shape the image does not have, and the card is letterboxed or cropped on the first share — the one that matters most.\n\nIt is also the declaration that blinds every other check. `og.image.ratio` reads these two numbers and refuses to fetch, so an invented 1200×630 scores 1.9 and passes over an image that is square. Found on a site whose cover art is 1024×1024 and one of whose covers is a 337-byte 1×1 placeholder, all three declared 1200×630 by a library that had never looked at them.",
|
|
750
|
+
"rigor": "guideline",
|
|
751
|
+
"sources": [
|
|
752
|
+
"ogp",
|
|
753
|
+
"meta-og-sharing"
|
|
754
|
+
],
|
|
755
|
+
"reads": [
|
|
756
|
+
"openGraph.images",
|
|
757
|
+
"assets"
|
|
758
|
+
],
|
|
759
|
+
"relates": [
|
|
760
|
+
"og.image.dimensions",
|
|
761
|
+
"og.image.ratio",
|
|
762
|
+
"og.image.reachable"
|
|
763
|
+
],
|
|
764
|
+
"expected": "declared dimensions equal to the file's own",
|
|
765
|
+
"fix": {
|
|
766
|
+
"title": "Measure the image, or let the file convention do it",
|
|
767
|
+
"snippet": "// app/…/opengraph-image.tsx — `size` is the truth and the declaration at once\nexport const size = { width: 1200, height: 630 };\n\n// Naming a file instead? Then the numbers are that file's, not a default.\nopenGraph: { images: [{ url: cover.url, width: cover.width, height: cover.height }] }",
|
|
768
|
+
"language": "tsx"
|
|
769
|
+
}
|
|
770
|
+
},
|
|
698
771
|
{
|
|
699
772
|
"id": "og.locale.alternates",
|
|
700
773
|
"scope": "page",
|
|
@@ -952,6 +1025,17 @@
|
|
|
952
1025
|
"google-canonicalization"
|
|
953
1026
|
]
|
|
954
1027
|
},
|
|
1028
|
+
{
|
|
1029
|
+
"id": "sitemap.entry.out-of-scope",
|
|
1030
|
+
"scope": "site",
|
|
1031
|
+
"severity": "error",
|
|
1032
|
+
"kind": "boolean",
|
|
1033
|
+
"summary": "A sitemap may only list URLs under its own directory",
|
|
1034
|
+
"rigor": "spec-required",
|
|
1035
|
+
"sources": [
|
|
1036
|
+
"sitemaps-protocol"
|
|
1037
|
+
]
|
|
1038
|
+
},
|
|
955
1039
|
{
|
|
956
1040
|
"id": "sitemap.entry.protocol-mismatch",
|
|
957
1041
|
"scope": "site",
|
|
@@ -1022,6 +1106,17 @@
|
|
|
1022
1106
|
"google-sitemaps"
|
|
1023
1107
|
]
|
|
1024
1108
|
},
|
|
1109
|
+
{
|
|
1110
|
+
"id": "sitemap.limits.exceeded",
|
|
1111
|
+
"scope": "site",
|
|
1112
|
+
"severity": "error",
|
|
1113
|
+
"kind": "boolean",
|
|
1114
|
+
"summary": "No single sitemap document may exceed 50,000 entries or 50 MB",
|
|
1115
|
+
"rigor": "spec-required",
|
|
1116
|
+
"sources": [
|
|
1117
|
+
"sitemaps-protocol"
|
|
1118
|
+
]
|
|
1119
|
+
},
|
|
1025
1120
|
{
|
|
1026
1121
|
"id": "sitemap.missing",
|
|
1027
1122
|
"scope": "site",
|