cornhsu-polymigrate 2.2.0 → 2.3.0
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 +49 -6
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ PolyMigrate turns legacy dynamic sites (old PHP sites and the like) into clean,
|
|
|
13
13
|
Markdown — and it treats multilingual content as a first-class concern, not an afterthought.
|
|
14
14
|
Config-driven, fully offline-rerunnable, built on .NET.
|
|
15
15
|
|
|
16
|
-
**Status: 2.
|
|
16
|
+
**Status: 2.3.** The extraction pipeline, pairing, verification, thumbnails and orphan-page
|
|
17
17
|
recovery are complete and validated against a real full-site migration (see below). The **CLI
|
|
18
18
|
surface and the Phase output contracts remain stable** (unchanged since 1.0): new features bump
|
|
19
19
|
the minor version, fixes bump the patch. 2.0 was an engineering release — it narrowed the
|
|
@@ -21,8 +21,9 @@ the minor version, fixes bump the patch. 2.0 was an engineering release — it n
|
|
|
21
21
|
config fields; 2.1 fixed up the release pipeline (arm64 npm packages, symbol packages).
|
|
22
22
|
2.2 closed a blind spot in `verify` — pages nothing links to were reported as fine, because a
|
|
23
23
|
link-following audit never reaches them — and added `slugs` for keeping up with a source site
|
|
24
|
-
that is still being updated. **Upgrading to 2.2 can turn a passing `verify` into exit 1
|
|
25
|
-
|
|
24
|
+
that is still being updated. **Upgrading to 2.2 can turn a passing `verify` into exit 1**.
|
|
25
|
+
2.3 reads the source site's own `hreflang` declarations (see below) and reports them in a new
|
|
26
|
+
`hreflang_map.csv`. See the [CHANGELOG](CHANGELOG.md) for the migration notes.
|
|
26
27
|
|
|
27
28
|
## Why
|
|
28
29
|
|
|
@@ -33,14 +34,48 @@ the pairing to you. PolyMigrate:
|
|
|
33
34
|
|
|
34
35
|
- **pairs automatically** where filenames are symmetric (`/ch/news/x` ↔ `/en/news/x` share a
|
|
35
36
|
`translation_key`),
|
|
36
|
-
- **
|
|
37
|
-
|
|
37
|
+
- **reads the site's own `hreflang` declarations** — `<link rel="alternate" hreflang>` is the one
|
|
38
|
+
pairing signal the site's authors *stated* rather than something a tool inferred, so it is
|
|
39
|
+
tried first and can pair across sections (`/ch/news/` ↔ `/en/press/`),
|
|
40
|
+
- **suggests pairs heuristically** where neither applies — slugs that differ only in separators
|
|
41
|
+
(`2025-light-offering` vs `2025_light_offering`), shared photo albums, normalized dates hidden
|
|
42
|
+
in slugs (`20240121` vs `01212024`), title similarity,
|
|
38
43
|
- **honestly reports what it cannot pair**, producing a review-ready gap inventory instead of
|
|
39
44
|
guessing wrong.
|
|
40
45
|
|
|
46
|
+
Note what PolyMigrate deliberately does *not* do: match language versions by URL string
|
|
47
|
+
similarity. `/products/`, `/produkte/` and `/產品/` have a similarity of zero and are the same
|
|
48
|
+
page; symmetric-path pairing is an exact match after the language prefix is stripped, never a
|
|
49
|
+
fuzzy one.
|
|
50
|
+
|
|
41
51
|
Languages are not limited to two: declare any number in `lang_map` and every output
|
|
42
52
|
(frontmatter, inventories, pairing) expands accordingly. All locale output is standard BCP-47.
|
|
43
53
|
|
|
54
|
+
### About `hreflang`
|
|
55
|
+
|
|
56
|
+
Legacy sites are exactly the sites whose `hreflang` is missing or wrong — usually bolted on by
|
|
57
|
+
an SEO contractor years later. So PolyMigrate treats it as strong evidence, not as gospel.
|
|
58
|
+
A declaration is only used for pairing if it:
|
|
59
|
+
|
|
60
|
+
- isn't `x-default` (that's "where to go when nothing matches", not a language version),
|
|
61
|
+
- doesn't point at the page itself,
|
|
62
|
+
- resolves to a page actually in your mirror — **same host included**, since plenty of sites put
|
|
63
|
+
the English version on `en.example.org`, where a path-only comparison would make a page its
|
|
64
|
+
own translation,
|
|
65
|
+
- and **isn't one of many same-language pages claiming the same target**. This is the classic
|
|
66
|
+
failure: one `<link>` pasted into the site-wide template, so every Chinese page declares the
|
|
67
|
+
English homepage as its English version. One page cannot be the translation of three others,
|
|
68
|
+
so all of those claims are dropped rather than turned into a confident-looking wrong pair.
|
|
69
|
+
|
|
70
|
+
Everything declared — including the rejected ones — is written to `hreflang_map.csv` with
|
|
71
|
+
`in_mirror` / `reciprocal` / `usable` and a `reject_reason` naming which rule it failed, because
|
|
72
|
+
"can I trust this site's hreflang?" is a question you want answered with data, not a total.
|
|
73
|
+
|
|
74
|
+
Pairs are still only ever *suggested*: `hreflang` raises the evidence quality, it does not
|
|
75
|
+
switch the tool into merging pages on its own. That is a deliberate call, not an unfinished
|
|
76
|
+
one — the measurement behind it, and what would have to change to revisit it, are recorded in
|
|
77
|
+
[docs/hreflang_量測與決策.md](docs/hreflang_量測與決策.md).
|
|
78
|
+
|
|
44
79
|
## Battle-tested defaults
|
|
45
80
|
|
|
46
81
|
The extraction pipeline bakes in fixes for real problems found during a real migration —
|
|
@@ -55,6 +90,7 @@ things generic tools and LLM extractors silently get wrong:
|
|
|
55
90
|
| `<title>` polluted with dates and site name | body-first title extraction + configurable cleanup |
|
|
56
91
|
| Mixed date formats (`YYYYMMDD` / `MMDDYYYY` / `DDMMYYYY`) | all recognized and normalized |
|
|
57
92
|
| Old articles removed from indexes but still served | orphan probing (per-day URL candidates + suffix variants) |
|
|
93
|
+
| `hreflang` pointing at dead URLs, other hosts, or itself | validated against the mirror; unusable ones recorded, not applied |
|
|
58
94
|
| Broken images on the source site | detected, recorded, never blocks the run |
|
|
59
95
|
| Bot protection (JS cookie challenge → 409) | declarative cookie workaround in config |
|
|
60
96
|
| Legacy encodings (Big5, GB2312, …) | declared or defaulted per site |
|
|
@@ -105,6 +141,12 @@ polymigrate fetch-orphans site.yaml --section news
|
|
|
105
141
|
polymigrate slugs . --section news # slugs already mirrored under ./raw (pipeable)
|
|
106
142
|
```
|
|
107
143
|
|
|
144
|
+
> **`raw/` is the one thing you cannot regenerate.** Re-crawling a site takes hours at a
|
|
145
|
+
> polite request rate, and the source site may be switched off the moment you finish
|
|
146
|
+
> migrating — at which point your mirror is the only copy left. No PolyMigrate command
|
|
147
|
+
> ever deletes or overwrites `raw/` or `media/`; keep them until the migration is signed
|
|
148
|
+
> off, and back them up if the source is going away.
|
|
149
|
+
|
|
108
150
|
> No .NET? Every command above also runs with **zero install** via npm — just prefix it:
|
|
109
151
|
> `npx cornhsu-polymigrate extract site.yaml`, `npx cornhsu-polymigrate verify out/`, and so on.
|
|
110
152
|
|
|
@@ -122,7 +164,7 @@ url_pattern:
|
|
|
122
164
|
extract:
|
|
123
165
|
content: "section[id]:not(#header):not(#footer)"
|
|
124
166
|
pairing:
|
|
125
|
-
fallback: [shared_media, date, title_similarity]
|
|
167
|
+
fallback: [hreflang, slug_normalized, shared_media, date, title_similarity]
|
|
126
168
|
```
|
|
127
169
|
|
|
128
170
|
## Use as a library
|
|
@@ -153,6 +195,7 @@ Console.WriteLine($"{verify.Errors} errors, {verify.Warnings} warnings");
|
|
|
153
195
|
| `src/PolyMigrate.Cli` | the `polymigrate` CLI (NuGet tool package: `Cornhsu.PolyMigrate`) |
|
|
154
196
|
| `tests/` | unit/integration tests + an offline fixture site with golden-file baselines |
|
|
155
197
|
| `docs/contracts.md` | file-format contracts between pipeline phases |
|
|
198
|
+
| `docs/hreflang_量測與決策.md` | why `hreflang` only ever *suggests* — the measurement behind that call |
|
|
156
199
|
| `docs/搬遷工具_評估與規劃書.md` | the original design/planning doc (the `§X.Y` references throughout the source point here) |
|
|
157
200
|
|
|
158
201
|
## Development
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cornhsu-polymigrate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "The i18n-first static-site migrator — the only one that pairs your multilingual pages automatically. Mirrors a legacy site into frontmatter Markdown, auto-matches language versions, and honestly lists what it could not pair.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@cornhsu/polymigrate-win32-x64": "2.
|
|
40
|
-
"@cornhsu/polymigrate-win32-arm64": "2.
|
|
41
|
-
"@cornhsu/polymigrate-linux-x64": "2.
|
|
42
|
-
"@cornhsu/polymigrate-linux-arm64": "2.
|
|
43
|
-
"@cornhsu/polymigrate-darwin-x64": "2.
|
|
44
|
-
"@cornhsu/polymigrate-darwin-arm64": "2.
|
|
39
|
+
"@cornhsu/polymigrate-win32-x64": "2.3.0",
|
|
40
|
+
"@cornhsu/polymigrate-win32-arm64": "2.3.0",
|
|
41
|
+
"@cornhsu/polymigrate-linux-x64": "2.3.0",
|
|
42
|
+
"@cornhsu/polymigrate-linux-arm64": "2.3.0",
|
|
43
|
+
"@cornhsu/polymigrate-darwin-x64": "2.3.0",
|
|
44
|
+
"@cornhsu/polymigrate-darwin-arm64": "2.3.0"
|
|
45
45
|
}
|
|
46
46
|
}
|