import type { Parser, ExtractedRecord, RawDocument, ConnectorContext } from "@dci/connectors"; import { countryFromText, cleanText } from "@dci/core"; import { loadHtml, tableRows, txt, parenthetical, resolvePlace, regionRecord, type Place } from "./shared.js"; import { normKey } from "./city-coords.js"; /** * Alibaba Cloud — https://www.alibabacloud.com/help/en/ecs/product-overview/regions-and-zones (static docs): * table Region name | Region ID | Number of zones | Zone name | Zone ID e.g. "Germany (Frankfurt)" | eu-central-1 | 3 * Launch years come from https://www.alibabacloud.com/global-locations (static
  • : "Japan (Tokyo) Availability Zone: 5 * Released in 2016"), joined on the region display name. */ const LOCATIONS_PAGE = "https://www.alibabacloud.com/global-locations"; const NAME_ISO: Array<[RegExp, string]> = [[/^China\b/i, "CN"], [/^SAU\b/i, "SA"], [/^UK\b/i, "GB"], [/^US\b/i, "US"], [/^UAE\b/i, "AE"], [/^Mexico\b/i, "MX"]]; async function releaseYears(ctx: ConnectorContext): Promise> { const cached = await ctx.getState>("alibaba:releaseYears"); if (cached) return new Map(Object.entries(cached)); const map = new Map(); const doc = await ctx.fetch(LOCATIONS_PAGE, { group: "regions" }); if (doc.error || doc.status !== 200) { ctx.log("warn", `alibaba global-locations unavailable (${doc.error?.code ?? doc.status}); launch years left null`); return map; } const $ = loadHtml(doc); $("li.item").each((_, li) => { const t = txt($(li)); const m = t.match(/^(.*?)\s*Availability Zone:\s*(\d+)\s*Released in\s*(\d{4})/i); if (m) map.set(normKey(m[1]!), m[3]!); }); await ctx.setState("alibaba:releaseYears", Object.fromEntries(map)); return map; } export const alibabaRegionsParser: Parser = { name: "cloud_alibaba_regions_v1", version: "1.0.0", pageTypes: ["cloud_region"], async parse(doc: RawDocument, ctx): Promise { const $ = loadHtml(doc); const years = await releaseYears(ctx); const out: ExtractedRecord[] = []; const seen = new Set(); for (const row of tableRows($, $("table"))) { if (row.length < 3) continue; const code = row[1]!.trim(); if (!/^[a-z]{2}-[a-z0-9-]+$/.test(code) || seen.has(code)) continue; seen.add(code); const rawName = row[0]!; const closing = /closing down/i.test(rawName); const name = cleanText(rawName.replace(/closing down/i, "").replace(/\s*-\s*(Local|Partner) Region/i, "")) ?? rawName; const zones = Number.parseInt(row[2]!.replace(/[^\d]/g, ""), 10); const iso = NAME_ISO.find(([re]) => re.test(name))?.[1] ?? countryFromText(name.replace(/\(.*\)/, "")) ?? null; const label = parenthetical(rawName)?.replace(/\s*-\s*(Local|Partner) Region/i, "") ?? (iso === "SG" ? "Singapore" : null); const isoHint = label && /^hong kong$/i.test(label) ? "HK" : label && /^macau|macao$/i.test(label) ? "MO" : iso; const place: Place = resolvePlace(label, isoHint); if (!place.countryIso2) place.countryIso2 = isoHint ?? (label ? countryFromText(label) : null); const year = years.get(normKey(name)) ?? years.get(normKey(rawName.replace(/closing down/i, ""))) ?? null; const externalIds: Record = { alibaba_region_id: code, kind: /local region/i.test(rawName) ? "local_region" : "region" }; if (closing) externalIds.lifecycle = "closing_down"; if (/partner region/i.test(rawName)) externalIds.operated_by = "partner"; if (label) externalIds.published_location = label; out.push( regionRecord({ connectorId: ctx.connectorId, keyPrefix: "alibaba:region", providerName: "Alibaba Cloud", code, name, place, availabilityZones: Number.isFinite(zones) && zones > 0 ? zones : null, launchedOn: year, status: "operational", externalIds, url: doc.finalUrl, methods: { code: "html:table.RegionID", name: "html:table.RegionName", availabilityZones: "html:table.NumberOfZones", launchedOn: year ? "html:global-locations.ReleasedIn" : "none", city: "curated-city-table" }, }), ); } ctx.log("info", `alibaba: ${out.length} regions (${[...years.keys()].length} launch years matched from global-locations)`); return out; }, };