Skip to content

Commit 4a0ecf8

Browse files
committed
feat: Change download runtime on windows. Fix some windows bugs.
1 parent a360061 commit 4a0ecf8

File tree

9 files changed

+205
-27
lines changed

9 files changed

+205
-27
lines changed

genLocale.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const fs = require('fs');
22
const path = require('path');
33

44
const translationRegex =
5-
/t\('(.+?)'\s*,\s*{[^}]*?}\s*\)\s*\.d\(\s*`([^`]+)`\s*\)/g;
5+
/t\(\s*['"]([^'"]+)['"](?:\s*,\s*\{[^}]*\})?\s*\)\.d\(\s*(['"`])(.*?)\2\s*\)/g;
66
const outputPath = './src/i18n/locales.json';
77
let translations = {};
88

@@ -13,7 +13,7 @@ const parseFile = (filePath) => {
1313
const content = fs.readFileSync(filePath, 'utf-8');
1414
let match;
1515
while ((match = translationRegex.exec(content)) !== null) {
16-
const [_, key, msg] = match;
16+
const [_, key, __, msg] = match;
1717
if (!translations[key]) {
1818
translations[key] = {
1919
en: msg,

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "esa-cli",
3-
"version": "0.0.2-beta.4",
3+
"version": "0.0.2-beta.5",
44
"description": "A CLI for operating Alibaba Cloud ESA EdgeRoutine (Edge Functions).",
55
"main": "bin/enter.cjs",
66
"type": "module",
@@ -37,6 +37,7 @@
3737
"@testing-library/dom": "^10.4.0",
3838
"@testing-library/jest-dom": "^6.5.0",
3939
"@testing-library/react": "^16.0.1",
40+
"@types/adm-zip": "^0.5.7",
4041
"@types/babel__generator": "^7.6.8",
4142
"@types/babel__traverse": "^7.20.6",
4243
"@types/cli-table": "^0.3.4",
@@ -74,7 +75,7 @@
7475
"@iarna/toml": "^2.2.5",
7576
"@types/inquirer": "^9.0.7",
7677
"@vitest/coverage-istanbul": "^2.0.4",
77-
"axios": "^1.7.7",
78+
"adm-zip": "^0.5.16",
7879
"chalk": "^5.3.0",
7980
"chokidar": "^3.5.3",
8081
"cli-table3": "^0.6.5",

src/commands/dev/ew2/devPack.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,10 @@ const generateEntry = async (
8686

8787
return fs.promises.writeFile(
8888
devEntry,
89-
devEntryTempFile.replace(/'\$userPath'/g, `'${projectEntry}'`)
89+
devEntryTempFile.replace(
90+
/'\$userPath'/g,
91+
`'${projectEntry.replace(/\\/g, '/')}'`
92+
)
9093
);
9194
};
9295

src/commands/dev/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import { checkOS, Platforms } from '../../utils/checkOS.js';
2424

2525
let yargsIns: Argv;
2626
const OS = checkOS();
27-
const useEw2 =
28-
OS === Platforms.AppleArm || Platforms.AppleIntel || Platforms.LinuxX86;
27+
const EW2OS = [Platforms.AppleArm, Platforms.AppleIntel, Platforms.LinuxX86];
28+
const useEw2 = EW2OS.includes(OS);
2929
const dev: CommandModule = {
3030
command: 'dev [entry]',
3131
describe: `💻 ${t('dev_describe').d('Start a local server for developing your routine')}`,

src/commands/dev/mockWorker/devPack.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ const generateEntry = async (
4040

4141
return fs.promises.writeFile(
4242
devEntry,
43-
devEntryTempFile.replace(/'\$userPath'/g, `'${projectEntry}'`)
43+
devEntryTempFile.replace(
44+
/'\$userPath'/g,
45+
`'${projectEntry.replace(/\\/g, '/')}'`
46+
)
4447
);
4548
};
4649

src/i18n/locales.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,5 +894,21 @@
894894
"dev_url_invalid": {
895895
"en": "Invalid URL: ${url}. Please enter a valid URL.",
896896
"zh_CN": "不是正确的URL: ${url}. 请输入正确的URL."
897+
},
898+
"deno_download_failed": {
899+
"en": "Download failed",
900+
"zh_CN": "下载失败"
901+
},
902+
"deno_unzip_failed": {
903+
"en": "Unzip failed",
904+
"zh_CN": "解压失败"
905+
},
906+
"deno_add_path_failed": {
907+
"en": "Add BinDir to Path failed",
908+
"zh_CN": "添加环境变量失败"
909+
},
910+
"deno_install_success": {
911+
"en": "Runtime install success!",
912+
"zh_CN": "Runtime 安装成功!"
897913
}
898-
}
914+
}

src/index.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { getCliConfig } from './utils/fileUtils/index.js';
1919
import { handleCheckVersion } from './utils/checkVersion.js';
2020
import t from './i18n/index.js';
2121
import site from './commands/site/index.js';
22-
import { quickDeployRoutine } from './libs/service.js';
2322

2423
const main = async () => {
2524
const argv = hideBin(process.argv);
@@ -88,19 +87,6 @@ const main = async () => {
8887

8988
esa.command(lang);
9089

91-
esa.command(
92-
't',
93-
false,
94-
() => {},
95-
async (args) => {
96-
const res = await quickDeployRoutine({
97-
name: 'test',
98-
code: 'test'
99-
});
100-
// console.log(res);
101-
}
102-
);
103-
10490
esa.group(['help', 'version'], 'Options:');
10591

10692
esa.parse();

src/utils/download.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import fetch from 'node-fetch';
2+
import * as fs from 'fs/promises';
3+
import * as path from 'path';
4+
import os from 'os';
5+
import AdmZip from 'adm-zip';
6+
import { exec } from 'child_process';
7+
import { promisify } from 'util';
8+
import t from '../i18n/index.js';
9+
import logger from '../libs/logger.js';
10+
11+
const execAsync = promisify(exec);
12+
13+
function getBinDir(): string {
14+
const home = os.homedir();
15+
16+
return path.join(home || '', '.deno', 'bin');
17+
}
18+
19+
/**
20+
* 下载文件
21+
* @param url 远程文件URL
22+
* @param dest 本地保存路径
23+
*/
24+
export async function downloadFile(url: string, dest: string): Promise<void> {
25+
const response = await fetch(url);
26+
27+
if (!response.ok) {
28+
throw new Error(
29+
`Error downloading file: ${response.status} ${response.statusText}`
30+
);
31+
}
32+
33+
const fileStream = await fs.open(dest, 'w');
34+
return new Promise((resolve, reject) => {
35+
response.body?.pipe(fileStream.createWriteStream());
36+
response.body?.on('error', (err: Error) => {
37+
fileStream.close();
38+
reject(err);
39+
});
40+
fileStream.createWriteStream().on('finish', () => {
41+
fileStream.close();
42+
resolve();
43+
});
44+
});
45+
}
46+
47+
/**
48+
* 解压Zip文件 adm 是同步的
49+
* @param zipPath Zip文件路径
50+
* @param extractPath 解压目标目录
51+
*/
52+
export function unzipFile(zipPath: string, extractPath: string): void {
53+
const zip = new AdmZip(zipPath);
54+
zip.extractAllTo(extractPath, true);
55+
logger.info(`UnzipFile success: from ${zipPath} to ${extractPath}`);
56+
}
57+
58+
/**
59+
* 获取用户的 PATH 环境变量(win下专用)
60+
* @returns 用户 PATH
61+
*/
62+
async function getUserPath(): Promise<string> {
63+
const { stdout } = await execAsync('reg query "HKCU\\Environment" /v Path');
64+
const match = stdout.match(/Path\s+REG_EXPAND_SZ\s+(.*)/i);
65+
if (match && match[1]) {
66+
return match[1].trim();
67+
}
68+
return '';
69+
}
70+
71+
/**
72+
* 检查 BinDir 是否在用户的 PATH 中(win下专用)
73+
* @param binDir BinDir 路径
74+
* @returns 是否包含
75+
*/
76+
async function isBinDirInPath(binDir: string): Promise<boolean> {
77+
const userPath = await getUserPath();
78+
return userPath
79+
.split(';')
80+
.map((p) => p.toLowerCase())
81+
.includes(binDir.toLowerCase());
82+
}
83+
84+
/**
85+
* 将 BinDir 添加到用户的 PATH 环境变量(win下专用)
86+
* @param binDir BinDir 路径
87+
*/
88+
async function addBinDirToPath(binDir: string): Promise<void> {
89+
// 使用 setx 添加到 PATH
90+
// setx 对 PATH 的长度有2047字符的限制
91+
const command = `setx Path "%Path%;${binDir}"`;
92+
try {
93+
await execAsync(command);
94+
logger.info(`Path add success: ${binDir}`);
95+
} catch (error) {
96+
throw new Error(`Add BinDir to Path failed: ${error}`);
97+
}
98+
}
99+
100+
export async function downloadRuntimeAndUnzipForWindows() {
101+
try {
102+
const BinDir = getBinDir();
103+
const DenoZip = path.join(BinDir, 'deno.zip');
104+
const Target = 'x86_64-pc-windows-msvc';
105+
const DownloadUrl = `http://esa-runtime.myalicdn.com/runtime/deno-${Target}.zip`;
106+
107+
try {
108+
await fs.mkdir(BinDir, { recursive: true });
109+
} catch (error) {
110+
const err = error as Error;
111+
logger.error(`mkdir error ${BinDir}: ${err.message}`);
112+
process.exit(1);
113+
}
114+
115+
try {
116+
await downloadFile(DownloadUrl, DenoZip);
117+
logger.success(
118+
`${t('deno_download_success').d('Download success')}: ${DenoZip}`
119+
);
120+
} catch (error) {
121+
const err = error as Error;
122+
logger.error(
123+
`${t('deno_download_failed').d('Download failed')}: ${err.message}`
124+
);
125+
process.exit(1);
126+
}
127+
128+
logger.info(`Unzip file to: ${BinDir}`);
129+
try {
130+
unzipFile(DenoZip, BinDir);
131+
} catch (error) {
132+
const err = error as Error;
133+
logger.error(
134+
`${t('deno_unzip_failed').d('Unzip failed')}: ${err.message}`
135+
);
136+
process.exit(1);
137+
}
138+
139+
try {
140+
await fs.unlink(DenoZip);
141+
logger.info(`Delete temp file: ${DenoZip}`);
142+
} catch (error) {
143+
logger.warn(`Delete temp file ${DenoZip} failed: ${error}`);
144+
}
145+
146+
try {
147+
const inPath = await isBinDirInPath(BinDir);
148+
if (!inPath) {
149+
logger.info(`${BinDir} not in PATH, adding...`);
150+
await addBinDirToPath(BinDir);
151+
} else {
152+
logger.info(`${BinDir} in PATH already`);
153+
}
154+
} catch (error) {
155+
const err = error as Error;
156+
logger.error(
157+
`${t('deno_add_path_failed').d('Add BinDir to Path failed')}: ${err.message}`
158+
);
159+
process.exit(1);
160+
}
161+
162+
logger.success(t('deno_install_success').d('Runtime install success!'));
163+
} catch (error) {
164+
const err = error as Error;
165+
logger.error(`Download Error: ${err.message}`);
166+
process.exit(1);
167+
}
168+
}

src/utils/installDeno.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { exec, execSync } from 'child_process';
22
import os from 'os';
3-
import { getDirName } from './fileUtils/base.js';
4-
import logger from '../libs/logger.js';
53
import path from 'path';
4+
import logger from '../libs/logger.js';
5+
import { getDirName } from './fileUtils/base.js';
6+
import { downloadRuntimeAndUnzipForWindows } from './download.js';
67
import t from '../i18n/index.js';
78

89
export async function preCheckDeno(): Promise<string | false> {
@@ -52,8 +53,8 @@ export async function installDeno(): Promise<boolean> {
5253

5354
switch (os.platform()) {
5455
case 'win32':
55-
installCommand = `powershell.exe -Command "Get-Content '${p}/install.ps1' | iex"`;
56-
break;
56+
await downloadRuntimeAndUnzipForWindows();
57+
return true;
5758
case 'darwin':
5859
case 'linux':
5960
installCommand = `sh ${p}/install.sh`;

0 commit comments

Comments
 (0)