cornhsu-polymigrate 2.2.1 → 2.3.1
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 +52 -7
- package/bin/polymigrate.js +5 -0
- 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 |
|
|
@@ -91,7 +127,7 @@ dotnet tool install -g Cornhsu.PolyMigrate # .NET
|
|
|
91
127
|
```
|
|
92
128
|
|
|
93
129
|
> The npm build ships a self-contained native binary and only downloads the one
|
|
94
|
-
> matching your platform (win32
|
|
130
|
+
> matching your platform (win32 / linux / darwin, each in x64 and arm64).
|
|
95
131
|
> Migrating a site is usually a one-off job — installing a whole SDK for a single
|
|
96
132
|
> run is friction most people won't accept, so the tool meets you where you are.
|
|
97
133
|
|
|
@@ -128,7 +164,7 @@ url_pattern:
|
|
|
128
164
|
extract:
|
|
129
165
|
content: "section[id]:not(#header):not(#footer)"
|
|
130
166
|
pairing:
|
|
131
|
-
fallback: [shared_media, date, title_similarity]
|
|
167
|
+
fallback: [hreflang, slug_normalized, shared_media, date, title_similarity]
|
|
132
168
|
```
|
|
133
169
|
|
|
134
170
|
## Use as a library
|
|
@@ -159,6 +195,7 @@ Console.WriteLine($"{verify.Errors} errors, {verify.Warnings} warnings");
|
|
|
159
195
|
| `src/PolyMigrate.Cli` | the `polymigrate` CLI (NuGet tool package: `Cornhsu.PolyMigrate`) |
|
|
160
196
|
| `tests/` | unit/integration tests + an offline fixture site with golden-file baselines |
|
|
161
197
|
| `docs/contracts.md` | file-format contracts between pipeline phases |
|
|
198
|
+
| `docs/hreflang_量測與決策.md` | why `hreflang` only ever *suggests* — the measurement behind that call |
|
|
162
199
|
| `docs/搬遷工具_評估與規劃書.md` | the original design/planning doc (the `§X.Y` references throughout the source point here) |
|
|
163
200
|
|
|
164
201
|
## Development
|
|
@@ -169,6 +206,14 @@ dotnet test
|
|
|
169
206
|
dotnet run --project src/PolyMigrate.Cli -- --help
|
|
170
207
|
```
|
|
171
208
|
|
|
209
|
+
## See also
|
|
210
|
+
|
|
211
|
+
[**多語言網站搬遷最容易搬丟的東西**](https://cornhsu.com/articles/multilingual-site-migration-pairing)
|
|
212
|
+
(in Traditional Chinese) — background on the category rather than this tool: the five pairing
|
|
213
|
+
signals in priority order, why "just read `hreflang`" often misses on real legacy sites
|
|
214
|
+
(measured on one: 528 pages, **0** declarations, sitemap 404), the five gates a declaration has to
|
|
215
|
+
pass before it is usable at all, and why URL string similarity is the wrong tool for this job.
|
|
216
|
+
|
|
172
217
|
License: [MIT](LICENSE). All dependencies are MIT/BSD/Apache-2.0
|
|
173
218
|
(imaging via **Magick.NET**; ImageSharp was dropped when its 4.x line began requiring a
|
|
174
219
|
license key at build time). See [THIRD-PARTY-NOTICES](THIRD-PARTY-NOTICES.md).
|
package/bin/polymigrate.js
CHANGED
|
@@ -19,9 +19,14 @@ const { spawnSync } = require("node:child_process");
|
|
|
19
19
|
const path = require("node:path");
|
|
20
20
|
|
|
21
21
|
// 平台 → 子套件名 / 執行檔名
|
|
22
|
+
// ⚠ 這份清單要與 npm/prepare.mjs 的 TARGETS 一致。
|
|
23
|
+
// 兩邊漂開的話症狀很隱蔽:平台包會被 npm 正確裝好,啟動時卻說「沒有預先建置版本」。
|
|
24
|
+
// (2.1.0 加了 win32-arm64 / linux-arm64,但漏改這裡,2.1.0~2.3.0 的 arm64 使用者都中。)
|
|
22
25
|
const TARGETS = {
|
|
23
26
|
"win32 x64": { pkg: "@cornhsu/polymigrate-win32-x64", bin: "polymigrate.exe" },
|
|
27
|
+
"win32 arm64": { pkg: "@cornhsu/polymigrate-win32-arm64", bin: "polymigrate.exe" },
|
|
24
28
|
"linux x64": { pkg: "@cornhsu/polymigrate-linux-x64", bin: "polymigrate" },
|
|
29
|
+
"linux arm64": { pkg: "@cornhsu/polymigrate-linux-arm64", bin: "polymigrate" },
|
|
25
30
|
"darwin x64": { pkg: "@cornhsu/polymigrate-darwin-x64", bin: "polymigrate" },
|
|
26
31
|
"darwin arm64": { pkg: "@cornhsu/polymigrate-darwin-arm64", bin: "polymigrate" },
|
|
27
32
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cornhsu-polymigrate",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
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.1",
|
|
40
|
+
"@cornhsu/polymigrate-win32-arm64": "2.3.1",
|
|
41
|
+
"@cornhsu/polymigrate-linux-x64": "2.3.1",
|
|
42
|
+
"@cornhsu/polymigrate-linux-arm64": "2.3.1",
|
|
43
|
+
"@cornhsu/polymigrate-darwin-x64": "2.3.1",
|
|
44
|
+
"@cornhsu/polymigrate-darwin-arm64": "2.3.1"
|
|
45
45
|
}
|
|
46
46
|
}
|