@riebeckite/plugin-diagnostics 0.0.14 → 0.0.16
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 +8 -4
- package/README_ja.md +9 -8
- package/dist/bin.js +37 -16
- package/dist/index.js +97 -27
- package/dist/src/types.d.ts +1 -0
- package/dist/src/vault.d.ts +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -44,9 +44,9 @@ export default defineConfig({
|
|
|
44
44
|
| `broken-link` | `error` | Markdown link points to a missing or excluded note/file |
|
|
45
45
|
| `unused-asset` | `warning` | Image never referenced by any note (`reportUnusedAssets`) |
|
|
46
46
|
| `orphan-note` | `info` | Published note has no incoming links (`reportOrphans`) |
|
|
47
|
-
| `missing-frontmatter` | `warning` |
|
|
47
|
+
| `missing-frontmatter` | `warning` | A required frontmatter field is missing |
|
|
48
48
|
| `publish-conflict` | `warning` | `publish: true` combined with `draft: true` / `private: true` |
|
|
49
|
-
| `duplicate-title` | `warning` | Multiple published notes share a title |
|
|
49
|
+
| `duplicate-title` | `warning` | Multiple published notes share a title within the same language |
|
|
50
50
|
| `slug-collision` | `error` | Slugs collide case-insensitively |
|
|
51
51
|
| `duplicate-content-id` | `error` | Published notes share a stable content ID (`id`/`uid`) |
|
|
52
52
|
| `invalid-content-id` | `error` | `id`/`uid` frontmatter violates the stable content ID contract |
|
|
@@ -80,7 +80,10 @@ the existing plugin output/asset contracts so integrity checks can treat them as
|
|
|
80
80
|
valid public targets.
|
|
81
81
|
|
|
82
82
|
`runDiagnostics()` and the standalone CLI still use the filesystem/content-source
|
|
83
|
-
analyzer because no full manifest is available in that mode.
|
|
83
|
+
analyzer because no full manifest is available in that mode. As a result,
|
|
84
|
+
`orphan-note` and `unused-asset` are reported only by `runDiagnostics()` and the
|
|
85
|
+
standalone CLI; during a build they are omitted because link relationships come
|
|
86
|
+
from the manifest.
|
|
84
87
|
|
|
85
88
|
## Options
|
|
86
89
|
|
|
@@ -156,4 +159,5 @@ assertNoErrors(report);
|
|
|
156
159
|
|
|
157
160
|
## See also
|
|
158
161
|
|
|
159
|
-
- [Plugin guide](../../../docs/en/reference/plugin-api.md)
|
|
162
|
+
- [Plugin guide](../../../docs/en/docs/reference/plugin-api.md)
|
|
163
|
+
|
package/README_ja.md
CHANGED
|
@@ -28,22 +28,22 @@ export default defineConfig({
|
|
|
28
28
|
|
|
29
29
|
| コード | 既定の重要度 | 内容 |
|
|
30
30
|
| --- | --- | --- |
|
|
31
|
-
| `broken-wikilink` | error | Wikiリンクの参照先またはフラグメントを解決できない |
|
|
31
|
+
| `broken-wikilink` | error | Wiki リンクの参照先またはフラグメントを解決できない |
|
|
32
32
|
| `broken-image` | error | 埋め込み画像または画像リンクの参照先がない |
|
|
33
33
|
| `broken-link` | error | Markdown リンクが存在しない、または除外されたノートを指す |
|
|
34
34
|
| `unused-asset` | warning | どのノートからも参照されない画像 |
|
|
35
35
|
| `orphan-note` | info | 公開ノートのうち、他のノートからリンクされていないもの |
|
|
36
|
-
| `missing-frontmatter` | warning |
|
|
36
|
+
| `missing-frontmatter` | warning | 必須フィールドが欠けている |
|
|
37
37
|
| `publish-conflict` | warning | `publish: true` と `draft: true` または `private: true` が同居している |
|
|
38
|
-
| `duplicate-title` | warning |
|
|
38
|
+
| `duplicate-title` | warning | 同じ言語の公開ノート同士でタイトルが重複している |
|
|
39
39
|
| `slug-collision` | error | 大文字・小文字を区別しない slug が衝突している |
|
|
40
40
|
| `duplicate-content-id` | error | 公開ノート同士で安定コンテンツ ID(`id`/`uid`)が重複している |
|
|
41
|
-
| `invalid-content-id` | error | `id`/`uid` の frontmatter が安定コンテンツ ID
|
|
41
|
+
| `invalid-content-id` | error | `id`/`uid` の frontmatter が安定コンテンツ ID の要件を満たしていない |
|
|
42
42
|
| `excluded-public` | warning | 除外されたノートに `publish: true` が指定されている |
|
|
43
43
|
| `publish-boundary` | warning | 公開コンテンツから非公開コンテンツへリンク・埋め込みしている |
|
|
44
44
|
| `analytics-untracked` | info | 安定 `id` のない公開ノートは analytics プラグインで計測されない(`reportAnalyticsCoverage` で有効化。設定で analytics プラグインが有効な場合は自動で有効化) |
|
|
45
45
|
| `content-integrity:broken-link` | warning | 公開ページが、公開または登録されていないサイト内経路へリンクしている |
|
|
46
|
-
| `content-integrity:unresolved-wikilink` | warning | 公開ページ内の Wikiリンクを、設定済みの解決規則で解決できない |
|
|
46
|
+
| `content-integrity:unresolved-wikilink` | warning | 公開ページ内の Wiki リンクを、設定済みの解決規則で解決できない |
|
|
47
47
|
| `content-integrity:broken-asset` | warning | 公開ページが、解決済みアセット・プラグインアセット・生成出力・公開経路のいずれにもないローカルアセットを参照している |
|
|
48
48
|
| `content-integrity:duplicate-public-location` | error | 複数の公開コンテンツまたは生成経路が、同じ最終公開パスを所有している |
|
|
49
49
|
| `content-integrity:redirect-target-missing` | warning | 公開リダイレクトの転送先コンテンツが公開されない |
|
|
@@ -57,9 +57,9 @@ export default defineConfig({
|
|
|
57
57
|
|
|
58
58
|
Riebeckite のプラグインとして使う場合、リンク関係の検査はコンテンツを再走査せず、解決済み manifest から行います。`publicEntries`、解決済み `ContentLink`、`ContentPublicLocation`、`publicRedirects`、プラグインページのパス、プラグインアセット、生成出力を参照します。これにより permalink、alias、rename によるリダイレクト、l10n、公開・除外設定、Page System の扱いがビルド処理と揃います。
|
|
59
59
|
|
|
60
|
-
外部 URL の疎通確認、SEO、スペルチェック、Lighthouse、自動修復は行いません。`http:`、`https:`、`mailto:`、`tel:`、`data:`、プロトコル相対 URL、ページ内フラグメントだけのリンクは対象外です。経路の存在確認では、クエリ文字列とフラグメントを除いて判定します。生成ページや生成アセットを提供するプラグインは、Page Type
|
|
60
|
+
外部 URL の疎通確認、SEO、スペルチェック、Lighthouse、自動修復は行いません。`http:`、`https:`、`mailto:`、`tel:`、`data:`、プロトコル相対 URL、ページ内フラグメントだけのリンクは対象外です。経路の存在確認では、クエリ文字列とフラグメントを除いて判定します。生成ページや生成アセットを提供するプラグインは、Page Type、生成出力、アセット登録の既存の仕組みを使ってください。登録されていれば、整合性検査でも有効な公開先として扱われます。
|
|
61
61
|
|
|
62
|
-
`runDiagnostics()` と単体 CLI は、完全な manifest を持たないため、従来の content source
|
|
62
|
+
`runDiagnostics()` と単体 CLI は、完全な manifest を持たないため、従来の content source 解析を使います。そのため `orphan-note` と `unused-asset` は `runDiagnostics()` と単体 CLI でのみ報告されます。ビルド時はリンク関係を manifest から解決するため、これらは出力しません。
|
|
63
63
|
|
|
64
64
|
## 設定項目
|
|
65
65
|
|
|
@@ -115,4 +115,5 @@ assertNoErrors(report);
|
|
|
115
115
|
|
|
116
116
|
## 関連資料
|
|
117
117
|
|
|
118
|
-
- [プラグインシステム](../../../docs/ja/reference/plugin-api.md)
|
|
118
|
+
- [プラグインシステム](../../../docs/ja/docs/reference/plugin-api.md)
|
|
119
|
+
|
package/dist/bin.js
CHANGED
|
@@ -258,6 +258,7 @@ async function scanVault(config) {
|
|
|
258
258
|
const note = {
|
|
259
259
|
relativePath: relative,
|
|
260
260
|
slug,
|
|
261
|
+
language: resolveNoteLanguage(relative, config.languages),
|
|
261
262
|
markdown,
|
|
262
263
|
fm,
|
|
263
264
|
headings: extractHeadings(markdown),
|
|
@@ -416,6 +417,12 @@ function fragmentExists(note, fragment) {
|
|
|
416
417
|
function normalizeMarkdown(markdown) {
|
|
417
418
|
return markdown.replace(/\r\n/g, "\n");
|
|
418
419
|
}
|
|
420
|
+
function resolveNoteLanguage(relativePath, languages) {
|
|
421
|
+
if (!languages || languages.length === 0) return void 0;
|
|
422
|
+
const segment = relativePath.split("/")[0]?.toLowerCase();
|
|
423
|
+
if (!segment) return void 0;
|
|
424
|
+
return languages.find((language) => language.toLowerCase() === segment);
|
|
425
|
+
}
|
|
419
426
|
function getExtension(filePath) {
|
|
420
427
|
const lastSegment = filePath.split("/").pop() ?? filePath;
|
|
421
428
|
const dotIndex = lastSegment.lastIndexOf(".");
|
|
@@ -444,17 +451,7 @@ function normalizeRelativePath(parts) {
|
|
|
444
451
|
function checkFrontmatter(note, source, state, options, diagnostics) {
|
|
445
452
|
const location = { slug: note.slug, filePath: note.relativePath };
|
|
446
453
|
const values = note.fm.values;
|
|
447
|
-
if (
|
|
448
|
-
pushDiagnostic(
|
|
449
|
-
diagnostics,
|
|
450
|
-
options,
|
|
451
|
-
location,
|
|
452
|
-
"missing-frontmatter",
|
|
453
|
-
"note has no frontmatter",
|
|
454
|
-
note.slug,
|
|
455
|
-
"add frontmatter with the required fields"
|
|
456
|
-
);
|
|
457
|
-
} else {
|
|
454
|
+
if (note.fm.hasFrontmatter) {
|
|
458
455
|
for (const field of options.requiredFrontmatter) {
|
|
459
456
|
if (!hasField(values, field))
|
|
460
457
|
pushDiagnostic(
|
|
@@ -467,6 +464,16 @@ function checkFrontmatter(note, source, state, options, diagnostics) {
|
|
|
467
464
|
`add "${field}" to the frontmatter`
|
|
468
465
|
);
|
|
469
466
|
}
|
|
467
|
+
} else if (options.requiredFrontmatter.length > 0) {
|
|
468
|
+
pushDiagnostic(
|
|
469
|
+
diagnostics,
|
|
470
|
+
options,
|
|
471
|
+
location,
|
|
472
|
+
"missing-frontmatter",
|
|
473
|
+
"note has no frontmatter",
|
|
474
|
+
note.slug,
|
|
475
|
+
"add frontmatter with the required fields"
|
|
476
|
+
);
|
|
470
477
|
}
|
|
471
478
|
if (values.publish === true && values.draft === true)
|
|
472
479
|
pushDiagnostic(
|
|
@@ -629,10 +636,11 @@ function checkSlugCollisions(source, options, diagnostics) {
|
|
|
629
636
|
function checkDuplicateTitles(source, options, diagnostics) {
|
|
630
637
|
const byTitle = groupNotes(
|
|
631
638
|
source.includedNotes.filter((note) => note.published),
|
|
632
|
-
(note) => getNoteTitle(note.fm.values, note.slug).toLowerCase()
|
|
639
|
+
(note) => `${note.language ?? ""}\0${getNoteTitle(note.fm.values, note.slug).toLowerCase()}`
|
|
633
640
|
);
|
|
634
|
-
for (const [
|
|
641
|
+
for (const [groupKey, notes] of byTitle) {
|
|
635
642
|
if (notes.length < 2) continue;
|
|
643
|
+
const title = groupKey.slice(groupKey.indexOf("\0") + 1);
|
|
636
644
|
const slugs = notes.map((note) => note.slug).join(", ");
|
|
637
645
|
for (const note of notes)
|
|
638
646
|
pushDiagnostic(
|
|
@@ -833,8 +841,10 @@ async function analyzeContent(config, options = {}) {
|
|
|
833
841
|
checkSlugCollisions(source, normalizedOptions, diagnostics);
|
|
834
842
|
checkDuplicateTitles(source, normalizedOptions, diagnostics);
|
|
835
843
|
checkContentIdIntegrity(source, normalizedOptions, diagnostics);
|
|
836
|
-
|
|
837
|
-
|
|
844
|
+
if (!options.skipReferenceIntegrity) {
|
|
845
|
+
checkOrphans(source, state, normalizedOptions, diagnostics);
|
|
846
|
+
checkUnusedAssets(source, state, normalizedOptions, diagnostics);
|
|
847
|
+
}
|
|
838
848
|
checkExcludedPublic(source, normalizedOptions, diagnostics);
|
|
839
849
|
if (!options.skipReferenceIntegrity)
|
|
840
850
|
checkPublishBoundary(source, normalizedOptions, diagnostics);
|
|
@@ -978,6 +988,7 @@ async function runDiagnostics(target, options = {}) {
|
|
|
978
988
|
}
|
|
979
989
|
}
|
|
980
990
|
var ANALYTICS_PLUGIN_NAME = "analytics";
|
|
991
|
+
var L10N_PLUGIN_NAME = "l10n";
|
|
981
992
|
function hasEnabledAnalyticsPlugin(config) {
|
|
982
993
|
return config?.plugins.some(
|
|
983
994
|
(plugin) => plugin.name === ANALYTICS_PLUGIN_NAME && plugin.enabled !== false
|
|
@@ -1007,9 +1018,19 @@ function toAnalyzerConfig(target, options) {
|
|
|
1007
1018
|
target.content.exclude
|
|
1008
1019
|
),
|
|
1009
1020
|
exclude: [...target.content.exclude, ...options.exclude ?? []],
|
|
1010
|
-
publishStrategy: target.content.filters.publishStrategy
|
|
1021
|
+
publishStrategy: target.content.filters.publishStrategy,
|
|
1022
|
+
languages: resolveLanguages(target)
|
|
1011
1023
|
};
|
|
1012
1024
|
}
|
|
1025
|
+
function resolveLanguages(config) {
|
|
1026
|
+
const plugin = config.plugins.find(
|
|
1027
|
+
(entry) => entry.name === L10N_PLUGIN_NAME && entry.enabled !== false
|
|
1028
|
+
);
|
|
1029
|
+
const languages = plugin?.options?.languages;
|
|
1030
|
+
return Array.isArray(languages) ? languages.filter(
|
|
1031
|
+
(language) => typeof language === "string"
|
|
1032
|
+
) : [];
|
|
1033
|
+
}
|
|
1013
1034
|
|
|
1014
1035
|
// bin.ts
|
|
1015
1036
|
var HELP = `riebeckite-diagnostics
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,7 @@ async function scanVault(config) {
|
|
|
254
254
|
const note = {
|
|
255
255
|
relativePath: relative,
|
|
256
256
|
slug,
|
|
257
|
+
language: resolveNoteLanguage(relative, config.languages),
|
|
257
258
|
markdown,
|
|
258
259
|
fm,
|
|
259
260
|
headings: extractHeadings(markdown),
|
|
@@ -412,6 +413,12 @@ function fragmentExists(note, fragment) {
|
|
|
412
413
|
function normalizeMarkdown(markdown) {
|
|
413
414
|
return markdown.replace(/\r\n/g, "\n");
|
|
414
415
|
}
|
|
416
|
+
function resolveNoteLanguage(relativePath, languages) {
|
|
417
|
+
if (!languages || languages.length === 0) return void 0;
|
|
418
|
+
const segment = relativePath.split("/")[0]?.toLowerCase();
|
|
419
|
+
if (!segment) return void 0;
|
|
420
|
+
return languages.find((language) => language.toLowerCase() === segment);
|
|
421
|
+
}
|
|
415
422
|
function getExtension(filePath) {
|
|
416
423
|
const lastSegment = filePath.split("/").pop() ?? filePath;
|
|
417
424
|
const dotIndex = lastSegment.lastIndexOf(".");
|
|
@@ -440,17 +447,7 @@ function normalizeRelativePath(parts) {
|
|
|
440
447
|
function checkFrontmatter(note, source, state, options, diagnostics2) {
|
|
441
448
|
const location = { slug: note.slug, filePath: note.relativePath };
|
|
442
449
|
const values = note.fm.values;
|
|
443
|
-
if (
|
|
444
|
-
pushDiagnostic(
|
|
445
|
-
diagnostics2,
|
|
446
|
-
options,
|
|
447
|
-
location,
|
|
448
|
-
"missing-frontmatter",
|
|
449
|
-
"note has no frontmatter",
|
|
450
|
-
note.slug,
|
|
451
|
-
"add frontmatter with the required fields"
|
|
452
|
-
);
|
|
453
|
-
} else {
|
|
450
|
+
if (note.fm.hasFrontmatter) {
|
|
454
451
|
for (const field of options.requiredFrontmatter) {
|
|
455
452
|
if (!hasField(values, field))
|
|
456
453
|
pushDiagnostic(
|
|
@@ -463,6 +460,16 @@ function checkFrontmatter(note, source, state, options, diagnostics2) {
|
|
|
463
460
|
`add "${field}" to the frontmatter`
|
|
464
461
|
);
|
|
465
462
|
}
|
|
463
|
+
} else if (options.requiredFrontmatter.length > 0) {
|
|
464
|
+
pushDiagnostic(
|
|
465
|
+
diagnostics2,
|
|
466
|
+
options,
|
|
467
|
+
location,
|
|
468
|
+
"missing-frontmatter",
|
|
469
|
+
"note has no frontmatter",
|
|
470
|
+
note.slug,
|
|
471
|
+
"add frontmatter with the required fields"
|
|
472
|
+
);
|
|
466
473
|
}
|
|
467
474
|
if (values.publish === true && values.draft === true)
|
|
468
475
|
pushDiagnostic(
|
|
@@ -625,10 +632,11 @@ function checkSlugCollisions(source, options, diagnostics2) {
|
|
|
625
632
|
function checkDuplicateTitles(source, options, diagnostics2) {
|
|
626
633
|
const byTitle = groupNotes(
|
|
627
634
|
source.includedNotes.filter((note) => note.published),
|
|
628
|
-
(note) => getNoteTitle(note.fm.values, note.slug).toLowerCase()
|
|
635
|
+
(note) => `${note.language ?? ""}\0${getNoteTitle(note.fm.values, note.slug).toLowerCase()}`
|
|
629
636
|
);
|
|
630
|
-
for (const [
|
|
637
|
+
for (const [groupKey, notes] of byTitle) {
|
|
631
638
|
if (notes.length < 2) continue;
|
|
639
|
+
const title = groupKey.slice(groupKey.indexOf("\0") + 1);
|
|
632
640
|
const slugs = notes.map((note) => note.slug).join(", ");
|
|
633
641
|
for (const note of notes)
|
|
634
642
|
pushDiagnostic(
|
|
@@ -829,8 +837,10 @@ async function analyzeContent(config, options = {}) {
|
|
|
829
837
|
checkSlugCollisions(source, normalizedOptions, diagnostics2);
|
|
830
838
|
checkDuplicateTitles(source, normalizedOptions, diagnostics2);
|
|
831
839
|
checkContentIdIntegrity(source, normalizedOptions, diagnostics2);
|
|
832
|
-
|
|
833
|
-
|
|
840
|
+
if (!options.skipReferenceIntegrity) {
|
|
841
|
+
checkOrphans(source, state, normalizedOptions, diagnostics2);
|
|
842
|
+
checkUnusedAssets(source, state, normalizedOptions, diagnostics2);
|
|
843
|
+
}
|
|
834
844
|
checkExcludedPublic(source, normalizedOptions, diagnostics2);
|
|
835
845
|
if (!options.skipReferenceIntegrity)
|
|
836
846
|
checkPublishBoundary(source, normalizedOptions, diagnostics2);
|
|
@@ -985,7 +995,8 @@ var DEFAULT_SEVERITY2 = {
|
|
|
985
995
|
"content-integrity:duplicate-public-location": "error",
|
|
986
996
|
"content-integrity:redirect-target-missing": "warning",
|
|
987
997
|
"content-integrity:redirect-cycle": "error",
|
|
988
|
-
"content-integrity:redirect-public-location-conflict": "error"
|
|
998
|
+
"content-integrity:redirect-public-location-conflict": "error",
|
|
999
|
+
"content-integrity:ambiguous-folder-page-owner": "warning"
|
|
989
1000
|
};
|
|
990
1001
|
function checkSiteIntegrity(manifest, options = {}) {
|
|
991
1002
|
const diagnostics2 = [];
|
|
@@ -999,8 +1010,9 @@ function checkSiteIntegrity(manifest, options = {}) {
|
|
|
999
1010
|
checkResolvedLinks(entry, publicSlugs, assetPaths, diagnostics2, options);
|
|
1000
1011
|
checkHtmlReferences(entry, publicRoutes, assetPaths, diagnostics2, options);
|
|
1001
1012
|
}
|
|
1002
|
-
checkRedirectTargets(manifest,
|
|
1013
|
+
checkRedirectTargets(manifest, diagnostics2, options);
|
|
1003
1014
|
checkRedirectCycles(manifest, diagnostics2, options);
|
|
1015
|
+
checkAmbiguousFolderPageOwners(manifest, diagnostics2, options);
|
|
1004
1016
|
return diagnostics2;
|
|
1005
1017
|
}
|
|
1006
1018
|
function buildRouteOwners(manifest, diagnostics2, options) {
|
|
@@ -1019,14 +1031,24 @@ function buildRouteOwners(manifest, diagnostics2, options) {
|
|
|
1019
1031
|
kind: "redirect"
|
|
1020
1032
|
});
|
|
1021
1033
|
}
|
|
1022
|
-
const
|
|
1023
|
-
for (const
|
|
1034
|
+
const pageRoutes = manifest.pageRoutes ?? [];
|
|
1035
|
+
for (const route of pageRoutes) {
|
|
1024
1036
|
reserveRoute(owners, diagnostics2, options, {
|
|
1025
|
-
path: normalizeRoutePath(
|
|
1026
|
-
slug:
|
|
1027
|
-
kind: "page"
|
|
1037
|
+
path: normalizeRoutePath(route.pathname),
|
|
1038
|
+
slug: `(plugin-page:${route.pluginName}:${route.pageType})`,
|
|
1039
|
+
kind: "page",
|
|
1040
|
+
producer: `${route.pluginName}:${route.pageType}`
|
|
1028
1041
|
});
|
|
1029
1042
|
}
|
|
1043
|
+
if (pageRoutes.length === 0) {
|
|
1044
|
+
for (const path of manifest.pagePaths ?? []) {
|
|
1045
|
+
reserveRoute(owners, diagnostics2, options, {
|
|
1046
|
+
path: normalizeRoutePath(path),
|
|
1047
|
+
slug: "(plugin-page)",
|
|
1048
|
+
kind: "page"
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1030
1052
|
for (const output of manifest.generatedOutputs ?? []) {
|
|
1031
1053
|
reserveRoute(owners, diagnostics2, options, {
|
|
1032
1054
|
path: normalizeRoutePath(output.path),
|
|
@@ -1139,11 +1161,12 @@ function checkHtmlReferences(entry, publicRoutes, assetPaths, diagnostics2, opti
|
|
|
1139
1161
|
);
|
|
1140
1162
|
}
|
|
1141
1163
|
}
|
|
1142
|
-
function checkRedirectTargets(manifest,
|
|
1143
|
-
for (const [path, redirect] of manifest.
|
|
1164
|
+
function checkRedirectTargets(manifest, diagnostics2, options) {
|
|
1165
|
+
for (const [path, redirect] of manifest.redirects) {
|
|
1144
1166
|
const owner = manifest.bySlug.get(redirect.slug);
|
|
1145
1167
|
const target = owner ? normalizeRoutePath(owner.publicLocation.permalink) : null;
|
|
1146
|
-
if (target &&
|
|
1168
|
+
if (target && manifest.byRoutablePermalink.get(target)?.slug === redirect.slug)
|
|
1169
|
+
continue;
|
|
1147
1170
|
diagnostics2.push(
|
|
1148
1171
|
diagnostic(options, {
|
|
1149
1172
|
code: "content-integrity:redirect-target-missing",
|
|
@@ -1253,7 +1276,43 @@ function normalizeRoutePath(value) {
|
|
|
1253
1276
|
return path === "/" ? path : path.replace(/\/$/, "");
|
|
1254
1277
|
}
|
|
1255
1278
|
function describeOwner(owner) {
|
|
1256
|
-
return `${owner.kind} "${owner.slug}"`;
|
|
1279
|
+
return `${owner.kind} "${owner.producer ?? owner.slug}"`;
|
|
1280
|
+
}
|
|
1281
|
+
function checkAmbiguousFolderPageOwners(manifest, diagnostics2, options) {
|
|
1282
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1283
|
+
for (const entry of manifest.entries) {
|
|
1284
|
+
const basename = entry.slug.split("/").at(-1);
|
|
1285
|
+
if (basename !== "README" && basename !== "index") continue;
|
|
1286
|
+
const folder = entry.slug.split("/").slice(0, -1).join("/");
|
|
1287
|
+
if (!folder) continue;
|
|
1288
|
+
const folderCandidates = candidates.get(folder) ?? {};
|
|
1289
|
+
folderCandidates[basename] = entry;
|
|
1290
|
+
candidates.set(folder, folderCandidates);
|
|
1291
|
+
}
|
|
1292
|
+
for (const [folder, entries] of candidates) {
|
|
1293
|
+
const readme = entries.README;
|
|
1294
|
+
const index = entries.index;
|
|
1295
|
+
if (!readme || !index) continue;
|
|
1296
|
+
diagnostics2.push(
|
|
1297
|
+
diagnostic(options, {
|
|
1298
|
+
code: "content-integrity:ambiguous-folder-page-owner",
|
|
1299
|
+
target: folder,
|
|
1300
|
+
message: `Folder "${folder}" has both README and index Folder Page candidates.`,
|
|
1301
|
+
suggestion: "Keep either README or index so the Folder Page owner is unambiguous.",
|
|
1302
|
+
meta: {
|
|
1303
|
+
folder,
|
|
1304
|
+
readme: {
|
|
1305
|
+
slug: readme.slug,
|
|
1306
|
+
permalink: readme.publicLocation.permalink
|
|
1307
|
+
},
|
|
1308
|
+
index: {
|
|
1309
|
+
slug: index.slug,
|
|
1310
|
+
permalink: index.publicLocation.permalink
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
})
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1257
1316
|
}
|
|
1258
1317
|
function canonicalCycleKey(cycle) {
|
|
1259
1318
|
const closed = cycle.at(0) === cycle.at(-1) ? cycle.slice(0, -1) : [...cycle];
|
|
@@ -1314,6 +1373,7 @@ function diagnostics(options = {}) {
|
|
|
1314
1373
|
}
|
|
1315
1374
|
var diagnosticsPlugin = diagnostics;
|
|
1316
1375
|
var ANALYTICS_PLUGIN_NAME = "analytics";
|
|
1376
|
+
var L10N_PLUGIN_NAME = "l10n";
|
|
1317
1377
|
function hasEnabledAnalyticsPlugin(config) {
|
|
1318
1378
|
return config?.plugins.some(
|
|
1319
1379
|
(plugin) => plugin.name === ANALYTICS_PLUGIN_NAME && plugin.enabled !== false
|
|
@@ -1349,9 +1409,19 @@ function toAnalyzerConfig(target, options) {
|
|
|
1349
1409
|
target.content.exclude
|
|
1350
1410
|
),
|
|
1351
1411
|
exclude: [...target.content.exclude, ...options.exclude ?? []],
|
|
1352
|
-
publishStrategy: target.content.filters.publishStrategy
|
|
1412
|
+
publishStrategy: target.content.filters.publishStrategy,
|
|
1413
|
+
languages: resolveLanguages(target)
|
|
1353
1414
|
};
|
|
1354
1415
|
}
|
|
1416
|
+
function resolveLanguages(config) {
|
|
1417
|
+
const plugin = config.plugins.find(
|
|
1418
|
+
(entry) => entry.name === L10N_PLUGIN_NAME && entry.enabled !== false
|
|
1419
|
+
);
|
|
1420
|
+
const languages = plugin?.options?.languages;
|
|
1421
|
+
return Array.isArray(languages) ? languages.filter(
|
|
1422
|
+
(language) => typeof language === "string"
|
|
1423
|
+
) : [];
|
|
1424
|
+
}
|
|
1355
1425
|
export {
|
|
1356
1426
|
DiagnosticsFailure,
|
|
1357
1427
|
analyzeContent,
|
package/dist/src/types.d.ts
CHANGED
package/dist/src/vault.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riebeckite/plugin-diagnostics",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"remark-parse": "^11.0.0",
|
|
21
21
|
"unified": "^11.0.5",
|
|
22
22
|
"unist-util-visit": "^5.1.0",
|
|
23
|
-
"@riebeckite/core": "0.0.
|
|
23
|
+
"@riebeckite/core": "0.0.16"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/mdast": "^4.0.4",
|