From 59bb3d3093bbe1e7a998b081926ae0324fdbda77 Mon Sep 17 00:00:00 2001 From: Ismail Sunni Date: Fri, 17 Oct 2025 23:06:41 +0700 Subject: [PATCH 1/5] PB-1383: Migrate update pnpm workspace. --- package.json | 4 +- ...-workspace.js => update-pnpm-workspace.ts} | 105 ++++++++++++------ 2 files changed, 76 insertions(+), 33 deletions(-) rename scripts/{update-pnpm-workspace.js => update-pnpm-workspace.ts} (71%) diff --git a/package.json b/package.json index 6b5837ec02..93d80d3eb0 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,15 @@ "test:e2e:headless": "pnpm --filter=web-mapviewer run test:e2e:headless", "test:unit": "pnpm run --recursive --parallel --no-bail --if-present test:unit", "test:unit:watch": "pnpm run --recursive --if-present test:unit:watch", - "update:workspace": "node ./scripts/update-pnpm-workspace.js" + "update:workspace": "tsx ./scripts/update-pnpm-workspace.ts" }, "engines": { "node": ">=22.18", "pnpm": ">=10.15" }, "devDependencies": { + "@types/node": "catalog:", + "tsx": "catalog:", "yaml": "catalog:" }, "pnpm": { diff --git a/scripts/update-pnpm-workspace.js b/scripts/update-pnpm-workspace.ts similarity index 71% rename from scripts/update-pnpm-workspace.js rename to scripts/update-pnpm-workspace.ts index 5fc6624528..9024309357 100644 --- a/scripts/update-pnpm-workspace.js +++ b/scripts/update-pnpm-workspace.ts @@ -1,22 +1,57 @@ -// update-pnpm-workspace.js +// update-pnpm-workspace.ts // Requires: Node 18+ (global fetch) and `yaml` package // Install: pnpm add -D yaml // // Usage: -// node scripts/update-catalog-to-latest.js pnpm-workspace.yaml [--dry-run] [--same-major] +// node scripts/update-pnpm-workspace.ts pnpm-workspace.yaml [--dry-run] [--same-major] // [--registry ] [--tag ] [--concurrency ] // // Example: -// node scripts/update-catalog-to-latest.js pnpm-workspace.yaml --dry-run -// node scripts/update-catalog-to-latest.js pnpm-workspace.yaml --same-major -// node scripts/update-catalog-to-latest.js pnpm-workspace.yaml --registry https://registry.npmjs.org --tag latest +// node scripts/update-pnpm-workspace.ts pnpm-workspace.yaml --dry-run +// node scripts/update-pnpm-workspace.ts pnpm-workspace.yaml --same-major +// node scripts/update-pnpm-workspace.ts pnpm-workspace.yaml --registry https://registry.npmjs.org --tag latest import { readFileSync, writeFileSync } from 'node:fs' import { basename } from 'node:path' import YAML from 'yaml' +import type { Document } from 'yaml' -function parseArgs(argv) { - const args = { +interface Args { + file: string + dryRun: boolean + sameMajor: boolean + registry: string + tag: string + concurrency: number +} + +interface SemverParsed { + major: number + minor: number + patch: number + pre: string +} + +interface PackageUpdate { + name: string + from: string + to: string + base: string | null + target: string +} + +interface PackageMeta { + 'dist-tags'?: Record + versions?: Record +} + +interface YAMLCatalogNode { + items?: Array<{ key: { value: string }; value: { value: string } }> + set?: (name: string, value: string) => void +} + +function parseArgs(argv: string[]): Args { + const args: Args = { file: 'pnpm-workspace.yaml', dryRun: false, sameMajor: false, @@ -24,7 +59,7 @@ function parseArgs(argv) { tag: 'latest', concurrency: 8, } - const positional = [] + const positional: string[] = [] for (let i = 2; i < argv.length; i++) { const a = argv[i] if (a === '--dry-run') { @@ -32,9 +67,9 @@ function parseArgs(argv) { } else if (a === '--same-major') { args.sameMajor = true } else if (a === '--registry') { - args.registry = argv[++i] + args.registry = argv[++i]! } else if (a === '--tag') { - args.tag = argv[++i] + args.tag = argv[++i]! } else if (a === '--concurrency') { args.concurrency = Number(argv[++i]) || args.concurrency } else if (!a.startsWith('-')) { @@ -47,19 +82,24 @@ function parseArgs(argv) { return args } -function loadYamlDocument(file) { +function loadYamlDocument(file: string): Document.Parsed { const text = readFileSync(file, 'utf8') return YAML.parseDocument(text) } -function getCatalog(doc) { +function getCatalog(doc: Document.Parsed): YAMLCatalogNode | null { if (!doc.has('catalog')) { return null } - return doc.get('catalog') + return doc.get('catalog') as YAMLCatalogNode } -function setCatalogEntry(doc, catalogNode, name, value) { +function setCatalogEntry( + doc: Document.Parsed, + catalogNode: YAMLCatalogNode | null, + name: string, + value: string +): void { if (catalogNode && catalogNode.set) { catalogNode.set(name, value) } else { @@ -67,19 +107,19 @@ function setCatalogEntry(doc, catalogNode, name, value) { } } -function extractPrefix(spec) { +function extractPrefix(spec: string): string { // Preserve ^ or ~ if present; otherwise empty (exact) const m = spec.match(/^\s*([\^~])/) return m ? m[1] : '' } -function extractBaseVersion(spec) { +function extractBaseVersion(spec: string): string | null { // Pull the first x.y.z-like token; best-effort const m = spec.match(/(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/) return m ? m[1] : null } -function parseSemver(v) { +function parseSemver(v: string): SemverParsed | null { // Naive semver parser: returns {major, minor, patch, pre} or null if (!v) { return null @@ -91,7 +131,7 @@ function parseSemver(v) { return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] || '' } } -function cmpSemver(a, b) { +function cmpSemver(a: SemverParsed | null, b: SemverParsed | null): number { // Compare only numeric parts; treats prereleases as smaller than stable if (!a || !b) { return 0 @@ -111,32 +151,32 @@ function cmpSemver(a, b) { return aPre - bPre } -function pickHighest(versions) { +function pickHighest(versions: string[]): string | null { // Choose highest stable; if none, highest including prerelease const parsed = versions.map((v) => ({ v, p: parseSemver(v) })).filter((x) => x.p) if (parsed.length === 0) { return null } - const stable = parsed.filter((x) => !x.p.pre) + const stable = parsed.filter((x) => !x.p!.pre) const set = (stable.length ? stable : parsed).sort((x, y) => cmpSemver(x.p, y.p)) - return set[set.length - 1].v + return set[set.length - 1]!.v } -async function fetchPackageMeta(registry, pkg) { +async function fetchPackageMeta(registry: string, pkg: string): Promise { const url = `${registry.replace(/\/+$/, '')}/${encodeURIComponent(pkg)}` const res = await fetch(url, { redirect: 'follow' }) if (!res.ok) { throw new Error(`HTTP ${res.status} ${res.statusText}`) } - return res.json() + return res.json() as Promise } -function shouldSkipSpec(spec) { +function shouldSkipSpec(spec: string): boolean { // Skip non-registry specs return /^(workspace:|file:|link:|git\+|github:|bitbucket:|gitlab:)/.test(spec) } -function sameMajorTarget(base, allVersions) { +function sameMajorTarget(base: string, allVersions: string[]): string | null { const baseParsed = parseSemver(base) if (!baseParsed) { return null @@ -148,7 +188,7 @@ function sameMajorTarget(base, allVersions) { return pickHighest(same) } -async function main() { +async function main(): Promise { const args = parseArgs(process.argv) const doc = loadYamlDocument(args.file) const catalog = getCatalog(doc) @@ -157,20 +197,20 @@ async function main() { console.error('No "catalog" section found in the workspace file.') process.exit(1) } - const entries = catalog.items + const entries: Array<[string, string]> = catalog.items ? catalog.items.map((i) => [i.key.value, i.value.value]) : Object.entries(catalog) console.log( `Checking ${entries.length} catalog packages against ${args.registry} (tag: ${args.tag})...` ) - const updates = [] + const updates: PackageUpdate[] = [] let i = 0 - async function worker() { + async function worker(): Promise { while (i < entries.length) { const idx = i++ - const [name, specRaw] = entries[idx] + const [name, specRaw] = entries[idx]! const spec = String(specRaw).trim() if (shouldSkipSpec(spec)) { @@ -218,7 +258,8 @@ async function main() { }) } } catch (e) { - console.error(`Error fetching ${name}: ${e.message}`) + const error = e as Error + console.error(`Error fetching ${name}: ${error.message}`) } } } @@ -253,7 +294,7 @@ async function main() { console.log('\nPlease run `pnpm install` to update the lockfile.\n') } -main().catch((err) => { +main().catch((err: Error) => { console.error(err) process.exit(1) }) From 456f849aad0951e7500755af184d965544468345 Mon Sep 17 00:00:00 2001 From: Ismail Sunni Date: Fri, 17 Oct 2025 14:43:47 +0700 Subject: [PATCH 2/5] PB-1383: Make check external layer providers works again. --- .../check-external-layers-providers.js | 16 ++--- .../advancedTools/ImportCatalogue/utils.js | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js diff --git a/packages/viewer/scripts/check-external-layers-providers.js b/packages/viewer/scripts/check-external-layers-providers.js index 527ec3ae04..b816aa38f1 100644 --- a/packages/viewer/scripts/check-external-layers-providers.js +++ b/packages/viewer/scripts/check-external-layers-providers.js @@ -1,5 +1,5 @@ #!./node_modules/.bin/vite-node --script - + import { JSDOM } from 'jsdom' @@ -9,6 +9,12 @@ global.DOMParser = dom.window.DOMParser global.Node = dom.window.Node import { LV95 } from '@swissgeo/coordinates' +import { + EXTERNAL_SERVER_TIMEOUT, + parseWmsCapabilities, + parseWmtsCapabilities, + setWmsGetMapParams, +} from '@swissgeo/layers/api' import axios, { AxiosError } from 'axios' import axiosRetry from 'axios-retry' import { promises as fs } from 'fs' @@ -18,12 +24,6 @@ import writeYamlFile from 'write-yaml-file' import yargs from 'yargs' import { hideBin } from 'yargs/helpers' -import { - EXTERNAL_SERVER_TIMEOUT, - parseWmsCapabilities, - parseWmtsCapabilities, - setWmsGetMapParams, -} from '@/api/layers/layers-external.api' import { guessExternalLayerUrl, isWmsGetCap, @@ -252,7 +252,7 @@ function replaceUrlPlaceholders(urlTemplate, params) { }, {}) return urlTemplate.replace(/{(\w+)}/g, (_, key) => { const lowerKey = key.toLowerCase() - + if (normalizedParams.hasOwnProperty(lowerKey)) { return normalizedParams[lowerKey] } else { diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js b/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js new file mode 100644 index 0000000000..767a3e77e5 --- /dev/null +++ b/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js @@ -0,0 +1,59 @@ +import { setWmsGetCapabilitiesParams, setWmtsGetCapParams } from '@swissgeo/layers/api' + +/** + * Checks if file has WMS Capabilities XML content + * + * @param {string} fileContent + * @returns {boolean} + */ +export function isWmsGetCap(fileContent) { + return /<(WMT_MS_Capabilities|WMS_Capabilities)/.test(fileContent) +} + +/** + * Checks if file has WMTS Capabilities XML content + * + * @param {string} fileContent + * @returns {boolean} + */ +export function isWmtsGetCap(fileContent) { + return / Date: Fri, 17 Oct 2025 15:11:29 +0700 Subject: [PATCH 3/5] PB-1383: First try to migrate check-external-layers-providers. --- packages/viewer/package.json | 2 +- ....js => check-external-layers-providers.ts} | 158 +++++++++--------- 2 files changed, 80 insertions(+), 80 deletions(-) rename packages/viewer/scripts/{check-external-layers-providers.js => check-external-layers-providers.ts} (78%) diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 8d168d2073..806daa2898 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -8,7 +8,7 @@ "build:int": "pnpm run build --mode integration", "build:prod": "pnpm run build --mode production", "build:test": "pnpm run build --mode test", - "check:external": "pnpx vite-node scripts/check-external-layers-providers.js", + "check:external": "pnpx vite-node scripts/check-external-layers-providers.ts", "delete:reports": "rimraf tests/results/ || true", "delete:reports:unit": "rimraf tests/results/unit/ || true", "dev": "vite --port 8080 --host --cors", diff --git a/packages/viewer/scripts/check-external-layers-providers.js b/packages/viewer/scripts/check-external-layers-providers.ts similarity index 78% rename from packages/viewer/scripts/check-external-layers-providers.js rename to packages/viewer/scripts/check-external-layers-providers.ts index b816aa38f1..b5d480f621 100644 --- a/packages/viewer/scripts/check-external-layers-providers.js +++ b/packages/viewer/scripts/check-external-layers-providers.ts @@ -1,6 +1,5 @@ #!./node_modules/.bin/vite-node --script - import { JSDOM } from 'jsdom' // faking browser support, so that OpenLayers has what it requires to parse XMLs @@ -21,7 +20,11 @@ import { promises as fs } from 'fs' import { exit } from 'process' import sharp from 'sharp' import writeYamlFile from 'write-yaml-file' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import yargs from 'yargs' +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore import { hideBin } from 'yargs/helpers' import { @@ -31,6 +34,7 @@ import { isWmtsGetCap, isWmtsUrl, } from '@/modules/menu/components/advancedTools/ImportCatalogue/utils' + const SIZE_OF_CONTENT_DISPLAY = 150 const options = yargs(hideBin(process.argv)) @@ -56,25 +60,27 @@ const options = yargs(hideBin(process.argv)) .help('h') .alias('h', 'help').argv -function setupAxiosRetry() { +/* eslint-disable @typescript-eslint/no-explicit-any */ + +function setupAxiosRetry(): void { axiosRetry(axios, { retries: 8, // number of retries - retryDelay: (retryCount) => { + retryDelay: (retryCount: number): number => { console.log(`retry attempt: ${retryCount}`) return retryCount * 2000 // time interval between retries }, - retryCondition: (error) => { + retryCondition: (error: any): boolean => { // if retry condition is not specified, by default idempotent requests are retried return !error?.response || error?.response?.status >= 500 }, }) } -function compareResultByProvider(a, b) { +function compareResultByProvider(a: any, b: any): number { return compareCaseInsensitive(a['provider'], b['provider']) } -function compareCaseInsensitive(a, b) { +function compareCaseInsensitive(a: string, b: string): number { if (a.toLowerCase() > b.toLowerCase()) { return 1 } else if (a.toLowerCase() < b.toLowerCase()) { @@ -88,7 +94,7 @@ const requestHeaders = { 'Sec-Fetch-Site': 'cross-site', } -async function checkProviderGetMapTile(provider, capabilitiesResponse, result) { +async function checkProviderGetMapTile(provider: string, capabilitiesResponse: any, result: any): Promise { const content = capabilitiesResponse.data let isProviderMapTileValid = true if (isWmsGetCap(content)) { @@ -100,9 +106,9 @@ async function checkProviderGetMapTile(provider, capabilitiesResponse, result) { return isProviderMapTileValid } -async function handleWms(provider, content, result) { +async function handleWms(provider: string, content: string, result: any): Promise { let isProviderMapValid = true - const capabilities = parseWmsCapabilities(content, provider) + const capabilities = parseWmsCapabilities(content) as any const layers = capabilities.getAllExternalLayerObjects( LV95, 1, // opacity @@ -119,10 +125,10 @@ async function handleWms(provider, content, result) { // If the GetMap URL is the same as the GetCapabilities URL, we skip it because it's already checked const getMapUrls = capabilities.Capability.Request.GetMap.DCPType.map( - (d) => d.HTTP.Get.OnlineResource + (d: any) => d.HTTP.Get.OnlineResource ) .filter(Boolean) - .filter((url) => getCapabilitiesUrl !== url) + .filter((url: string) => getCapabilitiesUrl !== url) for (const getMapUrl of getMapUrls) { const url = setWmsGetMapParams(new URL(getMapUrl), firstLeaf.id, crs, style).toString() @@ -141,16 +147,16 @@ async function handleWms(provider, content, result) { )) } catch (error) { isProviderMapValid = false - result.invalid_wms.push(createErrorEntry(provider, url, error, content)) + result.invalid_wms.push(createErrorEntry(provider, url, error as Error, content)) } } return isProviderMapValid } -async function handleWmts(provider, content, result) { +async function handleWmts(provider: string, content: string, result: any): Promise { let isProviderGetTileValid = true - const capabilities = parseWmtsCapabilities(content, provider) + const capabilities = parseWmtsCapabilities(content, new URL(provider)) as any const layers = capabilities.getAllExternalLayerObjects( LV95, 1, // opacity @@ -175,46 +181,48 @@ async function handleWmts(provider, content, result) { // Find the default value for each dimension const placeHolderParams = - exampleLayer[dimensionKey]?.reduce((acc, curr) => { + dimensionKey && exampleLayer[dimensionKey]?.reduce((acc: any, curr: any) => { // Find the key for the default value const defaultKey = Object.keys(curr).find((key) => key.toLowerCase() === 'default') // Find the key for the identifier const identifierKey = Object.keys(curr).find( (key) => key.toLowerCase() === 'identifier' || key.toLowerCase() === 'id' ) - if (curr[defaultKey]) { + if (defaultKey && identifierKey && curr[defaultKey]) { acc[curr[identifierKey]] = curr[defaultKey] } return acc }, params) || params - let getTileUrl + let getTileUrl: string | undefined try { getTileUrl = replaceUrlPlaceholders(getTileUrlWithPlaceholders, placeHolderParams) - const { response, redirectHeaders } = await fetchMapTile(getTileUrl, provider) - isProviderGetTileValid = await checkProviderResponse( - provider, - getTileUrl, - response, - result, - redirectHeaders, - checkProviderResponseContentGetMap - ) + if (getTileUrl) { + const { response, redirectHeaders } = await fetchMapTile(getTileUrl, provider) + isProviderGetTileValid = await checkProviderResponse( + provider, + getTileUrl, + response, + result, + redirectHeaders, + checkProviderResponseContentGetMap + ) + } } catch (error) { isProviderGetTileValid = false - result.invalid_wmts.push(createErrorEntry(provider, getTileUrl, error, content)) + result.invalid_wmts.push(createErrorEntry(provider, getTileUrl || '', error as Error, content)) } return isProviderGetTileValid } -async function fetchMapTile(url, provider) { - let redirectHeaders = [] +async function fetchMapTile(url: string, provider: string): Promise { + const redirectHeaders: any[] = [] const response = await axios.get(url, { headers: requestHeaders, timeout: EXTERNAL_SERVER_TIMEOUT, responseType: 'arraybuffer', - beforeRedirect: (options_, response) => { + beforeRedirect: (options_: any, response: any) => { redirectHeaders.push(response.headers) }, }) @@ -225,10 +233,10 @@ async function fetchMapTile(url, provider) { ) } - return { response, redirectHeaders } + return { response, responseGetMap: response, redirectHeaders } } -function createErrorEntry(provider, url, error, content) { +function createErrorEntry(provider: string, url: string, error: Error, content: string): any { return { provider, url, @@ -239,18 +247,13 @@ function createErrorEntry(provider, url, error, content) { /** * Replace placeholders in a URL template with values from a params object - * - * @param {string} urlTemplate URL template with placeholders - * @param {Object} params Object with placeholder values - * @returns {string} URL with placeholders replaced - * @throws {Error} If a placeholder is missing in the params object */ -function replaceUrlPlaceholders(urlTemplate, params) { - const normalizedParams = Object.entries(params).reduce((acc, [key, _]) => { +function replaceUrlPlaceholders(urlTemplate: string, params: any): string { + const normalizedParams = Object.entries(params).reduce((acc: any, [key, _]) => { acc[key.toLowerCase()] = params[key] return acc }, {}) - return urlTemplate.replace(/{(\w+)}/g, (_, key) => { + return urlTemplate.replace(/{(\w+)}/g, (_: any, key: string) => { const lowerKey = key.toLowerCase() if (normalizedParams.hasOwnProperty(lowerKey)) { @@ -263,11 +266,8 @@ function replaceUrlPlaceholders(urlTemplate, params) { /** * Find the first leaf layer in a layer tree - * - * @param {Array} layers Array of layers - * @returns {Object} First leaf layer */ -function findFirstLeaf(layers) { +function findFirstLeaf(layers: any[]): any { if (!layers || layers.length === 0) { return null } @@ -285,21 +285,21 @@ function findFirstLeaf(layers) { return null } -async function checkProvider(provider, result) { +async function checkProvider(provider: string, result: any): Promise { const url = guessExternalLayerUrl(provider, 'en').toString() - let capabilitiesResponse - const redirectHeaders = [] + let capabilitiesResponse: any + const redirectHeaders: any[] = [] try { capabilitiesResponse = await axios.get(url, { headers: requestHeaders, - beforeRedirect: (options_, response) => { + beforeRedirect: (options_: any, response: any) => { redirectHeaders.push(response.headers) }, timeout: EXTERNAL_SERVER_TIMEOUT, }) } catch (error) { if (error instanceof AxiosError) { - console.error(`Provider ${provider} is not accessible: ${error}`) + console.error(`Provider ${provider} is not accessible: ${error.message}`) result.invalid_providers.push({ provider, url, @@ -332,13 +332,13 @@ async function checkProvider(provider, result) { } async function checkProviderResponse( - provider, - url, - response, - result, - redirectHeaders, - checkContentFnc -) { + provider: string, + url: string, + response: any, + result: any, + redirectHeaders: any[], + checkContentFnc: any +): Promise { if (![200, 201].includes(response.status)) { console.error(`Provider ${provider} is not valid: status=${response.status}`) result.invalid_providers.push({ @@ -346,7 +346,7 @@ async function checkProviderResponse( url: url, status: response.status, }) - } else if (!response.headers.has('access-control-allow-origin')) { + } else if (!response.headers.hasOwnProperty('access-control-allow-origin')) { console.error( `Provider ${provider} does not support CORS: status=${response.status}, ` + `missing access-control-allow-origin header` @@ -355,7 +355,7 @@ async function checkProviderResponse( provider: provider, url: url, status: response.status, - headers: response.headers.toJSON(), + headers: response.headers, }) } else if (redirectHeaders.some((header) => !header['access-control-allow-origin'])) { console.error( @@ -371,19 +371,19 @@ async function checkProviderResponse( }) } else if ( !['*', 'https://map.geo.admin.ch'].includes( - response.headers.get('access-control-allow-origin').toString()?.trim() + response.headers['access-control-allow-origin']?.toString()?.trim() ) ) { console.error( `Provider ${provider} does not have geoadmin in its CORS: status=${ response.status - }, Access-Control-Allow-Origin=${response.headers.get('access-control-allow-origin')}` + }, Access-Control-Allow-Origin=${response.headers['access-control-allow-origin']}` ) result.invalid_cors.push({ provider: provider, url: url, status: response.status, - headers: response.headers.toJSON(), + headers: response.headers, }) } else if (await checkContentFnc(provider, url, response, result)) { console.log(`Provider ${provider} is OK`) @@ -392,15 +392,15 @@ async function checkProviderResponse( return false } -function checkProviderResponseContent(provider, url, response, result) { +function checkProviderResponseContent(provider: string, url: string, response: any, result: any): boolean { const content = response.data - const parseCapabilities = (isCapFn, parseFn, type, result, resultArray) => { + const parseCapabilities = (isCapFn: any, parseFn: any, type: string, result: any, resultArray: string): boolean => { if (!isCapFn(content)) { return false } try { - const capabilities = parseFn(content, url) + const capabilities = parseFn(content, new URL(url)) const layers = capabilities.getAllExternalLayerObjects( LV95, 1, // opacity @@ -412,11 +412,11 @@ function checkProviderResponseContent(provider, url, response, result) { throw new Error(`No valid ${type} layers found`) } } catch (error) { - console.error(`Invalid provider ${provider}, ${type} get Cap parsing failed: ${error}`) + console.error(`Invalid provider ${provider}, ${type} get Cap parsing failed: ${String(error)}`) result[resultArray].push({ provider, url, - error: `${error}`, + error: `${String(error)}`, content: content.slice(0, SIZE_OF_CONTENT_DISPLAY), }) return false @@ -434,12 +434,13 @@ function checkProviderResponseContent(provider, url, response, result) { result.invalid_content.push({ provider, url, + error: 'file type not recognized', content: content.slice(0, SIZE_OF_CONTENT_DISPLAY), }) return false } -async function checkProviderResponseContentGetMap(provider, url, response, result) { +async function checkProviderResponseContentGetMap(provider: string, url: string, response: any, result: any): Promise { const content = Buffer.from(response.data) let isValid = false try { @@ -449,21 +450,21 @@ async function checkProviderResponseContentGetMap(provider, url, response, resul } catch (error) { if (isWmsUrl(url)) { console.error( - `Invalid provider ${provider}, WMS get Map content parsing failed: ${error}` + `Invalid provider ${provider}, WMS get Map content parsing failed: ${String(error)}` ) result.invalid_wms.push({ provider: provider, url: url, - error: `${error}`, + error: `${String(error)}`, }) } else if (isWmtsUrl(url)) { console.error( - `Invalid provider ${provider}, WMTS get Tiles content parsing failed: ${error}` + `Invalid provider ${provider}, WMTS get Tiles content parsing failed: ${String(error)}` ) result.invalid_wmts.push({ provider: provider, url: url, - error: `${error}`, + error: `${String(error)}`, }) } } @@ -471,11 +472,11 @@ async function checkProviderResponseContentGetMap(provider, url, response, resul return isValid } -async function checkProviders(providers, result) { +async function checkProviders(providers: string[], result: any): Promise { return Promise.all(providers.map(async (provider) => checkProvider(provider, result))) } -async function writeResult(result) { +async function writeResult(result: any): Promise { const resultsFolder = 'scripts/check-layer-providers-results' let prefix = '' if (options.datetime) { @@ -513,7 +514,7 @@ async function writeResult(result) { ]) } -async function main() { +async function main(): Promise { const result = { valid_providers: [], invalid_providers: [], @@ -524,11 +525,11 @@ async function main() { } setupAxiosRetry() - let providers = [] + let providers: string[] = [] if (options.url) { providers = [options.url] } else { - providers = JSON.parse(await fs.readFile(options.input, { encoding: 'utf-8' })) + providers = JSON.parse(await fs.readFile(options.input, { encoding: 'utf-8' })) as string[] } await checkProviders(providers, result) @@ -541,9 +542,8 @@ async function main() { } where invalids and ${result.invalid_cors.length} don't support CORS` ) } catch (error) { - console.error(`Failed to write results: ${error}`) + console.error(`Failed to write results: ${String(error)}`) } - return } -main().then(() => exit()) +void main().then(() => exit()) \ No newline at end of file From a359c0e29f2b52c9ff6b23c3cf319feb42b394c2 Mon Sep 17 00:00:00 2001 From: Ismail Sunni Date: Fri, 17 Oct 2025 23:27:38 +0700 Subject: [PATCH 4/5] PB-1383: Work partially for check external layers providers. --- .../check-external-layers-providers.ts | 71 +++++++++---------- .../advancedTools/ImportCatalogue/utils.js | 59 --------------- .../advancedTools/ImportCatalogue/utils.ts | 25 ++----- 3 files changed, 37 insertions(+), 118 deletions(-) delete mode 100644 packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js diff --git a/packages/viewer/scripts/check-external-layers-providers.ts b/packages/viewer/scripts/check-external-layers-providers.ts index b5d480f621..59a8305bbd 100644 --- a/packages/viewer/scripts/check-external-layers-providers.ts +++ b/packages/viewer/scripts/check-external-layers-providers.ts @@ -1,5 +1,6 @@ #!./node_modules/.bin/vite-node --script + import { JSDOM } from 'jsdom' // faking browser support, so that OpenLayers has what it requires to parse XMLs @@ -10,10 +11,9 @@ global.Node = dom.window.Node import { LV95 } from '@swissgeo/coordinates' import { EXTERNAL_SERVER_TIMEOUT, - parseWmsCapabilities, - parseWmtsCapabilities, setWmsGetMapParams, } from '@swissgeo/layers/api' +import { wmsCapabilitiesParser, wmtsCapabilitiesParser } from '@swissgeo/layers/parsers' import axios, { AxiosError } from 'axios' import axiosRetry from 'axios-retry' import { promises as fs } from 'fs' @@ -108,18 +108,16 @@ async function checkProviderGetMapTile(provider: string, capabilitiesResponse: a async function handleWms(provider: string, content: string, result: any): Promise { let isProviderMapValid = true - const capabilities = parseWmsCapabilities(content) as any - const layers = capabilities.getAllExternalLayerObjects( - LV95, - 1, // opacity - true, // visible - false // throw error in case of an error - ) + const capabilities = wmsCapabilitiesParser.parse(content, new URL(provider)) as any + const layers = wmsCapabilitiesParser.getAllExternalLayers(capabilities, { + outputProjection: LV95, + initialValues: { opacity: 1, isVisible: true }, + }) const firstLeaf = findFirstLeaf(layers) - const finding = capabilities.findLayer(firstLeaf.id) + const capabilitiesLayer = wmsCapabilitiesParser.getCapabilitiesLayer(capabilities, firstLeaf.id) const crs = capabilities.Capability.Layer.CRS[0] - const style = finding.layer?.Style ? finding.layer?.Style[0]?.Name : 'default' + const style = capabilitiesLayer?.Style ? capabilitiesLayer?.Style[0]?.Identifier : 'default' const getCapabilitiesUrl = capabilities.Capability.Request.GetCapabilities.DCPType[0].HTTP.Get.OnlineResource @@ -131,16 +129,16 @@ async function handleWms(provider: string, content: string, result: any): Promis .filter((url: string) => getCapabilitiesUrl !== url) for (const getMapUrl of getMapUrls) { - const url = setWmsGetMapParams(new URL(getMapUrl), firstLeaf.id, crs, style).toString() + const url = setWmsGetMapParams(new URL(getMapUrl), firstLeaf.id, crs, style!).toString() try { - const { responseGetMap, redirectHeaders } = await fetchMapTile(url, provider) + const { response, redirectHeaders } = await fetchMapTile(url, provider) isProviderMapValid = isProviderMapValid && (await checkProviderResponse( provider, url, - responseGetMap, + response, result, redirectHeaders, checkProviderResponseContentGetMap @@ -156,13 +154,11 @@ async function handleWms(provider: string, content: string, result: any): Promis async function handleWmts(provider: string, content: string, result: any): Promise { let isProviderGetTileValid = true - const capabilities = parseWmtsCapabilities(content, new URL(provider)) as any - const layers = capabilities.getAllExternalLayerObjects( - LV95, - 1, // opacity - true, // visible - false // throw error in case of an error - ) + const capabilities = wmtsCapabilitiesParser.parse(content, new URL(provider)) as any + const layers = wmtsCapabilitiesParser.getAllExternalLayers(capabilities, { + outputProjection: LV95, + initialValues: { opacity: 1, isVisible: true }, + }) const exampleLayer = findFirstLeaf(layers) const getTileUrlWithPlaceholders = exampleLayer.urlTemplate @@ -233,7 +229,7 @@ async function fetchMapTile(url: string, provider: string): Promise { ) } - return { response, responseGetMap: response, redirectHeaders } + return { response, redirectHeaders } } function createErrorEntry(provider: string, url: string, error: Error, content: string): any { @@ -346,7 +342,7 @@ async function checkProviderResponse( url: url, status: response.status, }) - } else if (!response.headers.hasOwnProperty('access-control-allow-origin')) { + } else if (!response.headers.has('access-control-allow-origin')) { console.error( `Provider ${provider} does not support CORS: status=${response.status}, ` + `missing access-control-allow-origin header` @@ -355,7 +351,7 @@ async function checkProviderResponse( provider: provider, url: url, status: response.status, - headers: response.headers, + headers: response.headers.toJSON(), }) } else if (redirectHeaders.some((header) => !header['access-control-allow-origin'])) { console.error( @@ -371,19 +367,19 @@ async function checkProviderResponse( }) } else if ( !['*', 'https://map.geo.admin.ch'].includes( - response.headers['access-control-allow-origin']?.toString()?.trim() + response.headers.get('access-control-allow-origin').toString()?.trim() ) ) { console.error( `Provider ${provider} does not have geoadmin in its CORS: status=${ response.status - }, Access-Control-Allow-Origin=${response.headers['access-control-allow-origin']}` + }, Access-Control-Allow-Origin=${response.headers.get('access-control-allow-origin')}` ) result.invalid_cors.push({ provider: provider, url: url, status: response.status, - headers: response.headers, + headers: response.headers.toJSON(), }) } else if (await checkContentFnc(provider, url, response, result)) { console.log(`Provider ${provider} is OK`) @@ -394,19 +390,17 @@ async function checkProviderResponse( function checkProviderResponseContent(provider: string, url: string, response: any, result: any): boolean { const content = response.data - const parseCapabilities = (isCapFn: any, parseFn: any, type: string, result: any, resultArray: string): boolean => { + const parseCapabilities = (isCapFn: any, parser: any, type: string, result: any, resultArray: string): boolean => { if (!isCapFn(content)) { return false } try { - const capabilities = parseFn(content, new URL(url)) - const layers = capabilities.getAllExternalLayerObjects( - LV95, - 1, // opacity - true, // visible - false // throw Error in case of error - ) + const capabilities = parser.parse(content, new URL(url)) + const layers = parser.getAllExternalLayers(capabilities, { + outputProjection: LV95, + initialValues: { opacity: 1, isVisible: true }, + }) if (layers.length === 0) { throw new Error(`No valid ${type} layers found`) @@ -424,8 +418,8 @@ function checkProviderResponseContent(provider: string, url: string, response: a return true } if ( - parseCapabilities(isWmsGetCap, parseWmsCapabilities, 'WMS', result, 'invalid_wms') || - parseCapabilities(isWmtsGetCap, parseWmtsCapabilities, 'WMTS', result, 'invalid_wmts') + parseCapabilities(isWmsGetCap, wmsCapabilitiesParser, 'WMS', result, 'invalid_wms') || + parseCapabilities(isWmtsGetCap, wmtsCapabilitiesParser, 'WMTS', result, 'invalid_wmts') ) { return true } @@ -434,7 +428,6 @@ function checkProviderResponseContent(provider: string, url: string, response: a result.invalid_content.push({ provider, url, - error: 'file type not recognized', content: content.slice(0, SIZE_OF_CONTENT_DISPLAY), }) return false @@ -546,4 +539,4 @@ async function main(): Promise { } } -void main().then(() => exit()) \ No newline at end of file +void main().then(() => exit()) diff --git a/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js b/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js deleted file mode 100644 index 767a3e77e5..0000000000 --- a/packages/viewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js +++ /dev/null @@ -1,59 +0,0 @@ -import { setWmsGetCapabilitiesParams, setWmtsGetCapParams } from '@swissgeo/layers/api' - -/** - * Checks if file has WMS Capabilities XML content - * - * @param {string} fileContent - * @returns {boolean} - */ -export function isWmsGetCap(fileContent) { - return /<(WMT_MS_Capabilities|WMS_Capabilities)/.test(fileContent) -} - -/** - * Checks if file has WMTS Capabilities XML content - * - * @param {string} fileContent - * @returns {boolean} - */ -export function isWmtsGetCap(fileContent) { - return / Date: Sun, 26 Oct 2025 21:44:37 +0100 Subject: [PATCH 5/5] PB-1383: translation script migration - Migrated the translation script - Also started reworking the external provider script by scrapping as many 'any' as possible. PB 1383: external provider scripts transferred - removed all 'any' that still were there, and added the correct types - hopefully the APIs are correctly called - imported types for the javascript libraries --- packages/viewer/package.json | 2 + .../check-external-layers-providers.ts | 127 ++++++++++++------ ...e-i18n-files.js => generate-i18n-files.ts} | 38 ++++-- pnpm-lock.yaml | 26 +++- pnpm-workspace.yaml | 2 +- 5 files changed, 137 insertions(+), 58 deletions(-) rename packages/viewer/scripts/{generate-i18n-files.js => generate-i18n-files.ts} (67%) diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 806daa2898..887e21fab0 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -114,11 +114,13 @@ "@tailwindcss/vite": "catalog:", "@types/bootstrap": "catalog:", "@types/geojson": "catalog:", + "@types/jsdom": "catalog:", "@types/lodash": "catalog:", "@types/luxon": "catalog:", "@types/node": "catalog:", "@types/pako": "catalog:", "@types/sortablejs": "^1.15.8", + "@types/yargs": "^17.0.34", "@vite-pwa/assets-generator": "catalog:", "@vitejs/plugin-basic-ssl": "catalog:", "@vitejs/plugin-vue": "catalog:", diff --git a/packages/viewer/scripts/check-external-layers-providers.ts b/packages/viewer/scripts/check-external-layers-providers.ts index 59a8305bbd..c410ebf0e9 100644 --- a/packages/viewer/scripts/check-external-layers-providers.ts +++ b/packages/viewer/scripts/check-external-layers-providers.ts @@ -13,18 +13,14 @@ import { EXTERNAL_SERVER_TIMEOUT, setWmsGetMapParams, } from '@swissgeo/layers/api' -import { wmsCapabilitiesParser, wmtsCapabilitiesParser } from '@swissgeo/layers/parsers' -import axios, { AxiosError } from 'axios' +import { wmsCapabilitiesParser, wmtsCapabilitiesParser, type WMSCapabilitiesResponse, type WMTSCapabilitiesResponse } from '@swissgeo/layers/parsers' +import axios, { AxiosError, type AxiosResponse } from 'axios' import axiosRetry from 'axios-retry' import { promises as fs } from 'fs' import { exit } from 'process' import sharp from 'sharp' import writeYamlFile from 'write-yaml-file' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import yargs from 'yargs' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import { hideBin } from 'yargs/helpers' import { @@ -34,9 +30,46 @@ import { isWmtsGetCap, isWmtsUrl, } from '@/modules/menu/components/advancedTools/ImportCatalogue/utils' +import path from 'path' + +// constants const SIZE_OF_CONTENT_DISPLAY = 150 +// types and interfaces + +interface ProviderObject { + provider: string, + url: string, + status?: number | undefined, + error?: string, + headers?: Record[] + content? : string +} + +type InvalidProviderArrayKeys = 'invalid_providers' | 'invalid_cors' | 'invalid_wms' | 'invalid_wmts' | 'invalid_content' +interface Result { + valid_providers: string[], + invalid_providers: ProviderObject[], + invalid_cors: ProviderObject[], + invalid_wms: ProviderObject[], + invalid_wmts: ProviderObject[], + invalid_content: ProviderObject[], +} + +interface HasProvider { + provider: string +} + +interface Options { + input?: string, + url?: string, + inplace?: boolean, + datetime?: boolean, + _: (string | number)[], + $0: string, +} + const options = yargs(hideBin(process.argv)) .usage('Usage: $0 [options]') .version('1.0.0') @@ -58,9 +91,7 @@ const options = yargs(hideBin(process.argv)) type: 'boolean', }) .help('h') - .alias('h', 'help').argv - -/* eslint-disable @typescript-eslint/no-explicit-any */ + .alias('h', 'help').argv as Options function setupAxiosRetry(): void { axiosRetry(axios, { @@ -69,15 +100,15 @@ function setupAxiosRetry(): void { console.log(`retry attempt: ${retryCount}`) return retryCount * 2000 // time interval between retries }, - retryCondition: (error: any): boolean => { + retryCondition: (error): boolean => { // if retry condition is not specified, by default idempotent requests are retried return !error?.response || error?.response?.status >= 500 }, }) } -function compareResultByProvider(a: any, b: any): number { - return compareCaseInsensitive(a['provider'], b['provider']) +function compareResultByProvider(a: HasProvider, b: HasProvider): number { + return compareCaseInsensitive(a.provider, b.provider) } function compareCaseInsensitive(a: string, b: string): number { @@ -94,7 +125,7 @@ const requestHeaders = { 'Sec-Fetch-Site': 'cross-site', } -async function checkProviderGetMapTile(provider: string, capabilitiesResponse: any, result: any): Promise { +async function checkProviderGetMapTile(provider: string, capabilitiesResponse: AxiosResponse, result: Result): Promise { const content = capabilitiesResponse.data let isProviderMapTileValid = true if (isWmsGetCap(content)) { @@ -106,9 +137,9 @@ async function checkProviderGetMapTile(provider: string, capabilitiesResponse: a return isProviderMapTileValid } -async function handleWms(provider: string, content: string, result: any): Promise { +async function handleWms(provider: string, content: string, result: Result): Promise { let isProviderMapValid = true - const capabilities = wmsCapabilitiesParser.parse(content, new URL(provider)) as any + const capabilities: WMSCapabilitiesResponse = wmsCapabilitiesParser.parse(content, new URL(provider)) const layers = wmsCapabilitiesParser.getAllExternalLayers(capabilities, { outputProjection: LV95, initialValues: { opacity: 1, isVisible: true }, @@ -116,20 +147,24 @@ async function handleWms(provider: string, content: string, result: any): Promis const firstLeaf = findFirstLeaf(layers) const capabilitiesLayer = wmsCapabilitiesParser.getCapabilitiesLayer(capabilities, firstLeaf.id) - const crs = capabilities.Capability.Layer.CRS[0] - const style = capabilitiesLayer?.Style ? capabilitiesLayer?.Style[0]?.Identifier : 'default' + const crs = capabilities?.Capability?.Layer?.CRS[0] + const style = capabilitiesLayer?.Style ? capabilitiesLayer.Style[0]?.Identifier : 'default' const getCapabilitiesUrl = - capabilities.Capability.Request.GetCapabilities.DCPType[0].HTTP.Get.OnlineResource + capabilities.Capability?.Request?.GetCapabilities?.DCPType[0]?.HTTP?.Get?.OnlineResource // If the GetMap URL is the same as the GetCapabilities URL, we skip it because it's already checked - const getMapUrls = capabilities.Capability.Request.GetMap.DCPType.map( - (d: any) => d.HTTP.Get.OnlineResource + const getMapUrls = capabilities.Capability?.Request.GetMap.DCPType.map( + (d) => d.HTTP.Get?.OnlineResource ) .filter(Boolean) - .filter((url: string) => getCapabilitiesUrl !== url) + .filter((url) => getCapabilitiesUrl !== url) + if (!getMapUrls || !crs) { + isProviderMapValid = false + return isProviderMapValid + } for (const getMapUrl of getMapUrls) { - const url = setWmsGetMapParams(new URL(getMapUrl), firstLeaf.id, crs, style!).toString() + const url = setWmsGetMapParams(new URL(`${getMapUrl}`), firstLeaf.id, crs, style!).toString() try { const { response, redirectHeaders } = await fetchMapTile(url, provider) @@ -152,9 +187,9 @@ async function handleWms(provider: string, content: string, result: any): Promis return isProviderMapValid } -async function handleWmts(provider: string, content: string, result: any): Promise { +async function handleWmts(provider: string, content: string, result: Result): Promise { let isProviderGetTileValid = true - const capabilities = wmtsCapabilitiesParser.parse(content, new URL(provider)) as any + const capabilities: WMTSCapabilitiesResponse = wmtsCapabilitiesParser.parse(content, new URL(provider)) const layers = wmtsCapabilitiesParser.getAllExternalLayers(capabilities, { outputProjection: LV95, initialValues: { opacity: 1, isVisible: true }, @@ -281,14 +316,14 @@ function findFirstLeaf(layers: any[]): any { return null } -async function checkProvider(provider: string, result: any): Promise { +async function checkProvider(provider: string, result: Result): Promise { const url = guessExternalLayerUrl(provider, 'en').toString() - let capabilitiesResponse: any - const redirectHeaders: any[] = [] + let capabilitiesResponse : AxiosResponse + const redirectHeaders: Record[] = [] try { - capabilitiesResponse = await axios.get(url, { + capabilitiesResponse = await axios.get(url, { // headers: requestHeaders, - beforeRedirect: (options_: any, response: any) => { + beforeRedirect: (options_, response) => { redirectHeaders.push(response.headers) }, timeout: EXTERNAL_SERVER_TIMEOUT, @@ -331,7 +366,7 @@ async function checkProviderResponse( provider: string, url: string, response: any, - result: any, + result: Result, redirectHeaders: any[], checkContentFnc: any ): Promise { @@ -388,15 +423,16 @@ async function checkProviderResponse( return false } -function checkProviderResponseContent(provider: string, url: string, response: any, result: any): boolean { +function checkProviderResponseContent(provider: string, url: string, response: any, result: Result): boolean { const content = response.data - const parseCapabilities = (isCapFn: any, parser: any, type: string, result: any, resultArray: string): boolean => { + // TODO HERE: Specify it's a function that takes one string and return a boolean + const parseCapabilities = (isCapFn: Function, parser: any, type: string, result: Result, resultArrayKey: InvalidProviderArrayKeys): boolean => { if (!isCapFn(content)) { return false } try { - const capabilities = parser.parse(content, new URL(url)) + const capabilities: WMTSCapabilitiesResponse | WMSCapabilitiesResponse = parser.parse(content, new URL(url)) const layers = parser.getAllExternalLayers(capabilities, { outputProjection: LV95, initialValues: { opacity: 1, isVisible: true }, @@ -407,7 +443,7 @@ function checkProviderResponseContent(provider: string, url: string, response: a } } catch (error) { console.error(`Invalid provider ${provider}, ${type} get Cap parsing failed: ${String(error)}`) - result[resultArray].push({ + result[resultArrayKey].push({ provider, url, error: `${String(error)}`, @@ -433,7 +469,7 @@ function checkProviderResponseContent(provider: string, url: string, response: a return false } -async function checkProviderResponseContentGetMap(provider: string, url: string, response: any, result: any): Promise { +async function checkProviderResponseContentGetMap(provider: string, url: string, response: any, result: Result): Promise { const content = Buffer.from(response.data) let isValid = false try { @@ -465,18 +501,18 @@ async function checkProviderResponseContentGetMap(provider: string, url: string, return isValid } -async function checkProviders(providers: string[], result: any): Promise { +async function checkProviders(providers: string[], result: Result): Promise { return Promise.all(providers.map(async (provider) => checkProvider(provider, result))) } -async function writeResult(result: any): Promise { +async function writeResult(result: Result): Promise { const resultsFolder = 'scripts/check-layer-providers-results' let prefix = '' if (options.datetime) { prefix = `${new Date().toISOString()}_` } let valid_providers_file = `${resultsFolder}/${prefix}valid_providers.json` - if (options.inplace) { + if (options.inplace && options.input) { valid_providers_file = options.input } return Promise.all([ @@ -508,7 +544,7 @@ async function writeResult(result: any): Promise { } async function main(): Promise { - const result = { + const result: Result = { valid_providers: [], invalid_providers: [], invalid_cors: [], @@ -519,10 +555,15 @@ async function main(): Promise { setupAxiosRetry() let providers: string[] = [] - if (options.url) { - providers = [options.url] - } else { - providers = JSON.parse(await fs.readFile(options.input, { encoding: 'utf-8' })) as string[] + const options_url = options.url + const options_input = options.input + if (options_url) { + providers = [options_url] + } else if (options_input){ + providers = JSON.parse(await fs.readFile(path.resolve(options_input), { encoding: 'utf-8' })) + } + else { + console.error(`No sources given for providers`) } await checkProviders(providers, result) diff --git a/packages/viewer/scripts/generate-i18n-files.js b/packages/viewer/scripts/generate-i18n-files.ts similarity index 67% rename from packages/viewer/scripts/generate-i18n-files.js rename to packages/viewer/scripts/generate-i18n-files.ts index 97456b607e..edefdbec01 100644 --- a/packages/viewer/scripts/generate-i18n-files.js +++ b/packages/viewer/scripts/generate-i18n-files.ts @@ -1,9 +1,10 @@ #!/usr/bin/env node - + import fs from 'fs' import { google } from 'googleapis' +type Lang = 'fr' | 'de' | 'en' | 'it' | 'rm' const googleApiKey = process.env.GOOGLE_API_KEY if (!googleApiKey) { @@ -11,6 +12,10 @@ if (!googleApiKey) { process.exit(1) } +function stringToLowerCaseLang(lang: string) : Lang { + return lang.toLowerCase() as Lang +} + // Reading translations from Google Spreadsheet https://docs.google.com/spreadsheets/d/1bRzdX2zwN2VG7LWEdlscrP-wGlp7O46nvrXkQNnFvVY/edit?usp=sharing const sheets = google.sheets({ version: 'v4', auth: googleApiKey }) sheets.spreadsheets.values.get( @@ -20,12 +25,19 @@ sheets.spreadsheets.values.get( }, (err, res) => { if (err) { - return console.log('The API returned an error: ' + err) + return console.log('The API returned an error: ' + err.toString()) } - const rows = res.data.values - if (rows.length) { - const translations = {} - const langByIndex = [] + const rows = res?.data.values + if (rows?.length) { + const translations: Record> = { + 'fr': {}, + 'de': {}, + 'it': {}, + 'en': {}, + 'rm': {} + } + // contains the keys to translations + const langByIndex: Lang[] = [] // creating a JSON structure with the Google spreadsheet content // structure of the JSON should be // { @@ -35,25 +47,25 @@ sheets.spreadsheets.values.get( // }, // "lang2_isoCode": { ... } // } - rows.forEach((row, rowIndex) => { + rows.forEach((row: string[], rowIndex) => { if (rowIndex === 0) { row.forEach((lang, langIndex) => { if (langIndex > 0) { - translations[lang.toLowerCase()] = {} - langByIndex[langIndex] = lang.toLowerCase() + langByIndex[langIndex] = stringToLowerCaseLang(lang) } }) } else { - langByIndex.forEach((lang, index) => { - if (index > 0) { + langByIndex.forEach((lang: Lang, index) => { + if (index > 0 && row[0]) { translations[lang][row[0]] = row[index] } }) } }) // ordering all keys alphabetically - Object.keys(translations).forEach((lang) => { - const translationForLang = translations[lang] + //@ts-expect-error we know the keys here are all Langs, but typescript doesn't like that + Object.keys(translations).forEach((lang: Lang) => { + const translationForLang: Record = translations[lang] translations[lang] = Object.keys(translationForLang) .sort() .reduce((acc, key) => ({ ...acc, [key]: translationForLang[key] }), {}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4bd32e2728..75f12864a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -91,7 +91,7 @@ catalogs: specifier: ^7946.0.16 version: 7946.0.16 '@types/jsdom': - specifier: ^27.0.0 + specifier: 27.0.0 version: 27.0.0 '@types/lodash': specifier: ^4.17.20 @@ -424,6 +424,12 @@ importers: .: devDependencies: + '@types/node': + specifier: 'catalog:' + version: 24.7.2 + tsx: + specifier: 'catalog:' + version: 4.20.6 yaml: specifier: 'catalog:' version: 2.8.1 @@ -1312,6 +1318,9 @@ importers: '@types/geojson': specifier: 'catalog:' version: 7946.0.16 + '@types/jsdom': + specifier: 'catalog:' + version: 27.0.0 '@types/lodash': specifier: 'catalog:' version: 4.17.20 @@ -1327,6 +1336,9 @@ importers: '@types/sortablejs': specifier: ^1.15.8 version: 1.15.8 + '@types/yargs': + specifier: ^17.0.34 + version: 17.0.34 '@vite-pwa/assets-generator': specifier: 'catalog:' version: 1.0.2 @@ -3683,6 +3695,12 @@ packages: '@types/web-bluetooth@0.0.21': resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.34': + resolution: {integrity: sha512-KExbHVa92aJpw9WDQvzBaGVE2/Pz+pLZQloT2hjL8IqsZnV62rlPOYvNnLmf/L2dyllfVUOVBj64M0z/46eR2A==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} @@ -11184,6 +11202,12 @@ snapshots: '@types/web-bluetooth@0.0.21': {} + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.34': + dependencies: + '@types/yargs-parser': 21.0.3 + '@types/yauzl@2.10.3': dependencies: '@types/node': 24.8.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b699eac5eb..557ad309b9 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -33,7 +33,7 @@ catalog: '@types/bootstrap': ^5.2.10 '@types/chai': ^5.2.2 '@types/geojson': ^7946.0.16 - '@types/jsdom': ^27.0.0 + '@types/jsdom': 27.0.0 '@types/lodash': ^4.17.20 '@types/luxon': ^3.7.1 '@types/node': ^24.8.1