emdash-plugin-relink 0.2.2
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/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +21 -0
- package/LICENSE +167 -0
- package/LICENSES/MIT-legacy.txt +21 -0
- package/README.md +95 -0
- package/THIRD_PARTY_NOTICES.md +19 -0
- package/dist/admin/app.d.ts +9 -0
- package/dist/admin/app.d.ts.map +1 -0
- package/dist/admin/app.js +333 -0
- package/dist/admin/app.js.map +1 -0
- package/dist/admin/client.d.ts +9 -0
- package/dist/admin/client.d.ts.map +1 -0
- package/dist/admin/client.js +22 -0
- package/dist/admin/client.js.map +1 -0
- package/dist/admin/i18n.d.ts +8 -0
- package/dist/admin/i18n.d.ts.map +1 -0
- package/dist/admin/i18n.js +35 -0
- package/dist/admin/i18n.js.map +1 -0
- package/dist/admin/index.d.ts +18 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +22 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/messages.d.ts +270 -0
- package/dist/admin/messages.d.ts.map +1 -0
- package/dist/admin/messages.js +269 -0
- package/dist/admin/messages.js.map +1 -0
- package/dist/admin/styles.css +330 -0
- package/dist/core/api.d.ts +559 -0
- package/dist/core/api.d.ts.map +1 -0
- package/dist/core/api.js +76 -0
- package/dist/core/api.js.map +1 -0
- package/dist/core/engine.d.ts +30 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +392 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/extract.d.ts +11 -0
- package/dist/core/extract.d.ts.map +1 -0
- package/dist/core/extract.js +99 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/html.d.ts +12 -0
- package/dist/core/html.d.ts.map +1 -0
- package/dist/core/html.js +103 -0
- package/dist/core/html.js.map +1 -0
- package/dist/core/http.d.ts +22 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +217 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/index.d.ts +10 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +10 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/paths.d.ts +4 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +38 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/providers.d.ts +32 -0
- package/dist/core/providers.d.ts.map +1 -0
- package/dist/core/providers.js +198 -0
- package/dist/core/providers.js.map +1 -0
- package/dist/core/schema.d.ts +526 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +238 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/storage.d.ts +47 -0
- package/dist/core/storage.d.ts.map +1 -0
- package/dist/core/storage.js +89 -0
- package/dist/core/storage.js.map +1 -0
- package/dist/core/url.d.ts +7 -0
- package/dist/core/url.d.ts.map +1 -0
- package/dist/core/url.js +62 -0
- package/dist/core/url.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +5 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +29 -0
- package/dist/integration.js.map +1 -0
- package/dist/metadata.d.ts +14 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +6 -0
- package/dist/metadata.js.map +1 -0
- package/dist/middleware.d.ts +8 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +97 -0
- package/dist/middleware.js.map +1 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +260 -0
- package/dist/plugin.js.map +1 -0
- package/docs/architecture.md +50 -0
- package/docs/configuration.md +37 -0
- package/docs/installation.md +71 -0
- package/docs/licensing.md +42 -0
- package/docs/recovery.md +27 -0
- package/docs/testing.md +31 -0
- package/docs/translations.md +24 -0
- package/package.json +124 -0
package/docs/recovery.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Link evidence, archive recovery and exceptions
|
|
2
|
+
|
|
3
|
+
## Checking originals
|
|
4
|
+
|
|
5
|
+
Relink tries HEAD first and confirms unsuccessful or HTML responses with GET. Redirects are bounded to five and ten-second request timeouts apply. Inspected bodies are limited to 512 KB. A successful HEAD cannot override a failing GET. Sign-in pages, bot challenges and suspicious soft-error titles are marked unverifiable.
|
|
6
|
+
|
|
7
|
+
404/410 and repeated server or transport failures can contribute to a broken-link decision. 401/407, 403/451 and 429 remain distinct authentication, blocked and rate-limited results. `Retry-After` is honoured with bounds. For transport/5xx failures, independent network probes must answer successfully enough to establish connectivity before a failure counts. If both probes fail, Relink records a local outage, retries in an hour and does not advance archive eligibility.
|
|
8
|
+
|
|
9
|
+
Failures count at most once per 24 hours, including manual retries. The default schedule is first check, +24 hours, +72 hours from initial failure. The third confirmed failure permits archive discovery. Healthy originals reset the failure series. Originals behind an active archive return after two successful checks at least 24 hours apart; an intervening confirmed broken check resets recovery evidence.
|
|
10
|
+
|
|
11
|
+
## Wayback
|
|
12
|
+
|
|
13
|
+
Relink queries the Internet Archive CDX endpoint for up to three of the latest exact-URL captures before the failure period. It requires a successful recorded status, a supported document MIME type, an accessible playback URL for that exact timestamp/resource, and all referenced section anchors. Calendar pages, errors, mismatching resources and missing sections are unsuitable. It records the capture date, original URL, verified fragments, playback evidence, decision reason and history.
|
|
14
|
+
|
|
15
|
+
Discovery never submits pages to Save Page Now. Missing captures leave the original in place and schedule discovery in 30 days. Archive API/playback outages are retryable service failures and are retried in one day. An archive that subsequently gives a confirmed broken response is removed and flagged; authentication or bot-blocking alone does not prove a previously verified archive is gone.
|
|
16
|
+
|
|
17
|
+
## Grokipedia
|
|
18
|
+
|
|
19
|
+
Disabled by default. The initial provider considers canonical Wikipedia `/wiki/` article URLs. Query-bearing/historical revisions and special namespaces are held for review. A candidate Grokipedia slug is only a discovery hint.
|
|
20
|
+
|
|
21
|
+
Automatic eligibility additionally requires a reachable exact canonical Grokipedia URL, matching visible article titles, matching source/target language, a canonical Wikipedia source, sufficient independent introductory-content overlap, no disambiguation evidence, and matching section identifiers. The current overlap rule requires at least six shared significant words and 60% overlap relative to the smaller introduction vocabulary. This conservative heuristic is not a universal semantic identity proof; ambiguous or insufficient evidence stays unchanged. German sources cannot be redirected to English destinations.
|
|
22
|
+
|
|
23
|
+
If a Grokipedia replacement becomes unavailable while Wikipedia is reachable, Relink restores Wikipedia. If both are broken, the normal archive workflow applies. **Check again** permits another matching attempt. Reviewers can always set a verified manual destination or retain the original with an undo exception. No undocumented paid API or account is required.
|
|
24
|
+
|
|
25
|
+
## Network safety
|
|
26
|
+
|
|
27
|
+
Production requests use EmDash's DNS-aware restricted HTTP bridge, with the unrestricted-public-network capability needed for arbitrary external links. Local/private addresses, credentials in URLs and unsafe redirects are rejected. Relink also rejects unsafe literals before calling the bridge. Literal IPv6 URLs are conservatively excluded in this release; ordinary hostnames with public IPv6 DNS are handled by EmDash. No cookies, browser credentials or incoming authorization headers are forwarded to link destinations.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Validation and known limitations
|
|
2
|
+
|
|
3
|
+
## Reproducible checks
|
|
4
|
+
|
|
5
|
+
`pnpm check` runs strict TypeScript checking, typed ESLint rules, Vitest and declaration/package builds. The suite exercises HTTP status distinctions, HEAD/GET disagreement, redirects, timeouts, response limits, archive evidence/outages, Grokipedia ambiguity/language/sections, recovery intervals, duplicate targets, removed occurrences, restart state, undo exceptions, publication checks, output replacement and cache headers. Catalog tests cover regional locales, fallback and pluralization.
|
|
6
|
+
|
|
7
|
+
Platform tests use the actual EmDash `PluginStorageRepository` and native Node SQLite adapter. The D1 test uses the actual EmDash D1 adapter against a local workerd D1 binding supplied by stable Miniflare 4.20260730.0. Only the binding import is substituted. Both platforms run EmDash 0.37.0's real atomic cron claim/recovery implementation. That pinned internal scheduler import is confined to tests; the shipped plugin uses only public EmDash APIs.
|
|
8
|
+
|
|
9
|
+
The workspace and isolated example pin vulnerable transitive versions of sharp to 0.35.4 and Undici 7 to 7.29.0. These patches address upstream [sharp](https://github.com/lovell/sharp/security/advisories/GHSA-rgj7-g3m4-5g8c) and [Undici](https://github.com/nodejs/undici/security/advisories/GHSA-4cwx-7wf7-3272) advisories. The plugin's own runtime dependencies are parse5 and Zod; consuming sites manage their EmDash/Astro dependency updates independently.
|
|
10
|
+
|
|
11
|
+
`pnpm test:package` builds a tarball, installs it into `work/package-site` with a separate dependency boundary, seeds EmDash, builds Astro, and checks its published page and authenticated plugin API. Test-only loopback authentication is copied into this isolated fixture and is not included in the package or normal examples. `--prepare-only` prepares that site for the browser suite. Never deploy `work/package-site`.
|
|
12
|
+
|
|
13
|
+
Run `node scripts/seed-browser.mjs` after the package test to populate a deterministic pending-link index from the fixture's actual published content, then run `pnpm test:browser`. The browser suite checks native navigation, dashboard/editor extensions, English/German and regional language detection, live host language switching, unsupported-language fallback, light/dark accessibility, keyboard focus, scoped RTL, CSV downloads and authenticated routes. A separate mounted React test exercises language activation without remounting. No synthetic link-health result is seeded.
|
|
14
|
+
|
|
15
|
+
Cache integration tests use Astro's actual memory provider and simulate elapsed time: a replacement and a rollback appear after the capped 300-second lifetime even when the page asks for a 24-hour cache and stale revalidation. Browser HTTP tests inspect server HTML with no JavaScript and compare actual CMS entries and revisions before/after replacement.
|
|
16
|
+
|
|
17
|
+
`pnpm --filter relink-example-cloudflare build` validates the Cloudflare example and supported scheduled Worker entry. CI runs these checks and uploads the package artifact. It does not publish to npm, create Cloudflare resources or deploy a production site.
|
|
18
|
+
|
|
19
|
+
## Scope and limits
|
|
20
|
+
|
|
21
|
+
- Only published content in configured collections/fields is indexed. Theme-only targets, JavaScript-generated links and embedded media are outside the initial scanner. Root Portable Text fields and explicit nested/wildcard URL selectors are supported.
|
|
22
|
+
- Path templates must match actual public URLs, including locale prefixes and trailing slashes. A slug containing path separators needs a compatible theme/template; placeholders are URL-encoded.
|
|
23
|
+
- Dynamic HTML is required. Previously cached content must be purged at installation, and overriding CDN policies must respect the freshness bound. See [cache operation](installation.md#cache-behaviour).
|
|
24
|
+
- HTTP evidence can be inconclusive. Authentication, anti-bot responses, missing metadata, oversized pages and uncertain article identity intentionally remain review items. No provider's uptime or coverage is guaranteed.
|
|
25
|
+
- Literal IPv6 targets and special-purpose IPv4 ranges are conservatively excluded. Normal public hostnames can resolve to IPv4 or IPv6 through EmDash's DNS-aware network guard. Self-hosted deployments retain EmDash's network-egress requirements; the plugin does not bypass that guard.
|
|
26
|
+
- Grokipedia matching uses visible content evidence and is intentionally conservative. It does not promise semantic equivalence across every encyclopedia article and does not translate articles or section names.
|
|
27
|
+
- Lists are paginated, but filtered counts and collection sweeps currently read the relevant plugin index in memory. Capacity testing is required for very large sites. The admin detail dialog shows the latest 100 events; the global History view paginates retained events. History has no automatic retention deletion.
|
|
28
|
+
- Notifications stay inside EmDash. No email or external messaging integration is included.
|
|
29
|
+
- This validation covers local Node/SQLite, local workerd/D1, built package installation and example builds. Production Cloudflare account configuration, real Cron delivery, CDN overrides and sustained load require deployment-specific checks.
|
|
30
|
+
|
|
31
|
+
English documentation is maintained alongside the code. Behavioural changes require matching tests and release notes; compatibility beyond EmDash 0.37.0 must be verified rather than assumed.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Translation and RTL contributions
|
|
2
|
+
|
|
3
|
+
All documentation, comments and release notes are written in English. The admin currently ships English and German. It listens to the active EmDash Lingui instance; browser language detection and user language changes remain owned by EmDash. Relink has no separate language selector.
|
|
4
|
+
|
|
5
|
+
`src/admin/messages.ts` contains both catalogs. German regional variants resolve to the German messages and English variants to English; other languages use English. Valid supported regional locales are retained for date/number formatting. Every key is required in both catalogs at compile time, and tests verify completeness and plural forms. Lingui's compiled plural representation is used for counts. Host catalogs are not overwritten.
|
|
6
|
+
|
|
7
|
+
To contribute a translation:
|
|
8
|
+
|
|
9
|
+
First arrange the contribution rights described in [CONTRIBUTING.md](../CONTRIBUTING.md). This also applies to translations and documentation so that gekko can include them in commercial distributions.
|
|
10
|
+
|
|
11
|
+
1. Add the catalog with exactly the keys of the English catalog, including errors, notifications, empty states, reasons and accessibility labels.
|
|
12
|
+
2. Extend the explicit locale resolver and supported catalog map.
|
|
13
|
+
3. Add regional/fallback/plural tests and an actual browser language-switch test.
|
|
14
|
+
4. Review long text, keyboard order, dialog focus and both colour themes.
|
|
15
|
+
|
|
16
|
+
Keep article language separate from interface language. Switching the UI to English must not make an English replacement eligible for a German article.
|
|
17
|
+
|
|
18
|
+
## Direction
|
|
19
|
+
|
|
20
|
+
Settings offers **Automatic / Left-to-right / Right-to-left**. Automatic inherits EmDash's direction. An override sets direction on Relink's root and its local Kumo direction provider. Dialogs are native modal dialogs inside that root; select menus inherit the same direction. Relink never changes the host document's `dir` or its language.
|
|
21
|
+
|
|
22
|
+
CSS uses logical dimensions, margins, padding, borders and text alignment. URLs are displayed with `<bdi dir="ltr">`; identifiers are isolated as well. Native dialog behaviour supplies a focus trap, Escape dismissal and focus return. Visible focus styles are supplied for interactive elements and horizontally scrollable tables. The browser suite checks forced RTL, readable URLs, keyboard navigation and dialog focus.
|
|
23
|
+
|
|
24
|
+
Forced RTL is layout support only. This version does not claim Arabic or Hebrew translation coverage.
|
package/package.json
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "emdash-plugin-relink",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Keep EmDash links alive with verified archives and scheduled reviews.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/giffeler/relink.git"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22.16"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"LICENSE",
|
|
19
|
+
"LICENSES",
|
|
20
|
+
"CONTRIBUTING.md",
|
|
21
|
+
"THIRD_PARTY_NOTICES.md",
|
|
22
|
+
"docs"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./runtime": {
|
|
30
|
+
"types": "./dist/plugin.d.ts",
|
|
31
|
+
"import": "./dist/plugin.js"
|
|
32
|
+
},
|
|
33
|
+
"./astro": {
|
|
34
|
+
"types": "./dist/integration.d.ts",
|
|
35
|
+
"import": "./dist/integration.js"
|
|
36
|
+
},
|
|
37
|
+
"./middleware": {
|
|
38
|
+
"types": "./dist/middleware.d.ts",
|
|
39
|
+
"import": "./dist/middleware.js"
|
|
40
|
+
},
|
|
41
|
+
"./admin": {
|
|
42
|
+
"types": "./dist/admin/index.d.ts",
|
|
43
|
+
"import": "./dist/admin/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./styles.css": "./dist/admin/styles.css",
|
|
46
|
+
"./core": {
|
|
47
|
+
"types": "./dist/core/index.d.ts",
|
|
48
|
+
"import": "./dist/core/index.js"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@cloudflare/kumo": "^2.6.0",
|
|
53
|
+
"@emdash-cms/admin": "0.37.0",
|
|
54
|
+
"@lingui/core": "^5.0.0",
|
|
55
|
+
"@lingui/react": "^5.0.0",
|
|
56
|
+
"astro": "^7.3.1",
|
|
57
|
+
"emdash": "0.37.0",
|
|
58
|
+
"react": "^19.2.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"parse5": "8.0.1",
|
|
62
|
+
"zod": "4.5.4"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@astrojs/cloudflare": "14.3.1",
|
|
66
|
+
"@astrojs/node": "11.1.5",
|
|
67
|
+
"@astrojs/react": "6.0.5",
|
|
68
|
+
"@axe-core/playwright": "4.11.1",
|
|
69
|
+
"@cloudflare/kumo": "^2.6.0",
|
|
70
|
+
"@emdash-cms/admin": "0.37.0",
|
|
71
|
+
"@emdash-cms/cloudflare": "0.37.0",
|
|
72
|
+
"@eslint/js": "10.0.1",
|
|
73
|
+
"@lingui/core": "^5.9.5",
|
|
74
|
+
"@lingui/react": "^5.9.5",
|
|
75
|
+
"@playwright/test": "1.63.0",
|
|
76
|
+
"@testing-library/react": "16.3.3",
|
|
77
|
+
"@types/node": "22.20.1",
|
|
78
|
+
"@types/react": "19.2.18",
|
|
79
|
+
"@types/react-dom": "19.2.7",
|
|
80
|
+
"astro": "^7.3.1",
|
|
81
|
+
"emdash": "0.37.0",
|
|
82
|
+
"esbuild": "0.28.1",
|
|
83
|
+
"eslint": "10.10.0",
|
|
84
|
+
"jsdom": "30.0.1",
|
|
85
|
+
"kysely": "0.29.5",
|
|
86
|
+
"miniflare": "4.20260730.0",
|
|
87
|
+
"prettier": "3.6.2",
|
|
88
|
+
"react": "^19.2.8",
|
|
89
|
+
"react-dom": "19.2.8",
|
|
90
|
+
"typescript": "6.0.3",
|
|
91
|
+
"typescript-eslint": "8.70.0",
|
|
92
|
+
"vitest": "5.0.0"
|
|
93
|
+
},
|
|
94
|
+
"homepage": "https://github.com/giffeler/relink#readme",
|
|
95
|
+
"bugs": {
|
|
96
|
+
"url": "https://github.com/giffeler/relink/issues"
|
|
97
|
+
},
|
|
98
|
+
"author": {
|
|
99
|
+
"name": "gekko, Gesellschaft für Kommunikation und Kooperation mbH",
|
|
100
|
+
"url": "https://gekko.de"
|
|
101
|
+
},
|
|
102
|
+
"keywords": [
|
|
103
|
+
"emdash",
|
|
104
|
+
"astro",
|
|
105
|
+
"links",
|
|
106
|
+
"link-checker",
|
|
107
|
+
"wayback",
|
|
108
|
+
"archive",
|
|
109
|
+
"cms"
|
|
110
|
+
],
|
|
111
|
+
"publishConfig": {
|
|
112
|
+
"access": "public",
|
|
113
|
+
"registry": "https://registry.npmjs.org/"
|
|
114
|
+
},
|
|
115
|
+
"scripts": {
|
|
116
|
+
"typecheck": "tsc --noEmit",
|
|
117
|
+
"lint": "eslint . --max-warnings 0",
|
|
118
|
+
"test": "vitest run",
|
|
119
|
+
"build": "tsc -p tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
120
|
+
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build",
|
|
121
|
+
"test:browser": "playwright test",
|
|
122
|
+
"test:package": "node scripts/test-package.mjs"
|
|
123
|
+
}
|
|
124
|
+
}
|