解决Windows平台下SWA CLI的StaticSitesClient.exe下载缓慢的方法和解决方法的探索过程

0. 引言

本站采用的是微软Azure Static Web App服务(以下简称SWA)部署的,如果不使用Github Actions或Azure Devops,那么SWA CLI是将网站部署到SWA的唯一选择。

SWA CLI在部署网站时依赖一个二进制文件:StaticSitesClient.exe,这个文件的下载可谓是极其的缓慢,60多M的文件,在我这里甚至需要近一个小时的下载时间,这实在是不可忍受的。

为了解决这个问题,我们可以手动下载这个文件并且手动生成配置文件以供SWA CLI使用。

以下是解决方法。

1. 查询版本信息

首先我们使用微软的https://swalocaldeploy.azureedge.net/downloads/versions.jsonAPI来获取到当前可供下载的版本信息,SWA CLI一般使用version为stable的版本。

下面是一个获取到的stable版本的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"version": "stable",
"buildId": "1.0.026911",
"publishDate": "2024-05-15T19:23:23.3973684Z",
"files": {
"linux-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/linux/StaticSitesClient",
"sha": "e1d9e033c973a35f64b7e41b6a114bd8e48022c9c3f7676e79047e87245a874d"
},
"win-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/windows/StaticSitesClient.exe",
"sha": "c67e5eed2b28fcf5c98348732653d1e2b37d842e6dde9a6b30322832c5d86fc7"
},
"osx-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/macOS/StaticSitesClient",
"sha": "18ca42a1b13db9b8b6db6bd8c77e65def56fa7bf3ce3fb1184e890d8cd7dd033"
}
}
}

我们找到其中的win-x64字段,其中的url字段就是下载链接,我们将其下载至本地。

2. 创建本地文件

我们在目录C:\Users\你的用户名.swa\deploy下创建一个文件夹,文件夹名为上方获取到的buildId内容,比如这里是1.0.026911,那么文件夹的全路径就是C:\Users\你的用户名\.swa\deploy\1.0.026911

将你下载好的StaticSitesClient.exe放入创建好的文件夹中,回到上级文件夹(deploy目录)中,创建StaticSitesClient.json文件,并向其中写入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"metadata": {
"version": "stable",
"buildId": "1.0.026911",
"publishDate": "2024-05-15T19:23:23.3973684Z",
"files": {
"linux-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/linux/StaticSitesClient",
"sha": "e1d9e033c973a35f64b7e41b6a114bd8e48022c9c3f7676e79047e87245a874d"
},
"win-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/windows/StaticSitesClient.exe",
"sha": "c67e5eed2b28fcf5c98348732653d1e2b37d842e6dde9a6b30322832c5d86fc7"
},
"osx-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/macOS/StaticSitesClient",
"sha": "18ca42a1b13db9b8b6db6bd8c77e65def56fa7bf3ce3fb1184e890d8cd7dd033"
}
}
},
"binary":"C:\\Users\\你的用户名\\.swa\\deploy\\1.0.026911\\StaticSitesClient.exe",
"checksum":"c67e5eed2b28fcf5c98348732653d1e2b37d842e6dde9a6b30322832c5d86fc7"
}

其中的metadata字段即为在微软版本API中获得的内容,binary字段为可执行文件的绝对路径(注意路径使用双反斜线分隔),checksum字段则为校验字段,其内容应于metadatafiles各平台字段中sha字段内容保持一致。

保存退出该文件,再次运行swa deploy即可成功部署了。

3. 探索过程

解决方案到上面就结束了,下面是整个问题探索的技术过程。

首先我们定位到工具的static-web-apps-cli\dist\cli\commands\deploy\deploy.js文件,这里包含了swa deploy命令的具体执行内容。

顺着文件向下查找,我们可以在此文件中的第220行中发现以下内容:

1
const { binary, buildId } = await (0, deploy_client_1.getDeployClientPath)();

追踪getDeployClientPath函数可以发现,该函数首先尝试获取本地可执行文件的metadata,同时尝试获取远程API中的metadata:

1
2
3
const localClientMetadata = getLocalClientMetadata();
const binaryVersion = (0, env_1.swaCLIEnv)().SWA_CLI_DEPLOY_BINARY_VERSION || constants_1.DEPLOY_BINARY_STABLE_TAG;
const remoteClientMetadata = await fetchClientVersionDefinition(binaryVersion);

此处的fetchClientVersionDefinition函数中传入的binaryVersion参数应为”stable”,具体原理由于时间关系未能探明。

