Skip to content

Commit 55ef283

Browse files
feat: Migrate storage client modules to typescript (#532)
1 parent b5dd3c9 commit 55ef283

File tree

4 files changed

+305
-270
lines changed

4 files changed

+305
-270
lines changed
Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@ import path from 'path';
33
import {logger} from '@appium/support';
44
import * as semver from 'semver';
55
import {ARCH, CPU} from '../constants';
6+
import type {ChromedriverDetailsMapping} from '../types';
67

78
const log = logger.getLogger('ChromedriverChromelabsStorageClient');
89

910
/**
10-
* Parses The output of the corresponding JSON API
11-
* that retrieves Chromedriver versions. See
12-
* https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints
13-
* for more details.
11+
* Parses the output of the JSON API that retrieves Chromedriver versions
1412
*
15-
* @param {string} jsonStr
16-
* @returns {ChromedriverDetailsMapping}
13+
* See https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints for more details.
14+
*
15+
* @param jsonStr - The JSON string from the known-good-versions-with-downloads API
16+
* @returns A mapping of chromedriver entry keys to their details
17+
* @throws {Error} if the JSON cannot be parsed or has an unsupported format
1718
*/
18-
export function parseKnownGoodVersionsWithDownloadsJson(jsonStr) {
19-
let json;
19+
export function parseKnownGoodVersionsWithDownloadsJson(
20+
jsonStr: string
21+
): ChromedriverDetailsMapping {
22+
let json: KnownGoodVersionsJson;
2023
try {
2124
json = JSON.parse(jsonStr);
2225
} catch (e) {
23-
const err = /** @type {Error} */ (e);
26+
const err = e as Error;
2427
throw new Error(`Storage JSON cannot be parsed. Original error: ${err.message}`);
2528
}
2629
/**
@@ -60,8 +63,7 @@ export function parseKnownGoodVersionsWithDownloadsJson(jsonStr) {
6063
* "version":"113.0.5672.35",
6164
* ...
6265
*/
63-
/** @type {ChromedriverDetailsMapping} */
64-
const mapping = {};
66+
const mapping: ChromedriverDetailsMapping = {};
6567
if (!_.isArray(json?.versions)) {
6668
log.debug(jsonStr);
6769
throw new Error('The format of the storage JSON is not supported');
@@ -80,10 +82,13 @@ export function parseKnownGoodVersionsWithDownloadsJson(jsonStr) {
8082
}
8183
const osNameMatch = /^[a-z]+/i.exec(downloadEntry.platform);
8284
if (!osNameMatch) {
83-
log.debug(`The entry '${downloadEntry.url}' does not contain valid platform name. Skipping it`);
85+
log.debug(
86+
`The entry '${downloadEntry.url}' does not contain valid platform name. Skipping it`
87+
);
8488
continue;
8589
}
86-
const key = `${path.basename(path.dirname(path.dirname(downloadEntry.url)))}/` +
90+
const key =
91+
`${path.basename(path.dirname(path.dirname(downloadEntry.url)))}/` +
8792
`${path.basename(downloadEntry.url)}`;
8893
mapping[key] = {
8994
url: downloadEntry.url,
@@ -94,7 +99,7 @@ export function parseKnownGoodVersionsWithDownloadsJson(jsonStr) {
9499
name: osNameMatch[0],
95100
arch: downloadEntry.platform.includes(ARCH.X64) ? ARCH.X64 : ARCH.X86,
96101
cpu: downloadEntry.platform.includes(CPU.ARM) ? CPU.ARM : CPU.INTEL,
97-
}
102+
},
98103
};
99104
}
100105
}
@@ -103,20 +108,20 @@ export function parseKnownGoodVersionsWithDownloadsJson(jsonStr) {
103108
}
104109

105110
/**
106-
* Parses The output of the corresponding JSON API
107-
* that retrieves the most recent stable Chromedriver version. See
108-
* https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints
109-
* for more details.
111+
* Parses the output of the JSON API that retrieves the most recent stable Chromedriver version
112+
*
113+
* See https://github.com/GoogleChromeLabs/chrome-for-testing#json-api-endpoints for more details.
110114
*
111-
* @param {string} jsonStr
112-
* @returns {string} The most recent available chromedriver version
115+
* @param jsonStr - The JSON string from the last-known-good-versions API
116+
* @returns The most recent available chromedriver version string
117+
* @throws {Error} if the JSON cannot be parsed or has an unsupported format
113118
*/
114-
export function parseLatestKnownGoodVersionsJson(jsonStr) {
115-
let json;
119+
export function parseLatestKnownGoodVersionsJson(jsonStr: string): string {
120+
let json: LatestKnownGoodVersionsJson;
116121
try {
117122
json = JSON.parse(jsonStr);
118123
} catch (e) {
119-
const err = /** @type {Error} */ (e);
124+
const err = e as Error;
120125
throw new Error(`Storage JSON cannot be parsed. Original error: ${err.message}`);
121126
}
122127
/**
@@ -136,6 +141,29 @@ export function parseLatestKnownGoodVersionsJson(jsonStr) {
136141
return json.channels.Stable.version;
137142
}
138143

139-
/**
140-
* @typedef {import('../types').ChromedriverDetailsMapping} ChromedriverDetailsMapping
141-
*/
144+
interface VersionEntry {
145+
version: string;
146+
revision?: string;
147+
downloads?: {
148+
chromedriver?: Array<{
149+
platform: string;
150+
url: string;
151+
}>;
152+
};
153+
}
154+
155+
interface KnownGoodVersionsJson {
156+
timestamp?: string;
157+
versions?: VersionEntry[];
158+
}
159+
160+
interface LatestKnownGoodVersionsJson {
161+
timestamp?: string;
162+
channels?: {
163+
Stable?: {
164+
channel?: string;
165+
version?: string;
166+
revision?: string;
167+
};
168+
};
169+
}
Lines changed: 55 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@ import {
1212
} from '../constants';
1313
import {DOMParser} from '@xmldom/xmldom';
1414
import path from 'node:path';
15+
import type {
16+
AdditionalDriverDetails,
17+
ChromedriverDetails,
18+
ChromedriverDetailsMapping,
19+
} from '../types';
1520

1621
const log = logger.getLogger('ChromedriverGoogleapisStorageClient');
1722
const MAX_PARALLEL_DOWNLOADS = 5;
1823

1924
/**
25+
* Finds a child node in an XML node by name and/or text content
2026
*
21-
* @param {Node|Attr} parent
22-
* @param {string?} childName
23-
* @param {string?} text
24-
* @returns
27+
* @param parent - The parent XML node to search in
28+
* @param childName - Optional child node name to match
29+
* @param text - Optional text content to match
30+
* @returns The matching child node or null if not found
2531
*/
26-
export function findChildNode(parent, childName = null, text = null) {
32+
export function findChildNode(
33+
parent: Node | Attr,
34+
childName: string | null = null,
35+
text: string | null = null
36+
): Node | Attr | null {
2737
if (!childName && !text) {
2838
return null;
2939
}
@@ -32,7 +42,7 @@ export function findChildNode(parent, childName = null, text = null) {
3242
}
3343

3444
for (let childNodeIdx = 0; childNodeIdx < parent.childNodes.length; childNodeIdx++) {
35-
const childNode = /** @type {Element|Attr} */ (parent.childNodes[childNodeIdx]);
45+
const childNode = parent.childNodes[childNodeIdx] as Element | Attr;
3646
if (childName && !text && childName === childNode.localName) {
3747
return childNode;
3848
}
@@ -52,26 +62,15 @@ export function findChildNode(parent, childName = null, text = null) {
5262
return null;
5363
}
5464

55-
/**
56-
*
57-
* @param {Node?} node
58-
* @returns
59-
*/
60-
function extractNodeText(node) {
61-
return !node?.firstChild || !util.hasValue(node.firstChild.nodeValue)
62-
? null
63-
: node.firstChild.nodeValue;
64-
}
65-
6665
/**
6766
* Gets additional chromedriver details from chromedriver
6867
* release notes
6968
*
70-
* @param {string} content - Release notes of the corresponding chromedriver
71-
* @returns {import('../types').AdditionalDriverDetails}
69+
* @param content - Release notes of the corresponding chromedriver
70+
* @returns AdditionalDriverDetails
7271
*/
73-
export function parseNotes(content) {
74-
const result = {};
72+
export function parseNotes(content: string): AdditionalDriverDetails {
73+
const result: AdditionalDriverDetails = {};
7574
const versionMatch = /^\s*[-]+ChromeDriver[\D]+([\d.]+)/im.exec(content);
7675
if (versionMatch) {
7776
result.version = versionMatch[1];
@@ -87,27 +86,26 @@ export function parseNotes(content) {
8786
* Parses chromedriver storage XML and returns
8887
* the parsed results
8988
*
90-
* @param {string} xml - The chromedriver storage XML
91-
* @param {boolean} shouldParseNotes [true] - If set to `true`
89+
* @param xml - The chromedriver storage XML
90+
* @param shouldParseNotes [true] - If set to `true`
9291
* then additional drivers information is going to be parsed
9392
* and assigned to `this.mapping`
94-
* @returns {Promise<ChromedriverDetailsMapping>}
93+
* @returns Promise<ChromedriverDetailsMapping>
9594
*/
96-
export async function parseGoogleapiStorageXml(xml, shouldParseNotes = true) {
95+
export async function parseGoogleapiStorageXml(
96+
xml: string,
97+
shouldParseNotes = true
98+
): Promise<ChromedriverDetailsMapping> {
9799
const doc = new DOMParser().parseFromString(xml, 'text/xml');
98-
const driverNodes = /** @type {Array<Node|Attr>} */ (
99-
// https://github.com/xmldom/xmldom/issues/724
100-
xpathSelect(`//*[local-name(.)='Contents']`, doc)
101-
);
100+
const driverNodes = xpathSelect(`//*[local-name(.)='Contents']`, doc) as Array<Node | Attr>;
102101
log.debug(`Parsed ${driverNodes.length} entries from storage XML`);
103102
if (_.isEmpty(driverNodes)) {
104103
throw new Error('Cannot retrieve any valid Chromedriver entries from the storage config');
105104
}
106105

107-
const promises = [];
108-
const chunk = [];
109-
/** @type {ChromedriverDetailsMapping} */
110-
const mapping = {};
106+
const promises: Promise<void>[] = [];
107+
const chunk: Promise<void>[] = [];
108+
const mapping: ChromedriverDetailsMapping = {};
111109
for (const driverNode of driverNodes) {
112110
const k = extractNodeText(findChildNode(driverNode, 'Key'));
113111
if (!_.includes(k, '/chromedriver_')) {
@@ -128,17 +126,16 @@ export async function parseGoogleapiStorageXml(xml, shouldParseNotes = true) {
128126
continue;
129127
}
130128

131-
/** @type {ChromedriverDetails} */
132-
const cdInfo = {
129+
const cdInfo: ChromedriverDetails = {
133130
url: `${GOOGLEAPIS_CDN}/${key}`,
134131
etag: _.trim(etag, '"'),
135-
version: /** @type {string} */ (_.first(key.split('/'))),
132+
version: _.first(key.split('/')) as string,
136133
minBrowserVersion: null,
137134
os: {
138135
name: osNameMatch[1],
139136
arch: filename.includes(ARCH.X64) ? ARCH.X64 : ARCH.X86,
140137
cpu: APPLE_ARM_SUFFIXES.some((suffix) => filename.includes(suffix)) ? CPU.ARM : CPU.INTEL,
141-
}
138+
},
142139
};
143140
mapping[key] = cdInfo;
144141

@@ -157,31 +154,36 @@ export async function parseGoogleapiStorageXml(xml, shouldParseNotes = true) {
157154
continue;
158155
}
159156

160-
const promise = B.resolve(retrieveAdditionalDriverInfo(key, `${GOOGLEAPIS_CDN}/${notesPath}`, cdInfo));
157+
const promise = B.resolve(
158+
retrieveAdditionalDriverInfo(key, `${GOOGLEAPIS_CDN}/${notesPath}`, cdInfo)
159+
);
161160
promises.push(promise);
162161
chunk.push(promise);
163162
if (chunk.length >= MAX_PARALLEL_DOWNLOADS) {
164163
await B.any(chunk);
165164
}
166-
_.remove(chunk, (p) => p.isFulfilled());
165+
_.remove(chunk, (p) => (p as B<void>).isFulfilled());
167166
}
168167
await B.all(promises);
169168
log.info(`The total count of entries in the mapping: ${_.size(mapping)}`);
170169
return mapping;
171170
}
172171

173172
/**
174-
* Downloads chromedriver release notes and puts them
175-
* into the dictionary argument
173+
* Downloads chromedriver release notes and updates the driver info dictionary
176174
*
177-
* The method call mutates by merging `AdditionalDriverDetails`
178-
* @param {string} driverKey - Driver version plus archive name
179-
* @param {string} notesUrl - The URL of chromedriver notes
180-
* @param {ChromedriverDetails} infoDict - The dictionary containing driver info.
181-
* @param {number} timeout
182-
* @throws {Error} if the release notes cannot be downloaded
175+
* Mutates `infoDict` by setting `minBrowserVersion` if found in notes
176+
* @param driverKey - Driver version plus archive name
177+
* @param notesUrl - The URL of chromedriver notes
178+
* @param infoDict - The dictionary containing driver info (will be mutated)
179+
* @param timeout - Request timeout in milliseconds
183180
*/
184-
async function retrieveAdditionalDriverInfo(driverKey, notesUrl, infoDict, timeout = STORAGE_REQ_TIMEOUT_MS) {
181+
async function retrieveAdditionalDriverInfo(
182+
driverKey: string,
183+
notesUrl: string,
184+
infoDict: ChromedriverDetails,
185+
timeout = STORAGE_REQ_TIMEOUT_MS
186+
): Promise<void> {
185187
const notes = await retrieveData(
186188
notesUrl,
187189
{
@@ -201,9 +203,8 @@ async function retrieveAdditionalDriverInfo(driverKey, notesUrl, infoDict, timeo
201203
infoDict.minBrowserVersion = minBrowserVersion;
202204
}
203205

204-
/**
205-
* @typedef {import('../types').SyncOptions} SyncOptions
206-
* @typedef {import('../types').OSInfo} OSInfo
207-
* @typedef {import('../types').ChromedriverDetails} ChromedriverDetails
208-
* @typedef {import('../types').ChromedriverDetailsMapping} ChromedriverDetailsMapping
209-
*/
206+
function extractNodeText(node: Node | null | undefined): string | null {
207+
return !node?.firstChild || !util.hasValue(node.firstChild.nodeValue)
208+
? null
209+
: node.firstChild.nodeValue;
210+
}

0 commit comments

Comments
 (0)