android2harmony 0.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/README.md +407 -0
- package/agents/build-fixer.md +385 -0
- package/agents/code-reviewer.md +478 -0
- package/agents/logic-coder.md +212 -0
- package/agents/logic-context-builder.md +199 -0
- package/agents/scripts/hmos-precheck/arkts.mjs +534 -0
- package/agents/scripts/hmos-precheck/codelinter.mjs +362 -0
- package/agents/scripts/hmos-precheck/index.mjs +926 -0
- package/agents/scripts/platform_query.mjs +535 -0
- package/dist/index.js +179 -0
- package/dist/index.js.map +7 -0
- package/package.json +29 -0
- package/skills/hmos-batch-ui-align/SKILL.md +141 -0
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +217 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
- package/skills/hmos-batch-ui-align/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
- package/skills/hmos-batch-ui-align/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
- package/skills/hmos-batch-ui-align/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
- package/skills/hmos-batch-ui-align/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
- package/skills/hmos-batch-ui-align/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
- package/skills/hmos-batch-ui-align/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
- package/skills/hmos-batch-ui-align/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
- package/skills/hmos-batch-ui-align/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
- package/skills/hmos-batch-ui-align/scripts/android_parse_fast.ts +1750 -0
- package/skills/hmos-convert-pipeline/SKILL.md +446 -0
- package/skills/hmos-fix-build-errors/SKILL.md +281 -0
- package/skills/hmos-fix-build-errors/references/arkts-strict-patterns.md +219 -0
- package/skills/hmos-fix-build-errors/references/known-patterns.md +157 -0
- package/skills/hmos-fix-build-errors/references/rdb-entity-pattern.md +131 -0
- package/skills/hmos-resources-convert/SKILL.md +654 -0
- package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
- package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
- package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
- package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
- package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
- package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
- package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
- package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
- package/skills/hmos-spec-generate/SKILL.md +413 -0
- package/skills/hmos-spec-generate/references/android-platform-tokens.md +105 -0
- package/skills/hmos-spec-generate/references/spec-sample-1.md +78 -0
- package/skills/hmos-spec-generate/references/spec-sample-2.md +58 -0
- package/skills/hmos-spec-generate/references/spec-sample-3.md +116 -0
- package/skills/hmos-spec-generate/references/step4-report-template.md +33 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { constants as fsConstants } from "node:fs";
|
|
3
|
+
import { access } from "node:fs/promises";
|
|
4
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
const CHECKER_SCRIPT = String.raw`
|
|
7
|
+
'use strict';
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = { project: '', etsLoader: '', output: '', cacheDir: '', apiType: 'stageMode', compatibleSdkVersion: 12, permissions: [], hmsSdkEts: '', sysResourceFile: '', files: [] };
|
|
12
|
+
let i = 2;
|
|
13
|
+
while (i < argv.length) {
|
|
14
|
+
if (argv[i] === '--project' && argv[i + 1]) args.project = path.resolve(argv[++i]);
|
|
15
|
+
else if (argv[i] === '--ets-loader' && argv[i + 1]) args.etsLoader = path.resolve(argv[++i]);
|
|
16
|
+
else if (argv[i] === '--output' && argv[i + 1]) args.output = path.resolve(argv[++i]);
|
|
17
|
+
else if (argv[i] === '--cache-dir' && argv[i + 1]) args.cacheDir = path.resolve(argv[++i]);
|
|
18
|
+
else if (argv[i] === '--api-type' && argv[i + 1]) args.apiType = argv[++i];
|
|
19
|
+
else if (argv[i] === '--compatible-sdk-version' && argv[i + 1]) args.compatibleSdkVersion = Number.parseInt(argv[++i], 10);
|
|
20
|
+
else if (argv[i] === '--permission' && argv[i + 1]) args.permissions.push(argv[++i]);
|
|
21
|
+
else if (argv[i] === '--hms-sdk-ets' && argv[i + 1]) args.hmsSdkEts = path.resolve(argv[++i]);
|
|
22
|
+
else if (argv[i] === '--sys-resource-file' && argv[i + 1]) args.sysResourceFile = path.resolve(argv[++i]);
|
|
23
|
+
else if (argv[i] === '--files') {
|
|
24
|
+
i++;
|
|
25
|
+
while (i < argv.length && !argv[i].startsWith('--')) args.files.push(argv[i++]);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
function parseDiagnosticLine(line) {
|
|
33
|
+
const match = line.match(/ArkTS:(ERROR|WARN)\s+File:\s+(.+?):(\d+):(\d+)/);
|
|
34
|
+
if (!match) return null;
|
|
35
|
+
return { severity: match[1].toLowerCase() === 'error' ? 'error' : 'warning', file: match[2], line: Number.parseInt(match[3], 10), column: Number.parseInt(match[4], 10) };
|
|
36
|
+
}
|
|
37
|
+
function parseMessageLine(line) {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
const match = trimmed.match(/^(.+?)\s*\(([@a-z][\w./-]*)\)\s*$/i);
|
|
40
|
+
if (!match) return { message: trimmed, rule: '' };
|
|
41
|
+
return { message: match[1].trim(), rule: match[2] };
|
|
42
|
+
}
|
|
43
|
+
function moduleJsonPath(project) {
|
|
44
|
+
const candidates = [path.join(project, 'entry', 'src', 'main', 'module.json5'), path.join(project, 'src', 'main', 'module.json5'), path.join(project, 'entry', 'module.json5')];
|
|
45
|
+
for (const candidate of candidates) if (fs.existsSync(candidate)) return candidate;
|
|
46
|
+
return path.join(project, 'entry', 'src', 'main', 'module.json5');
|
|
47
|
+
}
|
|
48
|
+
function loadSystemResourceNames(resourceFile) {
|
|
49
|
+
if (!resourceFile || !fs.existsSync(resourceFile)) return null;
|
|
50
|
+
const names = new Set();
|
|
51
|
+
const content = fs.readFileSync(resourceFile, 'utf-8');
|
|
52
|
+
const linePattern = /^id:\d+,\s*'[^']*'\s+'([^']+)'/;
|
|
53
|
+
for (const line of content.split('\n')) {
|
|
54
|
+
const match = line.match(linePattern);
|
|
55
|
+
if (match) names.add(match[1]);
|
|
56
|
+
}
|
|
57
|
+
return names;
|
|
58
|
+
}
|
|
59
|
+
function validateSystemResources(files, resourceFile, project) {
|
|
60
|
+
const validNames = loadSystemResourceNames(resourceFile);
|
|
61
|
+
if (!validNames) return [];
|
|
62
|
+
const diagnostics = [];
|
|
63
|
+
const refPattern = /\$r\(\s*['"]sys\.(media|symbol)\.([^'"]+)['"]\s*\)/g;
|
|
64
|
+
for (const filePath of files) {
|
|
65
|
+
if (!fs.existsSync(filePath)) continue;
|
|
66
|
+
const lines = fs.readFileSync(filePath, 'utf-8').split('\n');
|
|
67
|
+
for (let i = 0; i < lines.length; i++) {
|
|
68
|
+
refPattern.lastIndex = 0;
|
|
69
|
+
let match;
|
|
70
|
+
while ((match = refPattern.exec(lines[i])) !== null) {
|
|
71
|
+
const name = match[2];
|
|
72
|
+
if (!validNames.has(name)) diagnostics.push({ file: path.relative(project, filePath), line: i + 1, column: match.index + 1, severity: 'error', message: "Unknown system resource '" + name + "' for sys." + match[1], rule: 'resource-name-check' });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return diagnostics;
|
|
77
|
+
}
|
|
78
|
+
function main() {
|
|
79
|
+
const args = parseArgs(process.argv);
|
|
80
|
+
const captured = [];
|
|
81
|
+
const capture = (...items) => { captured.push(items.map(String).join(' ')); };
|
|
82
|
+
const origLog = console.log;
|
|
83
|
+
const origError = console.error;
|
|
84
|
+
const origWarn = console.warn;
|
|
85
|
+
try {
|
|
86
|
+
if (args.hmsSdkEts && fs.existsSync(args.hmsSdkEts)) {
|
|
87
|
+
const existing = process.env.externalApiPaths || '';
|
|
88
|
+
process.env.externalApiPaths = existing ? existing + path.delimiter + args.hmsSdkEts : args.hmsSdkEts;
|
|
89
|
+
}
|
|
90
|
+
const etsChecker = require(path.join(args.etsLoader, 'lib', 'ets_checker.js'));
|
|
91
|
+
const mainModule = require(path.join(args.etsLoader, 'main.js'));
|
|
92
|
+
Object.assign(mainModule.partialUpdateConfig, { executeArkTSLinter: true, standardArkTSLinter: true });
|
|
93
|
+
const fileMap = {};
|
|
94
|
+
args.files.forEach((file, index) => { fileMap["file_" + index] = path.isAbsolute(file) ? file : path.resolve(args.project, file); });
|
|
95
|
+
const projectConfig = {
|
|
96
|
+
projectPath: args.project,
|
|
97
|
+
projectRootPath: args.project,
|
|
98
|
+
modulePath: path.join(args.project, 'entry'),
|
|
99
|
+
moduleRootPath: path.join(args.project, 'entry'),
|
|
100
|
+
cachePath: args.cacheDir || path.join(args.project, '.cache', 'arkts-check'),
|
|
101
|
+
aceModuleJsonPath: moduleJsonPath(args.project),
|
|
102
|
+
compileMode: 'esmodule',
|
|
103
|
+
etsLoaderPath: args.etsLoader,
|
|
104
|
+
packageManagerType: 'ohpm',
|
|
105
|
+
packageDir: 'oh_modules',
|
|
106
|
+
runtimeOS: 'OpenHarmony',
|
|
107
|
+
sdkInfo: '5.0.0',
|
|
108
|
+
compatibleSdkVersion: Number.isFinite(args.compatibleSdkVersion) ? args.compatibleSdkVersion : 12,
|
|
109
|
+
compatibleSdkVersionStage: Number.isFinite(args.compatibleSdkVersion) ? args.compatibleSdkVersion : 12,
|
|
110
|
+
apiType: args.apiType,
|
|
111
|
+
permissions: args.permissions,
|
|
112
|
+
bundleType: '',
|
|
113
|
+
compilerTypes: [],
|
|
114
|
+
resolveModulePaths: [],
|
|
115
|
+
};
|
|
116
|
+
fs.mkdirSync(projectConfig.cachePath, { recursive: true });
|
|
117
|
+
if (args.apiType === 'stageMode') {
|
|
118
|
+
process.env.compileMode = 'moduleJson';
|
|
119
|
+
process.env.aceModuleJsonPath = projectConfig.aceModuleJsonPath;
|
|
120
|
+
}
|
|
121
|
+
console.log = capture; console.error = capture; console.warn = capture;
|
|
122
|
+
const logger = { debug: capture, info: capture, warn: capture, error: capture };
|
|
123
|
+
etsChecker.etsStandaloneChecker(fileMap, logger, projectConfig);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
captured.push("Internal error: " + (error && error.message ? error.message : String(error)));
|
|
126
|
+
} finally {
|
|
127
|
+
console.log = origLog; console.error = origError; console.warn = origWarn;
|
|
128
|
+
}
|
|
129
|
+
const diagnostics = [];
|
|
130
|
+
let current = null;
|
|
131
|
+
const lines = captured.flatMap((entry) => entry.split('\n'));
|
|
132
|
+
for (const raw of lines) {
|
|
133
|
+
const line = raw.replace(/\x1b\[[0-9;]*m/g, '');
|
|
134
|
+
const loc = parseDiagnosticLine(line);
|
|
135
|
+
if (loc) { current = loc; continue; }
|
|
136
|
+
if (current && line.trim() && !line.includes('ArkTS:') && !line.includes('For details about')) {
|
|
137
|
+
const message = parseMessageLine(line);
|
|
138
|
+
diagnostics.push({ file: path.relative(args.project, current.file), line: current.line, column: current.column, severity: current.severity, message: message.message, rule: message.rule });
|
|
139
|
+
current = null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const selectedFiles = args.files.map((file) => path.isAbsolute(file) ? file : path.resolve(args.project, file));
|
|
143
|
+
diagnostics.push(...validateSystemResources(selectedFiles, args.sysResourceFile, args.project));
|
|
144
|
+
const errorCount = diagnostics.filter((item) => item.severity === 'error').length;
|
|
145
|
+
const warnCount = diagnostics.filter((item) => item.severity === 'warning').length;
|
|
146
|
+
const payload = { tool_status: captured.some((line) => line.startsWith('Internal error:')) ? 'internal_error' : 'ok', success: errorCount === 0, errors: diagnostics, summary: { errorCount, warnCount }, raw_tail: captured.slice(-20) };
|
|
147
|
+
const rendered = JSON.stringify(payload, null, 2);
|
|
148
|
+
if (args.output) fs.writeFileSync(args.output, rendered + '\n');
|
|
149
|
+
process.stdout.write('\n__ARKTS_PRECHECK_JSON_START__\n');
|
|
150
|
+
process.stdout.write(rendered);
|
|
151
|
+
process.stdout.write('\n__ARKTS_PRECHECK_JSON_END__\n');
|
|
152
|
+
process.exitCode = errorCount > 0 ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
main();
|
|
155
|
+
`;
|
|
156
|
+
async function exists(pathname) {
|
|
157
|
+
try {
|
|
158
|
+
await access(pathname, fsConstants.R_OK);
|
|
159
|
+
return true;
|
|
160
|
+
} catch {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function runCommand(command, args, cwd, timeoutSeconds) {
|
|
165
|
+
return await new Promise((resolve) => {
|
|
166
|
+
const proc = spawn(command, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
167
|
+
let stdout = "";
|
|
168
|
+
let stderr = "";
|
|
169
|
+
let timedOut = false;
|
|
170
|
+
let settled = false;
|
|
171
|
+
const timer = timeoutSeconds && timeoutSeconds > 0 ? setTimeout(() => {
|
|
172
|
+
timedOut = true;
|
|
173
|
+
proc.kill("SIGKILL");
|
|
174
|
+
}, timeoutSeconds * 1e3) : null;
|
|
175
|
+
proc.stdout.on("data", (chunk) => {
|
|
176
|
+
stdout += String(chunk);
|
|
177
|
+
});
|
|
178
|
+
proc.stderr.on("data", (chunk) => {
|
|
179
|
+
stderr += String(chunk);
|
|
180
|
+
});
|
|
181
|
+
proc.on("close", (code) => {
|
|
182
|
+
if (settled) return;
|
|
183
|
+
settled = true;
|
|
184
|
+
if (timer) clearTimeout(timer);
|
|
185
|
+
resolve({ code: code ?? 1, stdout, stderr, timedOut });
|
|
186
|
+
});
|
|
187
|
+
proc.on("error", (error) => {
|
|
188
|
+
if (settled) return;
|
|
189
|
+
settled = true;
|
|
190
|
+
if (timer) clearTimeout(timer);
|
|
191
|
+
stderr += error instanceof Error ? error.message : String(error);
|
|
192
|
+
resolve({ code: 1, stdout, stderr, timedOut });
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function clearArtifacts(...paths) {
|
|
197
|
+
await Promise.all(paths.map(async (pathname) => {
|
|
198
|
+
await rm(pathname, { force: true });
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
function candidateRoots(commandlineTools) {
|
|
202
|
+
const resolved = path.resolve(commandlineTools);
|
|
203
|
+
return [.../* @__PURE__ */ new Set([resolved, path.dirname(resolved)])];
|
|
204
|
+
}
|
|
205
|
+
async function findArktsTool(commandlineTools) {
|
|
206
|
+
if (!commandlineTools) return { etsLoader: null };
|
|
207
|
+
const candidates = [];
|
|
208
|
+
for (const root of candidateRoots(commandlineTools)) {
|
|
209
|
+
candidates.push(
|
|
210
|
+
path.join(root, "sdk", "default", "openharmony", "ets", "build-tools", "ets-loader"),
|
|
211
|
+
path.join(root, "sdk", "openharmony", "ets", "build-tools", "ets-loader"),
|
|
212
|
+
path.join(root, "default", "openharmony", "ets", "build-tools", "ets-loader"),
|
|
213
|
+
path.join(root, "openharmony", "ets", "build-tools", "ets-loader")
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
for (const candidate of candidates) {
|
|
217
|
+
if (await exists(path.join(candidate, "lib", "ets_checker.js")) && await exists(path.join(candidate, "main.js"))) {
|
|
218
|
+
return { etsLoader: candidate };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { etsLoader: null };
|
|
222
|
+
}
|
|
223
|
+
async function findHmsSdkEts(commandlineTools) {
|
|
224
|
+
if (!commandlineTools) return null;
|
|
225
|
+
const candidates = [];
|
|
226
|
+
for (const root of candidateRoots(commandlineTools)) {
|
|
227
|
+
candidates.push(
|
|
228
|
+
path.join(root, "sdk", "default", "hms", "ets"),
|
|
229
|
+
path.join(root, "sdk", "hms", "ets"),
|
|
230
|
+
path.join(root, "default", "hms", "ets"),
|
|
231
|
+
path.join(root, "hms", "ets")
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
for (const candidate of candidates) {
|
|
235
|
+
if (await exists(candidate)) return candidate;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
async function findSystemResourceFile(commandlineTools) {
|
|
240
|
+
if (!commandlineTools) return null;
|
|
241
|
+
const candidates = [];
|
|
242
|
+
for (const root of candidateRoots(commandlineTools)) {
|
|
243
|
+
candidates.push(
|
|
244
|
+
path.join(root, "sdk", "default", "openharmony", "previewer", "common", "resources", "entry", "resources.txt"),
|
|
245
|
+
path.join(root, "sdk", "openharmony", "previewer", "common", "resources", "entry", "resources.txt"),
|
|
246
|
+
path.join(root, "default", "openharmony", "previewer", "common", "resources", "entry", "resources.txt"),
|
|
247
|
+
path.join(root, "openharmony", "previewer", "common", "resources", "entry", "resources.txt")
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
for (const candidate of candidates) {
|
|
251
|
+
if (await exists(candidate)) return candidate;
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
async function collectEtsFiles(projectRoot) {
|
|
256
|
+
const srcDir = path.join(projectRoot, "entry", "src", "main", "ets");
|
|
257
|
+
if (!await exists(srcDir)) return [];
|
|
258
|
+
const ignored = /* @__PURE__ */ new Set(["node_modules", "oh_modules", "build", ".cache"]);
|
|
259
|
+
const files = [];
|
|
260
|
+
async function walk(currentDir) {
|
|
261
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
262
|
+
for (const entry of entries) {
|
|
263
|
+
const absolutePath = path.join(currentDir, entry.name);
|
|
264
|
+
if (absolutePath.split(path.sep).some((part) => ignored.has(part))) continue;
|
|
265
|
+
if (entry.isDirectory()) {
|
|
266
|
+
await walk(absolutePath);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!entry.isFile()) continue;
|
|
270
|
+
if (!absolutePath.endsWith(".ets") || absolutePath.endsWith(".d.ets")) continue;
|
|
271
|
+
files.push(path.resolve(absolutePath));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
await walk(srcDir);
|
|
275
|
+
files.sort();
|
|
276
|
+
return files;
|
|
277
|
+
}
|
|
278
|
+
function stripJson5Comments(text) {
|
|
279
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
280
|
+
}
|
|
281
|
+
function extractJson5String(text, key) {
|
|
282
|
+
const match = new RegExp(`"${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"\\s*:\\s*"([^"]+)"`).exec(text);
|
|
283
|
+
return match?.[1] ?? null;
|
|
284
|
+
}
|
|
285
|
+
function parseApiLevel(value) {
|
|
286
|
+
if (!value) return null;
|
|
287
|
+
const paren = /\((\d+)\)/.exec(value);
|
|
288
|
+
if (paren) return Number.parseInt(paren[1], 10);
|
|
289
|
+
const plain = /\b(\d+)\b/.exec(value);
|
|
290
|
+
return plain ? Number.parseInt(plain[1], 10) : null;
|
|
291
|
+
}
|
|
292
|
+
async function readText(pathname) {
|
|
293
|
+
return await exists(pathname) ? await readFile(pathname, "utf8") : "";
|
|
294
|
+
}
|
|
295
|
+
async function projectCheckerConfig(projectRoot) {
|
|
296
|
+
const rootProfile = stripJson5Comments(await readText(path.join(projectRoot, "build-profile.json5")));
|
|
297
|
+
const entryProfile = stripJson5Comments(await readText(path.join(projectRoot, "entry", "build-profile.json5")));
|
|
298
|
+
const moduleJson = stripJson5Comments(await readText(path.join(projectRoot, "entry", "src", "main", "module.json5")));
|
|
299
|
+
const compatible = parseApiLevel(extractJson5String(rootProfile, "compatibleSdkVersion")) ?? 12;
|
|
300
|
+
const apiType = extractJson5String(entryProfile, "apiType") ?? "stageMode";
|
|
301
|
+
const permissions = [...new Set([...moduleJson.matchAll(/"name"\s*:\s*"(ohos\.permission\.[^"]+)"/g)].map((match) => match[1]))].sort();
|
|
302
|
+
return { compatibleSdkVersion: compatible, apiType, permissions };
|
|
303
|
+
}
|
|
304
|
+
function extractCheckerJson(stdout) {
|
|
305
|
+
const startMarker = "__ARKTS_PRECHECK_JSON_START__";
|
|
306
|
+
const endMarker = "__ARKTS_PRECHECK_JSON_END__";
|
|
307
|
+
const start = stdout.lastIndexOf(startMarker);
|
|
308
|
+
const end = stdout.lastIndexOf(endMarker);
|
|
309
|
+
if (start >= 0 && end > start) {
|
|
310
|
+
const snippet = stdout.slice(start + startMarker.length, end).trim();
|
|
311
|
+
try {
|
|
312
|
+
const parsed = JSON.parse(snippet);
|
|
313
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
314
|
+
} catch {
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const first = stdout.indexOf("{");
|
|
319
|
+
const last = stdout.lastIndexOf("}");
|
|
320
|
+
if (first >= 0 && last > first) {
|
|
321
|
+
const snippet = stdout.slice(first, last + 1);
|
|
322
|
+
try {
|
|
323
|
+
const parsed = JSON.parse(snippet);
|
|
324
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function filterProjectConfiguredDiagnostics(payload, checkerConfig) {
|
|
332
|
+
const permissions = new Set((Array.isArray(checkerConfig.permissions) ? checkerConfig.permissions : []).map(String));
|
|
333
|
+
const compatible = Number(checkerConfig.compatibleSdkVersion ?? 0);
|
|
334
|
+
const filtered = [];
|
|
335
|
+
const suppressed = [];
|
|
336
|
+
for (const rawItem of Array.isArray(payload.errors) ? payload.errors : []) {
|
|
337
|
+
const item = rawItem;
|
|
338
|
+
const message = String(item.message ?? "");
|
|
339
|
+
const permMatches = new Set([...message.matchAll(/ohos\.permission\.[A-Z0-9_]+/g)].map((match) => match[0]));
|
|
340
|
+
if (permMatches.size > 0 && [...permMatches].every((name) => permissions.has(name))) {
|
|
341
|
+
suppressed.push({ ...item, suppressed_reason: "permission already declared in module.json5" });
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const sdkMatch = /SDK version (\d+).*current compatible SDK version is (\d+)/.exec(message);
|
|
345
|
+
if (sdkMatch && compatible >= Number.parseInt(sdkMatch[1], 10)) {
|
|
346
|
+
suppressed.push({ ...item, suppressed_reason: "compatibleSdkVersion from build-profile.json5 is high enough" });
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (message.includes("current Mode is FA") && checkerConfig.apiType === "stageMode") {
|
|
350
|
+
suppressed.push({ ...item, suppressed_reason: "apiType from entry/build-profile.json5 is stageMode" });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
filtered.push(item);
|
|
354
|
+
}
|
|
355
|
+
payload.errors = filtered;
|
|
356
|
+
payload.suppressed_diagnostics = suppressed;
|
|
357
|
+
payload.summary = {
|
|
358
|
+
errorCount: filtered.filter((item) => item.severity === "error").length,
|
|
359
|
+
warnCount: filtered.filter((item) => item.severity === "warning").length
|
|
360
|
+
};
|
|
361
|
+
payload.success = payload.summary.errorCount === 0;
|
|
362
|
+
return payload;
|
|
363
|
+
}
|
|
364
|
+
function validateCheckerPayload(payload) {
|
|
365
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
366
|
+
return "checker emitted a non-object JSON payload";
|
|
367
|
+
}
|
|
368
|
+
if (payload.errors !== void 0 && !Array.isArray(payload.errors)) {
|
|
369
|
+
return "checker emitted a malformed errors payload";
|
|
370
|
+
}
|
|
371
|
+
for (const item of Array.isArray(payload.errors) ? payload.errors : []) {
|
|
372
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
373
|
+
return "checker emitted a malformed errors payload";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function unavailable(reason, elapsedMs = 0) {
|
|
379
|
+
return {
|
|
380
|
+
tool: "arkts_standalone_precheck",
|
|
381
|
+
tool_status: "unavailable",
|
|
382
|
+
evidence_status: "partial",
|
|
383
|
+
success: true,
|
|
384
|
+
mode: "full",
|
|
385
|
+
reason,
|
|
386
|
+
checked_files: [],
|
|
387
|
+
errors: [],
|
|
388
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
389
|
+
elapsed_ms: elapsedMs
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function runArktsStandalonePrecheck(options) {
|
|
393
|
+
const started = Date.now();
|
|
394
|
+
const node = process.execPath;
|
|
395
|
+
if (!node) return unavailable("node executable not found");
|
|
396
|
+
const projectRoot = path.resolve(options.projectRoot);
|
|
397
|
+
const commandlineTools = path.resolve(options.commandlineTools);
|
|
398
|
+
const tool = await findArktsTool(commandlineTools);
|
|
399
|
+
if (!tool.etsLoader) {
|
|
400
|
+
return unavailable(`ets-loader not found under commandline tools: ${commandlineTools}`, Date.now() - started);
|
|
401
|
+
}
|
|
402
|
+
const selectedFiles = options.files?.length ? options.files.map((file) => path.resolve(file)) : await collectEtsFiles(projectRoot);
|
|
403
|
+
const checkedFiles = selectedFiles.filter((file) => file.startsWith(projectRoot + path.sep) && file.endsWith(".ets")).map((file) => path.relative(projectRoot, file).replace(/\\/g, "/"));
|
|
404
|
+
const checkerConfig = await projectCheckerConfig(projectRoot);
|
|
405
|
+
const hmsSdkEts = await findHmsSdkEts(commandlineTools);
|
|
406
|
+
const systemResourceFile = await findSystemResourceFile(commandlineTools);
|
|
407
|
+
if (!checkedFiles.length) {
|
|
408
|
+
return {
|
|
409
|
+
tool: "arkts_standalone_precheck",
|
|
410
|
+
tool_status: "ok",
|
|
411
|
+
success: true,
|
|
412
|
+
mode: "full",
|
|
413
|
+
ets_loader: tool.etsLoader,
|
|
414
|
+
project_config: checkerConfig,
|
|
415
|
+
checked_files: [],
|
|
416
|
+
errors: [],
|
|
417
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
418
|
+
elapsed_ms: Date.now() - started
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
await mkdir(options.outputDir, { recursive: true });
|
|
422
|
+
const runnerPath = path.join(options.outputDir, "arkts-standalone-check-runner.js");
|
|
423
|
+
const resultPath = path.join(options.outputDir, "arkts-raw-diagnostics.json");
|
|
424
|
+
const stdoutPath = path.join(options.outputDir, "arkts-checker-stdout.txt");
|
|
425
|
+
const stderrPath = path.join(options.outputDir, "arkts-checker-stderr.txt");
|
|
426
|
+
await clearArtifacts(resultPath, stdoutPath, stderrPath);
|
|
427
|
+
await writeFile(runnerPath, CHECKER_SCRIPT, "utf8");
|
|
428
|
+
const cacheDir = path.join(options.outputDir, "arkts-check-cache");
|
|
429
|
+
await mkdir(cacheDir, { recursive: true });
|
|
430
|
+
const args = [
|
|
431
|
+
runnerPath,
|
|
432
|
+
"--project",
|
|
433
|
+
projectRoot,
|
|
434
|
+
"--ets-loader",
|
|
435
|
+
tool.etsLoader,
|
|
436
|
+
"--output",
|
|
437
|
+
resultPath,
|
|
438
|
+
"--api-type",
|
|
439
|
+
String(checkerConfig.apiType),
|
|
440
|
+
"--compatible-sdk-version",
|
|
441
|
+
String(checkerConfig.compatibleSdkVersion)
|
|
442
|
+
];
|
|
443
|
+
for (const permission of Array.isArray(checkerConfig.permissions) ? checkerConfig.permissions.map(String) : []) {
|
|
444
|
+
args.push("--permission", permission);
|
|
445
|
+
}
|
|
446
|
+
if (hmsSdkEts) args.push("--hms-sdk-ets", hmsSdkEts);
|
|
447
|
+
if (systemResourceFile) args.push("--sys-resource-file", systemResourceFile);
|
|
448
|
+
args.push("--cache-dir", cacheDir, "--files", ...checkedFiles);
|
|
449
|
+
const result = await runCommand(node, args, projectRoot, options.timeoutSeconds ?? 120);
|
|
450
|
+
if (result.timedOut) {
|
|
451
|
+
return unavailable(`arkts standalone checker timed out after ${options.timeoutSeconds ?? 120}s`, Date.now() - started);
|
|
452
|
+
}
|
|
453
|
+
await writeFile(stdoutPath, result.stdout || "", "utf8");
|
|
454
|
+
await writeFile(stderrPath, result.stderr || "", "utf8");
|
|
455
|
+
let payload = null;
|
|
456
|
+
let evidenceStatus = "partial";
|
|
457
|
+
if (await exists(resultPath)) {
|
|
458
|
+
try {
|
|
459
|
+
const parsed = JSON.parse(await readFile(resultPath, "utf8"));
|
|
460
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
461
|
+
payload = parsed;
|
|
462
|
+
evidenceStatus = "raw_structured";
|
|
463
|
+
}
|
|
464
|
+
} catch {
|
|
465
|
+
payload = null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!payload) {
|
|
469
|
+
payload = extractCheckerJson(result.stdout || "");
|
|
470
|
+
if (payload) evidenceStatus = "derived_from_text";
|
|
471
|
+
}
|
|
472
|
+
if (!payload) {
|
|
473
|
+
return {
|
|
474
|
+
tool: "arkts_standalone_precheck",
|
|
475
|
+
tool_status: "internal_error",
|
|
476
|
+
evidence_status: "partial",
|
|
477
|
+
success: true,
|
|
478
|
+
mode: "full",
|
|
479
|
+
reason: "checker did not emit JSON",
|
|
480
|
+
stdout_tail: (result.stdout || "").slice(-4e3),
|
|
481
|
+
stderr_tail: (result.stderr || "").slice(-4e3),
|
|
482
|
+
checked_files: checkedFiles,
|
|
483
|
+
errors: [],
|
|
484
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
485
|
+
elapsed_ms: Date.now() - started
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const payloadError = validateCheckerPayload(payload);
|
|
489
|
+
if (payloadError) {
|
|
490
|
+
return {
|
|
491
|
+
tool: "arkts_standalone_precheck",
|
|
492
|
+
tool_status: "internal_error",
|
|
493
|
+
evidence_status: "partial",
|
|
494
|
+
success: true,
|
|
495
|
+
mode: "full",
|
|
496
|
+
reason: payloadError,
|
|
497
|
+
checked_files: checkedFiles,
|
|
498
|
+
errors: [],
|
|
499
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
500
|
+
elapsed_ms: Date.now() - started,
|
|
501
|
+
artifact_paths: {
|
|
502
|
+
raw_json: resultPath,
|
|
503
|
+
stdout: stdoutPath,
|
|
504
|
+
stderr: stderrPath,
|
|
505
|
+
runner: runnerPath
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
payload.tool = "arkts_standalone_precheck";
|
|
510
|
+
payload.tool_status = payload.tool_status ?? "ok";
|
|
511
|
+
payload.success = payload.success ?? result.code === 0;
|
|
512
|
+
payload.evidence_status = evidenceStatus;
|
|
513
|
+
payload.mode = "full";
|
|
514
|
+
payload.ets_loader = tool.etsLoader;
|
|
515
|
+
payload.project_config = checkerConfig;
|
|
516
|
+
payload.checked_files = checkedFiles;
|
|
517
|
+
payload.elapsed_ms = Date.now() - started;
|
|
518
|
+
payload = filterProjectConfiguredDiagnostics(payload, checkerConfig);
|
|
519
|
+
payload.artifact_paths = {
|
|
520
|
+
raw_json: resultPath,
|
|
521
|
+
stdout: stdoutPath,
|
|
522
|
+
stderr: stderrPath,
|
|
523
|
+
runner: runnerPath
|
|
524
|
+
};
|
|
525
|
+
if (result.stderr) payload.stderr_tail = result.stderr.slice(-4e3);
|
|
526
|
+
return payload;
|
|
527
|
+
}
|
|
528
|
+
export {
|
|
529
|
+
extractCheckerJson,
|
|
530
|
+
findArktsTool,
|
|
531
|
+
findHmsSdkEts,
|
|
532
|
+
findSystemResourceFile,
|
|
533
|
+
runArktsStandalonePrecheck
|
|
534
|
+
};
|