@mailwoman/codex 9.1.0 → 9.2.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 +48 -5
- package/ca/index.ts +1 -1
- package/ca/postal-code.ts +11 -11
- package/country/display-names.ts +150 -0
- package/country/index.ts +2 -0
- package/country/population.ts +265 -0
- package/country/subdivision.ts +59 -0
- package/de/index.ts +1 -1
- package/de/postleitzahl.ts +3 -3
- package/es/codigo-postal.ts +2 -2
- package/fr/code-postal.ts +6 -6
- package/fr/index.ts +1 -1
- package/index.ts +10 -1
- package/it/cap.ts +2 -2
- package/jp/index.ts +1 -1
- package/jp/postal-code.ts +11 -11
- package/out/ca/index.d.ts +1 -1
- package/out/ca/index.js +1 -1
- package/out/ca/postal-code.d.ts +10 -10
- package/out/ca/postal-code.js +9 -9
- package/out/country/display-names.d.ts +67 -0
- package/out/country/display-names.d.ts.map +1 -0
- package/out/country/display-names.js +118 -0
- package/out/country/display-names.js.map +1 -0
- package/out/country/index.d.ts +2 -0
- package/out/country/index.d.ts.map +1 -1
- package/out/country/index.js +2 -0
- package/out/country/index.js.map +1 -1
- package/out/country/population.d.ts +16 -0
- package/out/country/population.d.ts.map +1 -0
- package/out/country/population.js +265 -0
- package/out/country/population.js.map +1 -0
- package/out/country/subdivision.d.ts +7 -0
- package/out/country/subdivision.d.ts.map +1 -1
- package/out/country/subdivision.js +49 -0
- package/out/country/subdivision.js.map +1 -1
- package/out/de/index.d.ts +1 -1
- package/out/de/index.js +1 -1
- package/out/de/postleitzahl.d.ts +3 -3
- package/out/de/postleitzahl.js +2 -2
- package/out/es/codigo-postal.d.ts +2 -2
- package/out/es/codigo-postal.js +1 -1
- package/out/fr/code-postal.d.ts +5 -5
- package/out/fr/code-postal.js +6 -6
- package/out/fr/index.d.ts +1 -1
- package/out/fr/index.js +1 -1
- package/out/index.d.ts +1 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +1 -1
- package/out/index.js.map +1 -1
- package/out/it/cap.d.ts +2 -2
- package/out/it/cap.js +1 -1
- package/out/jp/index.d.ts +1 -1
- package/out/jp/index.js +1 -1
- package/out/jp/postal-code.d.ts +11 -11
- package/out/jp/postal-code.js +8 -8
- package/out/postcode-systems.d.ts +84 -0
- package/out/postcode-systems.d.ts.map +1 -1
- package/out/postcode-systems.js +106 -0
- package/out/postcode-systems.js.map +1 -1
- package/out/tools/build-country-surface-lexicon.js +1 -1
- package/out/tools/build-country-surface-lexicon.js.map +1 -1
- package/out/tools/export-country-surfaces.js +1 -1
- package/out/tools/export-country-surfaces.js.map +1 -1
- package/out/tools/generate-country-population.d.ts +42 -0
- package/out/tools/generate-country-population.d.ts.map +1 -0
- package/out/tools/generate-country-population.js +106 -0
- package/out/tools/generate-country-population.js.map +1 -0
- package/out/tools/generate-country-reference.d.ts.map +1 -1
- package/out/tools/generate-country-reference.js +4 -4
- package/out/tools/generate-country-reference.js.map +1 -1
- package/out/tools/generate-official-languages.d.ts.map +1 -1
- package/out/tools/generate-official-languages.js +7 -4
- package/out/tools/generate-official-languages.js.map +1 -1
- package/out/tools/index.d.ts +2 -2
- package/out/tools/index.d.ts.map +1 -1
- package/out/tools/index.js +2 -2
- package/out/tools/index.js.map +1 -1
- package/out/us/zipcode.d.ts +1 -1
- package/out/us/zipcode.js +1 -1
- package/package.json +326 -4
- package/postcode-systems.ts +112 -0
- package/tools/build-country-surface-lexicon.ts +1 -1
- package/tools/export-country-surfaces.ts +1 -1
- package/tools/generate-country-population.ts +143 -0
- package/tools/generate-country-reference.ts +5 -3
- package/tools/generate-official-languages.ts +8 -4
- package/tools/index.ts +2 -2
- package/us/zipcode.ts +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Regenerate `codex/country/population.ts` — the per-country population table — from GeoNames
|
|
7
|
+
* `countryInfo.txt` (https://download.geonames.org/export/dump/countryInfo.txt, CC-BY-4.0). The
|
|
8
|
+
* output is committed; this tool makes it reproducible (provenance), not a hand-typed dictionary.
|
|
9
|
+
*
|
|
10
|
+
* Why it exists (#1650): WOF carries no readable population for 147 of 237 country records
|
|
11
|
+
* (measured against the 2026-08-18 candidate build), so those countries entered every prominence
|
|
12
|
+
* race at an asserted ZERO — ranked below any namesake hamlet. The magnitude is what a fame race
|
|
13
|
+
* reads, so a census-vintage figure is entirely sufficient; currency of the estimate is not the
|
|
14
|
+
* point.
|
|
15
|
+
*
|
|
16
|
+
* Rows whose GeoNames population is 0 are DROPPED rather than emitted: an entry in this table is a
|
|
17
|
+
* positive claim, and the consumer's absence branch (`?? undefined`) must stay reachable for
|
|
18
|
+
* territories GeoNames itself declines to estimate (the meaning-of-zero rule).
|
|
19
|
+
*
|
|
20
|
+
* Usage: mailwoman dev generate country-population
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { writeFileSync } from "node:fs"
|
|
24
|
+
import { fileURLToPath } from "node:url"
|
|
25
|
+
|
|
26
|
+
import { APIClient, pluckResponseData } from "@mailwoman/core/api"
|
|
27
|
+
|
|
28
|
+
const SOURCE = "https://download.geonames.org/export/dump/countryInfo.txt"
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The committed output path, resolved relative to this module (codex/tools/ → codex/country/). See
|
|
32
|
+
* `generate-country-reference.ts` for why this is `import.meta.url` rather than a `core` path helper (codex is
|
|
33
|
+
* zero-runtime-dep and `core` already references `codex` — importing core here would cycle the project graph).
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_OUT = fileURLToPath(new URL("../country/population.ts", import.meta.url))
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tab positions this tool reads from `countryInfo.txt`'s 19-column rows. Named so the parse states which columns it
|
|
39
|
+
* believes in; a GeoNames format change fails the count guard below rather than silently reading the wrong column.
|
|
40
|
+
*/
|
|
41
|
+
const COLUMN_ISO2 = 0
|
|
42
|
+
const COLUMN_POPULATION = 7
|
|
43
|
+
const MINIMUM_COLUMNS = 8
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* GeoNames publishes ~250 countries/territories; a parse recovering fewer than this read the wrong column or a
|
|
47
|
+
* truncated body, and the guard fails loudly instead of committing a hollow table.
|
|
48
|
+
*/
|
|
49
|
+
const MINIMUM_PLAUSIBLE_COUNTRIES = 200
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Numbers below five digits are emitted bare — the house numeric-separator style groups by three and only from five
|
|
53
|
+
* digits up (`8450`, not `8_450`).
|
|
54
|
+
*/
|
|
55
|
+
const SEPARATOR_MINIMUM = 10_000
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Options for {@linkcode generateCountryPopulation}.
|
|
59
|
+
*/
|
|
60
|
+
export interface GenerateCountryPopulationOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Output path override. Default: `codex/country/population.ts` (the committed table).
|
|
63
|
+
*/
|
|
64
|
+
out?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Summary returned by {@linkcode generateCountryPopulation}.
|
|
69
|
+
*/
|
|
70
|
+
export interface GenerateCountryPopulationSummary {
|
|
71
|
+
countries: number
|
|
72
|
+
outPath: string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch GeoNames `countryInfo.txt` and regenerate the committed `COUNTRY_POPULATION` table.
|
|
77
|
+
*/
|
|
78
|
+
export async function generateCountryPopulation(
|
|
79
|
+
options: GenerateCountryPopulationOptions = {},
|
|
80
|
+
report?: (line: string) => void
|
|
81
|
+
): Promise<GenerateCountryPopulationSummary> {
|
|
82
|
+
const outPath = options.out ?? DEFAULT_OUT
|
|
83
|
+
|
|
84
|
+
// `responseType: "text"` because the source is a tab-separated dump, not JSON.
|
|
85
|
+
const text = await new APIClient({ displayName: "geonames-country-info", retry: true })
|
|
86
|
+
.fetch<string>({ url: SOURCE, responseType: "text" })
|
|
87
|
+
.then(pluckResponseData)
|
|
88
|
+
|
|
89
|
+
const rows: Record<string, number> = {}
|
|
90
|
+
|
|
91
|
+
// oxlint-disable-next-line mailwoman/prefer-spliterator -- countryInfo.txt is ~35 kB and bounded (~300 rows)
|
|
92
|
+
for (const line of text.split("\n")) {
|
|
93
|
+
if (!line || line.startsWith("#")) continue
|
|
94
|
+
// oxlint-disable-next-line mailwoman/prefer-spliterator -- one 19-column row
|
|
95
|
+
const columns = line.split("\t")
|
|
96
|
+
|
|
97
|
+
if (columns.length < MINIMUM_COLUMNS) continue
|
|
98
|
+
const alpha2 = columns[COLUMN_ISO2]!.trim()
|
|
99
|
+
const population = Number(columns[COLUMN_POPULATION])
|
|
100
|
+
|
|
101
|
+
if (!/^[A-Z]{2}$/.test(alpha2)) continue
|
|
102
|
+
|
|
103
|
+
if (!Number.isFinite(population) || population <= 0) continue
|
|
104
|
+
|
|
105
|
+
rows[alpha2] = population
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Object.keys(rows).length < MINIMUM_PLAUSIBLE_COUNTRIES) {
|
|
109
|
+
throw new Error(`generateCountryPopulation: only ${Object.keys(rows).length} rows parsed — format drift?`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const body = Object.keys(rows)
|
|
113
|
+
.toSorted()
|
|
114
|
+
.map((k) => {
|
|
115
|
+
const n = rows[k]!
|
|
116
|
+
const literal = n >= SEPARATOR_MINIMUM ? n.toLocaleString("en-US").replaceAll(",", "_") : String(n)
|
|
117
|
+
|
|
118
|
+
return `\t${k}: ${literal},`
|
|
119
|
+
})
|
|
120
|
+
.join("\n")
|
|
121
|
+
|
|
122
|
+
const header = `/**
|
|
123
|
+
* @copyright Sister Software
|
|
124
|
+
* @license AGPL-3.0
|
|
125
|
+
* @author Teffen Ellis, et al.
|
|
126
|
+
*
|
|
127
|
+
* GENERATED — do not edit by hand. Per-country population, derived from GeoNames countryInfo.txt
|
|
128
|
+
* (https://download.geonames.org/export/dump/countryInfo.txt, CC-BY-4.0). Estimates are
|
|
129
|
+
* census-vintage; a prominence race reads the magnitude, not the currency. Countries GeoNames
|
|
130
|
+
* declines to estimate are ABSENT, never zero.
|
|
131
|
+
* Regenerate with: mailwoman dev generate country-population
|
|
132
|
+
*/
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* ISO 3166-1 alpha-2 → population estimate.
|
|
136
|
+
*/
|
|
137
|
+
export const COUNTRY_POPULATION: Readonly<Record<string, number>> = {`
|
|
138
|
+
|
|
139
|
+
writeFileSync(outPath, `${header}\n${body}\n}\n`)
|
|
140
|
+
report?.(`wrote ${outPath} (${Object.keys(rows).length} countries)`)
|
|
141
|
+
|
|
142
|
+
return { countries: Object.keys(rows).length, outPath }
|
|
143
|
+
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import { writeFileSync } from "node:fs"
|
|
18
18
|
import { fileURLToPath } from "node:url"
|
|
19
19
|
|
|
20
|
+
import { APIClient, pluckResponseData } from "@mailwoman/core/api"
|
|
21
|
+
|
|
20
22
|
const SOURCE = "https://raw.githubusercontent.com/mledoze/countries/master/countries.json"
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -97,10 +99,10 @@ export async function generateCountryReference(
|
|
|
97
99
|
report?: (line: string) => void
|
|
98
100
|
): Promise<GenerateCountryReferenceSummary> {
|
|
99
101
|
const outPath = options.out ?? DEFAULT_OUT
|
|
100
|
-
const response = await fetch(SOURCE)
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const countries = await new APIClient({ displayName: "mledoze-countries", retry: true })
|
|
104
|
+
.fetch<MledozeCountry[]>({ url: SOURCE })
|
|
105
|
+
.then(pluckResponseData)
|
|
104
106
|
|
|
105
107
|
const rows: Record<string, CountryReferenceEntry> = {}
|
|
106
108
|
|
|
@@ -23,6 +23,7 @@ import { readFileSync, writeFileSync } from "node:fs"
|
|
|
23
23
|
import { join } from "node:path"
|
|
24
24
|
import { fileURLToPath } from "node:url"
|
|
25
25
|
|
|
26
|
+
import { APIClient, pluckResponseData } from "@mailwoman/core/api"
|
|
26
27
|
import { parseJSONStrict } from "@mailwoman/core/objects"
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -59,6 +60,12 @@ export interface GenerateOfficialLanguagesSummary {
|
|
|
59
60
|
outPath: string
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* One call per CLDR supplemental file, against a CDN that throttles. Retry only — the caller makes a handful of
|
|
65
|
+
* requests, so a rate budget would be ceremony over a burst that never happens.
|
|
66
|
+
*/
|
|
67
|
+
const cldrClient = new APIClient({ displayName: "cldr", retry: true })
|
|
68
|
+
|
|
62
69
|
interface LanguagePopulation {
|
|
63
70
|
_officialStatus?: string
|
|
64
71
|
}
|
|
@@ -69,11 +76,8 @@ async function loadCLDR(file: string, cldrDir: string | undefined, cldrVersion:
|
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
const url = `https://cdn.jsdelivr.net/npm/cldr-core@${cldrVersion}/supplemental/${file}.json`
|
|
72
|
-
const res = await fetch(url)
|
|
73
|
-
|
|
74
|
-
if (!res.ok) throw new Error(`${url}: HTTP ${res.status}`)
|
|
75
79
|
|
|
76
|
-
return
|
|
80
|
+
return cldrClient.fetch<unknown>({ url }).then(pluckResponseData)
|
|
77
81
|
}
|
|
78
82
|
|
|
79
83
|
/**
|
package/tools/index.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
6
|
* Codex operator tools — the `run()`-style modules behind `mailwoman dev generate …` commands. No
|
|
7
|
-
* argv, no `process.exit`: commands own parsing, rendering, and exit codes
|
|
8
|
-
* scripts→Pastel spec).
|
|
7
|
+
* argv, no `process.exit`: commands own parsing, rendering, and exit codes.
|
|
9
8
|
*/
|
|
10
9
|
|
|
10
|
+
export * from "./generate-country-population.ts"
|
|
11
11
|
export * from "./generate-country-reference.ts"
|
|
12
12
|
export * from "./generate-official-languages.ts"
|
package/us/zipcode.ts
CHANGED
|
@@ -191,7 +191,7 @@ export interface PluckedStateZIPCodeResult {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
/**
|
|
194
|
-
* Given an address string like `"NY"`, `"CA 94016"`, attempts to match the state abbreviation and
|
|
194
|
+
* Given an address string like `"NY"`, `"CA 94016"`, attempts to match the state abbreviation and postcode, if
|
|
195
195
|
* applicable.
|
|
196
196
|
*
|
|
197
197
|
* @see {@linkcode isUSStateAbbreviation} to validate the state abbreviation.
|