zed-ets-language-server 2.3.4 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +142 -9
- package/lib/data-parser.js +17 -12
- package/lib/lib-expander.js +11 -3
- package/lib/sdk-discovery.js +182 -0
- package/lib/sdk-discovery.test.js +252 -0
- package/package.json +3 -3
- package/tests/integration/kit-diagnostics.test.js +232 -0
- package/tests/integration/lsp-server.test.js +148 -68
- package/tests/integration/sdk-initialization.test.js +167 -0
- package/tests/mocks/mock-ets-server.js +4 -2
package/index.js
CHANGED
|
@@ -2,13 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
5
7
|
import { logger } from './lib/logger.js';
|
|
6
8
|
import { parse } from './lib/data-parser.js';
|
|
7
|
-
import { listHelperPaths } from './lib/lib-expander.js'
|
|
9
|
+
import { listHelperPaths } from './lib/lib-expander.js';
|
|
10
|
+
import { resolveHmsSdkPath, resolveOhosSdkPath } from './lib/sdk-discovery.js';
|
|
8
11
|
|
|
9
12
|
// ETS language server path, passed by Rust extension process through environment variable
|
|
10
13
|
const etsLangServerPath = process.env.ETS_LANG_SERVER;
|
|
11
14
|
|
|
15
|
+
// The extension installs ohos-typescript next to @arkts/language-server, so a
|
|
16
|
+
// usable tsdk can be derived from the server path when settings don't name one:
|
|
17
|
+
// <work dir>/node_modules/@arkts/language-server/bin/ets-language-server.js
|
|
18
|
+
// <work dir>/node_modules/ohos-typescript/lib
|
|
19
|
+
// @arkts/language-server v1.3+ refuses to initialize unless ets.sdkPath points at
|
|
20
|
+
// a directory containing ets/build-tools/ets-loader/tsconfig.json (v1.2 accepted
|
|
21
|
+
// any value). Provide a minimal skeleton so the server starts without a real SDK;
|
|
22
|
+
// ArkUI typings degrade but TypeScript-level features keep working.
|
|
23
|
+
function ensurePlaceholderSdk() {
|
|
24
|
+
const sdkDir = path.join(os.tmpdir(), 'zed-ets-empty-ohos-sdk');
|
|
25
|
+
const etsLoaderDir = path.join(sdkDir, 'ets', 'build-tools', 'ets-loader');
|
|
26
|
+
try {
|
|
27
|
+
fs.mkdirSync(path.join(etsLoaderDir, 'declarations'), { recursive: true });
|
|
28
|
+
fs.mkdirSync(path.join(sdkDir, 'ets', 'component'), { recursive: true });
|
|
29
|
+
const tsconfigPath = path.join(etsLoaderDir, 'tsconfig.json');
|
|
30
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
31
|
+
fs.writeFileSync(tsconfigPath, '{}\n');
|
|
32
|
+
}
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error(`Failed to prepare placeholder SDK dir ${sdkDir}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
return sdkDir;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function detectTsdk() {
|
|
40
|
+
if (!etsLangServerPath) return undefined;
|
|
41
|
+
// The extension installs ohos-typescript next to @arkts/language-server:
|
|
42
|
+
// <work dir>/node_modules/ohos-typescript/lib
|
|
43
|
+
// <work dir>/node_modules/@arkts/language-server/bin/ets-language-server.js
|
|
44
|
+
// Walk the ancestors of the server path and accept either layout so the
|
|
45
|
+
// detection also works for servers installed at other depths.
|
|
46
|
+
let dir = path.dirname(path.resolve(etsLangServerPath));
|
|
47
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
48
|
+
for (const candidate of [
|
|
49
|
+
path.join(dir, 'ohos-typescript', 'lib'),
|
|
50
|
+
path.join(dir, 'node_modules', 'ohos-typescript', 'lib'),
|
|
51
|
+
]) {
|
|
52
|
+
if (fs.existsSync(path.join(candidate, 'typescript.js'))) return candidate;
|
|
53
|
+
}
|
|
54
|
+
const parent = path.dirname(dir);
|
|
55
|
+
if (parent === dir) break;
|
|
56
|
+
dir = parent;
|
|
57
|
+
}
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// A usable tsdk must contain lib/typescript.js. The native TypeScript 7 line
|
|
62
|
+
// ships no compiler API there, and settings inherited from another machine may
|
|
63
|
+
// point at a half-installed directory; forwarding such a tsdk makes the server
|
|
64
|
+
// hang inside `initialize` without ever answering.
|
|
65
|
+
function isValidTsdk(dir) {
|
|
66
|
+
return Boolean(dir) && fs.existsSync(path.join(dir, 'typescript.js'));
|
|
67
|
+
}
|
|
68
|
+
|
|
12
69
|
async function main() {
|
|
13
70
|
logger.section('🚀 ETS Language Server Wrapper');
|
|
14
71
|
|
|
@@ -39,29 +96,104 @@ async function main() {
|
|
|
39
96
|
logger.info(`Language server process exited, exit code: ${code}, signal: ${signal}`);
|
|
40
97
|
});
|
|
41
98
|
|
|
99
|
+
// Requests injected by this wrapper (not sent by the editor); their responses
|
|
100
|
+
// must not be forwarded to the editor, which never issued them.
|
|
101
|
+
const wrapperRequestIds = new Set();
|
|
102
|
+
|
|
42
103
|
// Set up forwarding of serverProcess IPC messages to process.stdout
|
|
43
104
|
serverProcess.on('message', (message) => {
|
|
105
|
+
if (message?.id !== undefined && wrapperRequestIds.delete(message.id)) {
|
|
106
|
+
logger.info(`Swallowed response to wrapper-injected request ${message.id}: ${JSON.stringify(message.result ?? message.error)}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
44
109
|
// Convert IPC message to standard LSP format and send to stdout
|
|
45
110
|
const messageStr = JSON.stringify(message);
|
|
46
111
|
const headers = `Content-Length: ${Buffer.byteLength(messageStr)}\r\n\r\n`;
|
|
47
112
|
process.stdout.write(headers + messageStr);
|
|
48
113
|
});
|
|
49
114
|
|
|
50
|
-
// Set up forwarding of process.stdin to serverProcess IPC
|
|
51
|
-
|
|
115
|
+
// Set up forwarding of process.stdin to serverProcess IPC.
|
|
116
|
+
// No setEncoding here: the parser needs raw bytes because LSP Content-Length
|
|
117
|
+
// counts bytes, not characters.
|
|
52
118
|
process.stdin.on('data', (data) => parse(data, async (message) => {
|
|
53
119
|
// This special ets request is required in document: https://github.com/ohosvscode/arkTS/tree/next/packages/language-server
|
|
54
120
|
// When this goes wrong, ETS UI decorators and functions will be type of any
|
|
55
121
|
if (message.method === 'initialize') {
|
|
56
|
-
|
|
122
|
+
message.params = message.params ?? {};
|
|
123
|
+
const initializationOptions = message.params.initializationOptions ?? {};
|
|
124
|
+
message.params.initializationOptions = initializationOptions;
|
|
125
|
+
|
|
126
|
+
// Zed only passes initializationOptions when the user configured
|
|
127
|
+
// lsp.arkts-language-server.initialization_options in settings. Fall back to
|
|
128
|
+
// env vars, then auto-detection, so the server starts out of the box.
|
|
129
|
+
if (!initializationOptions.tsdk) {
|
|
130
|
+
initializationOptions.tsdk = process.env.ZED_ETS_TSDK || process.env.TSDK || detectTsdk();
|
|
131
|
+
logger.info(`No tsdk in initializationOptions; falling back to: ${initializationOptions.tsdk}`);
|
|
132
|
+
}
|
|
133
|
+
// A native TypeScript installation may not contain the JS compiler API.
|
|
134
|
+
if (!isValidTsdk(initializationOptions.tsdk)) {
|
|
135
|
+
const fallbackTsdk = detectTsdk();
|
|
136
|
+
if (fallbackTsdk) {
|
|
137
|
+
logger.error(`tsdk ${initializationOptions.tsdk} has no lib/typescript.js; falling back to ${fallbackTsdk}`);
|
|
138
|
+
initializationOptions.tsdk = fallbackTsdk;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let hasRealOhosSdk = false;
|
|
143
|
+
try {
|
|
144
|
+
const resolvedSdk = resolveOhosSdkPath({
|
|
145
|
+
configuredPath: initializationOptions.ohosSdkPath,
|
|
146
|
+
env: process.env,
|
|
147
|
+
});
|
|
148
|
+
if (resolvedSdk) {
|
|
149
|
+
initializationOptions.ohosSdkPath = resolvedSdk.path;
|
|
150
|
+
hasRealOhosSdk = true;
|
|
151
|
+
logger.info(`Using HarmonyOS SDK from ${resolvedSdk.source}: ${resolvedSdk.path}`);
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error(error.message);
|
|
155
|
+
initializationOptions.ohosSdkPath = undefined;
|
|
156
|
+
}
|
|
57
157
|
|
|
58
|
-
|
|
158
|
+
// The server cannot finish `initialize` without a tsdk (it fails loading
|
|
159
|
+
// TypeScript and Zed reports "Failed to start language server").
|
|
160
|
+
if (!initializationOptions.tsdk) {
|
|
161
|
+
logger.error(`No tsdk in LSP settings, env (ZED_ETS_TSDK/TSDK), or next to ${etsLangServerPath}; forwarding initialize as-is, the server will likely fail to start.`);
|
|
162
|
+
serverProcess.send(message);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
59
165
|
|
|
60
|
-
|
|
61
|
-
|
|
166
|
+
if (!initializationOptions.ohosSdkPath) {
|
|
167
|
+
initializationOptions.ohosSdkPath = ensurePlaceholderSdk();
|
|
168
|
+
logger.error('No valid HarmonyOS SDK was found in LSP settings, environment variables, or standard DevEco Studio locations; using a placeholder SDK skeleton. ArkUI and @kit types will be unavailable until lsp.arkts-language-server.initialization_options.ohosSdkPath is set in Zed settings.');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const resolvedHmsSdk = resolveHmsSdkPath({
|
|
173
|
+
configuredPath: initializationOptions.hmsSdkPath,
|
|
174
|
+
ohosSdkPath: hasRealOhosSdk ? initializationOptions.ohosSdkPath : undefined,
|
|
175
|
+
env: process.env,
|
|
176
|
+
});
|
|
177
|
+
if (resolvedHmsSdk) {
|
|
178
|
+
initializationOptions.hmsSdkPath = resolvedHmsSdk.path;
|
|
179
|
+
logger.info(`Using HMS SDK from ${resolvedHmsSdk.source}: ${resolvedHmsSdk.path}`);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logger.error(error.message);
|
|
183
|
+
initializationOptions.hmsSdkPath = undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const ohos = await listHelperPaths(
|
|
187
|
+
initializationOptions.tsdk,
|
|
188
|
+
initializationOptions.ohosSdkPath,
|
|
189
|
+
initializationOptions.hmsSdkPath,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Current servers read `ets`; retain the `ohos` alias for clients that
|
|
193
|
+
// still inspect the older configuration key.
|
|
62
194
|
const etsSpecialRequest = {
|
|
63
195
|
jsonrpc: '2.0',
|
|
64
|
-
id: Date.now()
|
|
196
|
+
id: `zed-ets-wrapper-${Date.now()}`,
|
|
65
197
|
method: 'ets/waitForEtsConfigurationChangedRequested',
|
|
66
198
|
params: {
|
|
67
199
|
typescript: {
|
|
@@ -81,7 +213,8 @@ async function main() {
|
|
|
81
213
|
|
|
82
214
|
logger.info(JSON.stringify(generalInitRequest));
|
|
83
215
|
logger.info(JSON.stringify(etsSpecialRequest));
|
|
84
|
-
|
|
216
|
+
|
|
217
|
+
wrapperRequestIds.add(etsSpecialRequest.id);
|
|
85
218
|
serverProcess.send(generalInitRequest);
|
|
86
219
|
serverProcess.send(etsSpecialRequest);
|
|
87
220
|
return;
|
package/lib/data-parser.js
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
1
|
import { logger } from './logger.js';
|
|
2
2
|
|
|
3
|
-
let stdinBuffer =
|
|
3
|
+
let stdinBuffer = Buffer.alloc(0);
|
|
4
4
|
|
|
5
5
|
export function parse(data, callback) {
|
|
6
|
-
|
|
6
|
+
// LSP Content-Length is measured in bytes, not characters, so all parsing
|
|
7
|
+
// must operate on a Buffer. Convert string input to a Buffer if necessary
|
|
8
|
+
// (happens when stdin.setEncoding('utf8') is used, which yields strings).
|
|
9
|
+
const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
10
|
+
stdinBuffer = Buffer.concat([stdinBuffer, dataBuffer]);
|
|
7
11
|
|
|
8
12
|
while (true) {
|
|
9
|
-
// Find Content-Length header
|
|
10
|
-
const lengthMatch = stdinBuffer.match(/Content-Length: (\d+)\r\n/);
|
|
11
|
-
if (!lengthMatch) break;
|
|
12
|
-
|
|
13
|
-
const contentLength = Number.parseInt(lengthMatch[1]);
|
|
14
13
|
const headerEnd = stdinBuffer.indexOf('\r\n\r\n');
|
|
15
|
-
|
|
16
14
|
if (headerEnd === -1) break;
|
|
17
15
|
|
|
16
|
+
const headerPart = stdinBuffer.subarray(0, headerEnd).toString('utf8');
|
|
17
|
+
const lengthMatch = headerPart.match(/Content-Length: (\d+)/);
|
|
18
|
+
if (!lengthMatch) break;
|
|
19
|
+
|
|
20
|
+
const contentLength = Number.parseInt(lengthMatch[1]);
|
|
18
21
|
const messageStart = headerEnd + 4;
|
|
19
22
|
const messageEnd = messageStart + contentLength;
|
|
20
23
|
|
|
24
|
+
// Content-Length counts bytes, so compare against the buffer's byte length.
|
|
21
25
|
if (stdinBuffer.length < messageEnd) break;
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stdinBuffer = stdinBuffer.substring(messageEnd);
|
|
27
|
+
const messageJson = stdinBuffer.subarray(messageStart, messageEnd).toString('utf8');
|
|
28
|
+
stdinBuffer = stdinBuffer.subarray(messageEnd);
|
|
26
29
|
|
|
27
30
|
try {
|
|
28
31
|
const message = JSON.parse(messageJson);
|
|
29
32
|
callback(message);
|
|
30
33
|
} catch (error) {
|
|
31
34
|
logger.error(`Error parsing message: ${error.message} ${error.stack} ${messageJson}`);
|
|
35
|
+
// Clear buffer on parse error to prevent corruption from leftover data
|
|
36
|
+
stdinBuffer = Buffer.alloc(0);
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
export function clearBuffer() {
|
|
37
|
-
stdinBuffer =
|
|
42
|
+
stdinBuffer = Buffer.alloc(0);
|
|
38
43
|
}
|
package/lib/lib-expander.js
CHANGED
|
@@ -73,22 +73,30 @@ export async function listLibs(dirPath) {
|
|
|
73
73
|
return await getFilesByPattern(dirPath, /d\.ts$/i);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
export async function listHelperPaths(tsDir, harmonyDir) {
|
|
76
|
+
export async function listHelperPaths(tsDir, harmonyDir, hmsDir) {
|
|
77
77
|
const etsComponentPath = path.join(harmonyDir, '/ets/component');
|
|
78
78
|
const etsLoaderConfigPath = path.join(harmonyDir, '/ets/build-tools/ets-loader/tsconfig.json');
|
|
79
79
|
const etsLoaderPath = path.join(harmonyDir, '/ets/build-tools/ets-loader');
|
|
80
80
|
const etsLoaderLibs = await listLibs(path.join(etsLoaderPath, '/declarations'));
|
|
81
81
|
|
|
82
|
+
const modulePaths = ["./api/*", "./kits/*", "./arkts/*"];
|
|
83
|
+
if (hmsDir) {
|
|
84
|
+
modulePaths.push(path.join(hmsDir, 'ets', 'api', '*'));
|
|
85
|
+
modulePaths.push(path.join(hmsDir, 'ets', 'kits', '*'));
|
|
86
|
+
}
|
|
87
|
+
|
|
82
88
|
return {
|
|
83
89
|
sdkPath: harmonyDir,
|
|
90
|
+
hmsPath: hmsDir,
|
|
91
|
+
hmsSdkPath: hmsDir,
|
|
84
92
|
etsComponentPath,
|
|
85
93
|
etsLoaderConfigPath,
|
|
86
94
|
etsLoaderPath,
|
|
87
95
|
baseUrl: path.join(harmonyDir, '/ets'),
|
|
88
96
|
lib: [...(await listLibs(tsDir)), ...(await listLibs(etsComponentPath)), ...etsLoaderLibs],
|
|
89
97
|
"paths": {
|
|
90
|
-
"*":
|
|
98
|
+
"*": modulePaths,
|
|
91
99
|
"@internal/full/*": ["./api/@internal/full/*"]
|
|
92
100
|
},
|
|
93
101
|
};
|
|
94
|
-
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const ENVIRONMENT_VARIABLES = [
|
|
6
|
+
'ZED_ETS_OHOS_SDK_PATH',
|
|
7
|
+
'OHOS_SDK_PATH',
|
|
8
|
+
'HARMONYOS_SDK_HOME',
|
|
9
|
+
'OPENHARMONY_SDK_HOME',
|
|
10
|
+
'DEVECO_SDK_HOME',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const REQUIRED_SDK_ENTRIES = [
|
|
14
|
+
{ relativePath: path.join('ets', 'kits'), type: 'directory' },
|
|
15
|
+
{ relativePath: path.join('ets', 'api'), type: 'directory' },
|
|
16
|
+
{
|
|
17
|
+
relativePath: path.join('ets', 'build-tools', 'ets-loader', 'tsconfig.json'),
|
|
18
|
+
type: 'file',
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const REQUIRED_HMS_ENTRIES = [
|
|
23
|
+
{ relativePath: path.join('ets', 'kits'), type: 'directory' },
|
|
24
|
+
{ relativePath: path.join('ets', 'api'), type: 'directory' },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function hasRequiredEntries(candidate, entries) {
|
|
28
|
+
try {
|
|
29
|
+
return entries.every(({ relativePath, type }) => {
|
|
30
|
+
const entryPath = path.join(candidate, relativePath);
|
|
31
|
+
const stats = fs.statSync(entryPath);
|
|
32
|
+
const accessMode = type === 'directory'
|
|
33
|
+
? fs.constants.R_OK | fs.constants.X_OK
|
|
34
|
+
: fs.constants.R_OK;
|
|
35
|
+
fs.accessSync(entryPath, accessMode);
|
|
36
|
+
return type === 'directory' ? stats.isDirectory() : stats.isFile();
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isValidOhosSdkPath(candidate) {
|
|
44
|
+
return hasRequiredEntries(candidate, REQUIRED_SDK_ENTRIES);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isValidHmsSdkPath(candidate) {
|
|
48
|
+
return hasRequiredEntries(candidate, REQUIRED_HMS_ENTRIES);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function expandHome(candidate, homeDirectory) {
|
|
52
|
+
return candidate.startsWith('~/')
|
|
53
|
+
? path.join(homeDirectory, candidate.slice(2))
|
|
54
|
+
: candidate;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeCandidate(candidate, homeDirectory = os.homedir()) {
|
|
58
|
+
if (!candidate) return undefined;
|
|
59
|
+
|
|
60
|
+
const expanded = expandHome(candidate, homeDirectory);
|
|
61
|
+
const normalized = path.resolve(expanded);
|
|
62
|
+
const possibleRoots = [
|
|
63
|
+
normalized,
|
|
64
|
+
path.join(normalized, 'openharmony'),
|
|
65
|
+
path.join(normalized, 'default', 'openharmony'),
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const versionDirectories = fs
|
|
70
|
+
.readdirSync(normalized, { withFileTypes: true })
|
|
71
|
+
.filter((entry) => entry.isDirectory() && entry.name !== 'default')
|
|
72
|
+
.map((entry) => entry.name)
|
|
73
|
+
.sort((left, right) => right.localeCompare(left, undefined, { numeric: true }));
|
|
74
|
+
for (const versionDirectory of versionDirectories) {
|
|
75
|
+
possibleRoots.push(path.join(normalized, versionDirectory, 'openharmony'));
|
|
76
|
+
possibleRoots.push(path.join(normalized, versionDirectory));
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Missing or unreadable candidates are ignored during discovery.
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return possibleRoots.find(isValidOhosSdkPath);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function defaultCandidates(platform, homeDirectory, env) {
|
|
86
|
+
const candidates = [];
|
|
87
|
+
const studioHome = env.DEVECO_STUDIO_HOME || env.DEVECO_HOME;
|
|
88
|
+
if (studioHome) {
|
|
89
|
+
candidates.push(path.join(studioHome, 'sdk'));
|
|
90
|
+
candidates.push(path.join(studioHome, 'Contents', 'sdk'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (platform === 'darwin') {
|
|
94
|
+
candidates.push(path.join(homeDirectory, 'Library', 'OpenHarmony', 'Sdk'));
|
|
95
|
+
candidates.push(path.join(homeDirectory, 'Library', 'Huawei', 'Sdk'));
|
|
96
|
+
candidates.push('/Applications/DevEco-Studio.app/Contents/sdk');
|
|
97
|
+
candidates.push(path.join(homeDirectory, 'Applications', 'DevEco-Studio.app', 'Contents', 'sdk'));
|
|
98
|
+
} else if (platform === 'win32') {
|
|
99
|
+
if (env.ProgramFiles) {
|
|
100
|
+
candidates.push(path.join(env.ProgramFiles, 'Huawei', 'DevEco Studio', 'sdk'));
|
|
101
|
+
}
|
|
102
|
+
if (env.LOCALAPPDATA) {
|
|
103
|
+
candidates.push(path.join(env.LOCALAPPDATA, 'Huawei', 'DevEcoStudio', 'sdk'));
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
candidates.push('/opt/DevEco-Studio/sdk');
|
|
107
|
+
candidates.push(path.join(homeDirectory, 'DevEco-Studio', 'sdk'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return candidates;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function resolveOhosSdkPath({
|
|
114
|
+
configuredPath,
|
|
115
|
+
env = process.env,
|
|
116
|
+
platform = process.platform,
|
|
117
|
+
homeDirectory = os.homedir(),
|
|
118
|
+
candidates,
|
|
119
|
+
} = {}) {
|
|
120
|
+
if (configuredPath) {
|
|
121
|
+
const resolved = normalizeCandidate(configuredPath, homeDirectory);
|
|
122
|
+
if (!resolved) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Invalid HarmonyOS SDK path: ${configuredPath}. Expected ets/kits, ets/api, and ets/build-tools/ets-loader/tsconfig.json.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return { path: resolved, source: 'settings' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const variableName of ENVIRONMENT_VARIABLES) {
|
|
131
|
+
const resolved = normalizeCandidate(env[variableName], homeDirectory);
|
|
132
|
+
if (resolved) {
|
|
133
|
+
return { path: resolved, source: variableName };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const searchCandidates = candidates ?? defaultCandidates(platform, homeDirectory, env);
|
|
138
|
+
for (const candidate of searchCandidates) {
|
|
139
|
+
const resolved = normalizeCandidate(candidate, homeDirectory);
|
|
140
|
+
if (resolved) {
|
|
141
|
+
return { path: resolved, source: 'auto-detected' };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function resolveHmsSdkPath({
|
|
149
|
+
configuredPath,
|
|
150
|
+
ohosSdkPath,
|
|
151
|
+
env = process.env,
|
|
152
|
+
homeDirectory = os.homedir(),
|
|
153
|
+
} = {}) {
|
|
154
|
+
const candidates = configuredPath
|
|
155
|
+
? [configuredPath, path.join(configuredPath, 'hms'), path.join(configuredPath, 'default', 'hms')]
|
|
156
|
+
: [
|
|
157
|
+
env.ZED_ETS_HMS_SDK_PATH,
|
|
158
|
+
env.HMS_SDK_PATH,
|
|
159
|
+
ohosSdkPath && path.join(path.dirname(ohosSdkPath), 'hms'),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
if (!candidate) continue;
|
|
164
|
+
const normalized = path.resolve(expandHome(candidate, homeDirectory));
|
|
165
|
+
if (isValidHmsSdkPath(normalized)) {
|
|
166
|
+
return {
|
|
167
|
+
path: normalized,
|
|
168
|
+
source: configuredPath ? 'settings' : 'auto-detected',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (configuredPath) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Invalid HMS SDK path: ${configuredPath}. Expected ets/kits and ets/api.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { isValidHmsSdkPath, isValidOhosSdkPath };
|