继续追踪fetchClientVersionDefinition函数,可以看到该函数尝试从一个常量中定义的URL,也就是之前提到的API地址获取版本信息,具体网址为:https://swalocaldeploy.azureedge.net/downloads/versions.json
访问这个地址会得到一个数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
[
{
"version": "latest",
"buildId": "1.0.027044",
"publishDate": "2024-05-28T19:44:23.0025058Z",
"files": {
"linux-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.027044/linux/StaticSitesClient",
"sha": "e1d9e033c973a35f64b7e41b6a114bd8e48022c9c3f7676e79047e87245a874d"
},
"win-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.027044/windows/StaticSitesClient.exe",
"sha": "c67e5eed2b28fcf5c98348732653d1e2b37d842e6dde9a6b30322832c5d86fc7"
},
"osx-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.027044/macOS/StaticSitesClient",
"sha": "18ca42a1b13db9b8b6db6bd8c77e65def56fa7bf3ce3fb1184e890d8cd7dd033"
}
}
},
{
"version": "stable",
"buildId": "1.0.026911",
"publishDate": "2024-05-15T19:23:23.3973684Z",
"files": {
"linux-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/linux/StaticSitesClient",
"sha": "e1d9e033c973a35f64b7e41b6a114bd8e48022c9c3f7676e79047e87245a874d"
},
"win-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/windows/StaticSitesClient.exe",
"sha": "c67e5eed2b28fcf5c98348732653d1e2b37d842e6dde9a6b30322832c5d86fc7"
},
"osx-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026911/macOS/StaticSitesClient",
"sha": "18ca42a1b13db9b8b6db6bd8c77e65def56fa7bf3ce3fb1184e890d8cd7dd033"
}
}
},
{
"version": "backup",
"buildId": "1.0.026792",
"publishDate": "2024-05-03T18:31:36.0288058Z",
"files": {
"linux-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026792/linux/StaticSitesClient",
"sha": "a9dcd998d22a3476fb97fe1c446e83cc7f060a3a36cdb6757b828d0facc42347"
},
"win-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026792/windows/StaticSitesClient.exe",
"sha": "15f03e3f91208db2ea4015c4fce0623a692da1a289f23a782578ab7bc8a810e2"
},
"osx-x64": {
"url": "https://swalocaldeploy.azureedge.net/downloads/1.0.026792/macOS/StaticSitesClient",
"sha": "454b10f0351694ec3475a3d85b8746356932b2061a5be7e7fc093a6509cf000c"
}
}
}
]

函数会筛选返回的结果以匹配binaryVersion字段的内容,例如version为stable的内容。

追踪getLocalClientMetadata函数可以发现,该函数尝试从部署文件夹中读取一个名为StaticSitesClient.json的文件并做二进制文件存在性检测,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
exports.getDeployClientPath = getDeployClientPath;
function getLocalClientMetadata() {
const metadataFilename = path_1.default.join(constants_1.DEPLOY_FOLDER, `${constants_1.DEPLOY_BINARY_NAME}.json`);
if (!fs_1.default.existsSync(metadataFilename)) {
utils_1.logger.warn(`Could not find ${constants_1.DEPLOY_BINARY_NAME} local binary`);
return null;
}
let metadata = null;
try {
metadata = JSON.parse(fs_1.default.readFileSync(metadataFilename, "utf8"));
}
catch (err) {
utils_1.logger.warn(`Could not read ${constants_1.DEPLOY_BINARY_NAME} metadata: ${err}`);
return null;
}
if (metadata) {
utils_1.logger.warn(fs_1.default.existsSync(metadata.binary));
if (!fs_1.default.existsSync(metadata.binary)) {
utils_1.logger.warn(`Could not find ${constants_1.DEPLOY_BINARY_NAME} binary: ${metadata.binary}`);
return null;
}
else if (fs_1.default.existsSync(metadata.binary)) {
return metadata;
}
}
return null;
}

顺着getDeployClientPath函数继续往下走,会发现当localClientMetadata不存在时,函数便会调用download_binary_helper.js中的downloadAndValidateBinary函数进行二进制文件的下载。

继续追踪downloadAndValidateBinary函数,我们直奔重点,发现其中存在一个名为saveMetadata的函数,想必这其中就包含了我们需要的Json文件的内容。

追踪saveMetadata函数,可以看到该函数有如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*
* @param release binary Metadata
* @param binaryFileName binary file location
* @param sha hash value
* @param binaryType StaticSiteClient or DataApiBuilder
*/
function saveMetadata(release, binaryFileName, sha, binaryName) {
const downloadFolder = getFolderForSavingMetadata(binaryName);
if (downloadFolder != null) {
const metadataFileName = path_1.default.join(downloadFolder, `${binaryName}.json`);
const metdata = {
metadata: release,
binary: binaryFileName,
checksum: sha,
};
fs_1.default.writeFileSync(metadataFileName, JSON.stringify(metdata));
utils_1.logger.silly(`Saved metadata to ${metadataFileName}`);
}
}

该函数内容清晰易懂,结合上方分析内容,即可得出我们需要的Json内容了。

将二进制文件和Json文件分别放置在版本号文件夹和部署文件夹后,再次运行swa deploy,问题即可完美解决。

感谢你能看到这里,如果这篇文章能解决你关于SWA CLI下载二进制文件速度慢的问题的话,那再好不过了。

顺带吐槽微软这个stable版本的下载速度,科学上网也只有两三百K,怪不得慢呢。