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,926 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { constants as fsConstants } from "node:fs";
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
6
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { findArktsTool, findHmsSdkEts, findSystemResourceFile, runArktsStandalonePrecheck } from "./arkts.mjs";
|
|
11
|
+
import { findCodelinterTool, runCodelinterDiffScan } from "./codelinter.mjs";
|
|
12
|
+
const PROJECT_CONFIG_NAME = ".hmos-precheck.json";
|
|
13
|
+
const USER_CONFIG_BASENAME = "config.json";
|
|
14
|
+
const DEFAULT_PROFILES = ["arkts-diff", "codelinter-diff"];
|
|
15
|
+
const SUPPORTED_PROFILES = new Set(DEFAULT_PROFILES);
|
|
16
|
+
function isObject(value) {
|
|
17
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
async function exists(pathname) {
|
|
20
|
+
try {
|
|
21
|
+
await access(pathname, fsConstants.R_OK);
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function loadJson(pathname) {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(await readFile(pathname, "utf8"));
|
|
30
|
+
return isObject(parsed) ? parsed : null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function expandHome(input) {
|
|
36
|
+
if (!input) return input;
|
|
37
|
+
if (input === "~") return os.homedir();
|
|
38
|
+
if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(os.homedir(), input.slice(2));
|
|
39
|
+
return input;
|
|
40
|
+
}
|
|
41
|
+
function defaultUserConfigPath() {
|
|
42
|
+
if (process.platform === "darwin") {
|
|
43
|
+
return path.join(os.homedir(), "Library", "Application Support", "hmos-precheck", USER_CONFIG_BASENAME);
|
|
44
|
+
}
|
|
45
|
+
if (process.platform.startsWith("win")) {
|
|
46
|
+
const appdata = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
47
|
+
return path.join(appdata, "hmos-precheck", USER_CONFIG_BASENAME);
|
|
48
|
+
}
|
|
49
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
50
|
+
return path.join(xdg, "hmos-precheck", USER_CONFIG_BASENAME);
|
|
51
|
+
}
|
|
52
|
+
function defaultHomeTransConfigPath() {
|
|
53
|
+
return path.join(os.homedir(), ".hometrans", "config.json");
|
|
54
|
+
}
|
|
55
|
+
function commandlineToolsHelp() {
|
|
56
|
+
return "Run `hmos-precheck init --scope user --commandline-tools /abs/path/to/command-line-tools`, or pass `--commandline-tools`.";
|
|
57
|
+
}
|
|
58
|
+
async function hasSdkShape(candidate) {
|
|
59
|
+
const probes = [
|
|
60
|
+
path.join(candidate, "default", "openharmony", "toolchains"),
|
|
61
|
+
path.join(candidate, "default", "openharmony", "ets"),
|
|
62
|
+
path.join(candidate, "default", "hms", "ets"),
|
|
63
|
+
path.join(candidate, "default", "hms", "toolchains")
|
|
64
|
+
];
|
|
65
|
+
for (const probe of probes) {
|
|
66
|
+
if (await exists(probe)) return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
async function normalizeCommandlineToolsCandidate(candidate) {
|
|
71
|
+
const resolved = path.resolve(expandHome(candidate));
|
|
72
|
+
if (await hasSdkShape(resolved)) return { sdkRoot: resolved, toolRoot: path.dirname(resolved) };
|
|
73
|
+
const nested = path.join(resolved, "sdk");
|
|
74
|
+
if (await hasSdkShape(nested)) return { sdkRoot: nested, toolRoot: resolved };
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
async function resolveCommonCommandlineTools() {
|
|
78
|
+
const candidates = [];
|
|
79
|
+
if (process.platform.startsWith("win")) {
|
|
80
|
+
for (const root of [process.env.LOCALAPPDATA, process.env.PROGRAMFILES, process.env["PROGRAMFILES(X86)"], process.env.USERPROFILE].filter(Boolean)) {
|
|
81
|
+
candidates.push(
|
|
82
|
+
path.join(root, "HarmonyOS", "command-line-tools"),
|
|
83
|
+
path.join(root, "Huawei", "HarmonyOS", "command-line-tools"),
|
|
84
|
+
path.join(root, "OpenHarmony", "command-line-tools")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
candidates.push(path.join(os.homedir(), "HarmonyOS", "command-line-tools"));
|
|
88
|
+
} else if (process.platform === "darwin") {
|
|
89
|
+
candidates.push("/Applications/HarmonyOS/command-line-tools", path.join(os.homedir(), "HarmonyOS", "command-line-tools"));
|
|
90
|
+
} else {
|
|
91
|
+
candidates.push("/opt/harmonyos/command-line-tools", path.join(os.homedir(), "HarmonyOS", "command-line-tools"), path.join(os.homedir(), "harmonyos", "command-line-tools"));
|
|
92
|
+
}
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
const info = await normalizeCommandlineToolsCandidate(candidate);
|
|
95
|
+
if (info) return info.sdkRoot;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
async function resolveCommandlineToolsInfo(options) {
|
|
100
|
+
const env = options.env ?? process.env;
|
|
101
|
+
const hint = commandlineToolsHelp();
|
|
102
|
+
const wrap = async (candidate, source) => {
|
|
103
|
+
if (!candidate) return null;
|
|
104
|
+
const info = await normalizeCommandlineToolsCandidate(candidate);
|
|
105
|
+
if (!info) {
|
|
106
|
+
return {
|
|
107
|
+
path: path.resolve(expandHome(candidate)),
|
|
108
|
+
toolRoot: null,
|
|
109
|
+
sdkRoot: null,
|
|
110
|
+
source,
|
|
111
|
+
configured: true,
|
|
112
|
+
available: false,
|
|
113
|
+
message: `Configured HarmonyOS command-line tools path is unavailable: ${path.resolve(expandHome(candidate))}`,
|
|
114
|
+
hint
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
path: info.sdkRoot,
|
|
119
|
+
toolRoot: info.toolRoot,
|
|
120
|
+
sdkRoot: info.sdkRoot,
|
|
121
|
+
source,
|
|
122
|
+
configured: true,
|
|
123
|
+
available: true,
|
|
124
|
+
message: null,
|
|
125
|
+
hint
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const explicit = await wrap(options.explicitPath ?? null, "cli");
|
|
129
|
+
if (explicit) return explicit;
|
|
130
|
+
const sdkResult = await wrap(env.DEVECO_SDK_HOME ?? null, "env");
|
|
131
|
+
if (sdkResult) return sdkResult;
|
|
132
|
+
if (options.projectRoot) {
|
|
133
|
+
const projectConfig = await loadJson(path.join(options.projectRoot, PROJECT_CONFIG_NAME));
|
|
134
|
+
const value = isObject(projectConfig?.toolchain) ? projectConfig.toolchain.commandline_tools : void 0;
|
|
135
|
+
const projectResult = await wrap(typeof value === "string" ? value : null, "project_config");
|
|
136
|
+
if (projectResult) return projectResult;
|
|
137
|
+
}
|
|
138
|
+
const homeTransConfig = await loadJson(defaultHomeTransConfigPath());
|
|
139
|
+
const homeTransValue = isObject(homeTransConfig?.env) ? homeTransConfig.env.DEVECO_SDK_HOME : void 0;
|
|
140
|
+
const homeTransResult = await wrap(typeof homeTransValue === "string" ? homeTransValue : null, "hometrans_config");
|
|
141
|
+
if (homeTransResult) return homeTransResult;
|
|
142
|
+
const userConfig = await loadJson(options.userConfigPath ?? defaultUserConfigPath());
|
|
143
|
+
const userValue = isObject(userConfig?.toolchain) ? userConfig.toolchain.commandline_tools : void 0;
|
|
144
|
+
const userResult = await wrap(typeof userValue === "string" ? userValue : null, "user_config");
|
|
145
|
+
if (userResult) return userResult;
|
|
146
|
+
const auto = await resolveCommonCommandlineTools();
|
|
147
|
+
if (auto) {
|
|
148
|
+
return { path: auto, toolRoot: path.dirname(auto), sdkRoot: auto, source: "auto", configured: true, available: true, message: null, hint };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
path: null,
|
|
152
|
+
toolRoot: null,
|
|
153
|
+
sdkRoot: null,
|
|
154
|
+
source: "missing",
|
|
155
|
+
configured: false,
|
|
156
|
+
available: false,
|
|
157
|
+
message: "HarmonyOS command-line tools are not configured. ArkTS-dependent checks will be skipped.",
|
|
158
|
+
hint
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function resolveProfiles(options) {
|
|
162
|
+
const normalize = (values) => {
|
|
163
|
+
if (!values) return null;
|
|
164
|
+
const raw = Array.isArray(values) ? values : values.split(",");
|
|
165
|
+
const deduped = [...new Set(raw.map((item) => item.trim()).filter(Boolean))];
|
|
166
|
+
return deduped.length ? deduped : null;
|
|
167
|
+
};
|
|
168
|
+
const explicit = normalize(options.explicitProfiles ?? null);
|
|
169
|
+
if (explicit) return explicit;
|
|
170
|
+
if (options.projectRoot) {
|
|
171
|
+
const projectConfig = await loadJson(path.join(options.projectRoot, PROJECT_CONFIG_NAME));
|
|
172
|
+
const value2 = isObject(projectConfig?.run) ? projectConfig.run.profiles : void 0;
|
|
173
|
+
const fromProject = normalize(Array.isArray(value2) ? value2.map(String) : typeof value2 === "string" ? value2 : null);
|
|
174
|
+
if (fromProject) return fromProject;
|
|
175
|
+
}
|
|
176
|
+
const userConfig = await loadJson(options.userConfigPath ?? defaultUserConfigPath());
|
|
177
|
+
const value = isObject(userConfig?.run) ? userConfig.run.profiles : void 0;
|
|
178
|
+
return normalize(Array.isArray(value) ? value.map(String) : typeof value === "string" ? value : null);
|
|
179
|
+
}
|
|
180
|
+
async function initConfig(targetPath, commandlineTools) {
|
|
181
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
182
|
+
await writeFile(targetPath, `${JSON.stringify({ toolchain: { commandline_tools: path.resolve(commandlineTools) } }, null, 2)}
|
|
183
|
+
`, "utf8");
|
|
184
|
+
return targetPath;
|
|
185
|
+
}
|
|
186
|
+
async function writeJson(pathname, payload) {
|
|
187
|
+
await mkdir(path.dirname(pathname), { recursive: true });
|
|
188
|
+
await writeFile(pathname, `${JSON.stringify(payload, null, 2)}
|
|
189
|
+
`, "utf8");
|
|
190
|
+
}
|
|
191
|
+
async function runGit(projectRoot, args) {
|
|
192
|
+
const { spawn } = await import("node:child_process");
|
|
193
|
+
return await new Promise((resolve) => {
|
|
194
|
+
const proc = spawn("git", args, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
|
|
195
|
+
let stdout = "";
|
|
196
|
+
let stderr = "";
|
|
197
|
+
proc.stdout.on("data", (chunk) => {
|
|
198
|
+
stdout += String(chunk);
|
|
199
|
+
});
|
|
200
|
+
proc.stderr.on("data", (chunk) => {
|
|
201
|
+
stderr += String(chunk);
|
|
202
|
+
});
|
|
203
|
+
proc.on("close", (code) => resolve({ code: code ?? 1, stdout, stderr }));
|
|
204
|
+
proc.on("error", (error) => resolve({ code: 1, stdout, stderr: error instanceof Error ? error.message : String(error) }));
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function prepareRunOutputDir(outputDir, runId) {
|
|
208
|
+
return path.join(outputDir, `run-${runId}`);
|
|
209
|
+
}
|
|
210
|
+
function backendCrashPayload(profile, error) {
|
|
211
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
212
|
+
if (profile === "arkts-diff") {
|
|
213
|
+
return {
|
|
214
|
+
tool: "arkts_standalone_precheck",
|
|
215
|
+
tool_status: "unavailable",
|
|
216
|
+
evidence_status: "partial",
|
|
217
|
+
success: true,
|
|
218
|
+
mode: "diff",
|
|
219
|
+
checked_files: [],
|
|
220
|
+
errors: [],
|
|
221
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
222
|
+
reason,
|
|
223
|
+
scope: "diff",
|
|
224
|
+
changed_files_only: true,
|
|
225
|
+
changed_line_ranges: {},
|
|
226
|
+
failure_rule: "backend-crashed"
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
tool: "codelinter",
|
|
231
|
+
tool_status: "unavailable",
|
|
232
|
+
evidence_status: "partial",
|
|
233
|
+
success: true,
|
|
234
|
+
checked_files: [],
|
|
235
|
+
diagnostics: [],
|
|
236
|
+
summary: { errorCount: 0, warnCount: 0, suggestionCount: 0 },
|
|
237
|
+
reason,
|
|
238
|
+
scope: "diff",
|
|
239
|
+
changed_files_only: true,
|
|
240
|
+
changed_line_ranges: {},
|
|
241
|
+
failure_rule: "backend-crashed"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function repairHint(item) {
|
|
245
|
+
const rule = String(item.rule ?? "");
|
|
246
|
+
const file = String(item.file ?? "the flagged file");
|
|
247
|
+
const line = item.line ?? "?";
|
|
248
|
+
const message = String(item.message ?? "");
|
|
249
|
+
if (item.profile === "arkts-diff") return `Edit the ArkTS code at ${file}:${line} to satisfy compiler rule \`${rule}\`. Resolve the exact reported error: ${message}`;
|
|
250
|
+
if (item.profile === "codelinter-diff") return `Edit ${file}:${line} to satisfy linter rule \`${rule}\`. Apply the rule-specific fix implied by: ${message}`;
|
|
251
|
+
return `Edit ${file}:${line} to remove the blocking condition reported as \`${rule}\`: ${message}`;
|
|
252
|
+
}
|
|
253
|
+
function warningHint(item) {
|
|
254
|
+
const profile = String(item.profile ?? "");
|
|
255
|
+
const rule = String(item.rule ?? "");
|
|
256
|
+
if (rule === "git-diff-failed") return "Use a valid git base/head ref and ensure the repository has the required history.";
|
|
257
|
+
if (rule === "invalid-profile") return "Remove unsupported profiles from CLI/config and use only supported profiles.";
|
|
258
|
+
if (profile === "arkts-diff") return "Configure HarmonyOS command-line tools if you need ArkTS validation.";
|
|
259
|
+
if (profile === "codelinter-diff") return "Install or configure codelinter if you need lint validation.";
|
|
260
|
+
return "Restore the skipped validation path if you need full confidence from this check run.";
|
|
261
|
+
}
|
|
262
|
+
function warningIssue(item) {
|
|
263
|
+
const profile = String(item.profile ?? "validation");
|
|
264
|
+
const message = String(item.message ?? "check unavailable");
|
|
265
|
+
const label = profile.endsWith("-diff") ? profile.slice(0, -5) : profile;
|
|
266
|
+
const rule = String(item.rule ?? "");
|
|
267
|
+
const problem = ["git-diff-failed", "invalid-profile"].includes(rule) ? message : `${label} check skipped: ${message}`;
|
|
268
|
+
return {
|
|
269
|
+
kind: rule || "environment-issue",
|
|
270
|
+
problem,
|
|
271
|
+
fix: warningHint(item)
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
async function fileExists(pathname) {
|
|
275
|
+
return Boolean(pathname) && await exists(String(pathname));
|
|
276
|
+
}
|
|
277
|
+
async function sanitizeIssueEvidence(evidence) {
|
|
278
|
+
if (!evidence) return null;
|
|
279
|
+
let status = String(evidence.status ?? "partial");
|
|
280
|
+
const rawFile = typeof evidence.raw_file === "string" ? evidence.raw_file : null;
|
|
281
|
+
if (rawFile && await fileExists(rawFile)) {
|
|
282
|
+
return { status, raw_file: rawFile };
|
|
283
|
+
}
|
|
284
|
+
if (status === "raw_structured") status = "partial";
|
|
285
|
+
return { status };
|
|
286
|
+
}
|
|
287
|
+
function dedupeIssues(issues) {
|
|
288
|
+
const deduped = [];
|
|
289
|
+
const seen = /* @__PURE__ */ new Set();
|
|
290
|
+
for (const issue of issues) {
|
|
291
|
+
const key = JSON.stringify([
|
|
292
|
+
issue.kind,
|
|
293
|
+
issue.file,
|
|
294
|
+
issue.line,
|
|
295
|
+
issue.column,
|
|
296
|
+
issue.rule,
|
|
297
|
+
issue.problem,
|
|
298
|
+
issue.fix
|
|
299
|
+
]);
|
|
300
|
+
if (seen.has(key)) continue;
|
|
301
|
+
seen.add(key);
|
|
302
|
+
deduped.push(issue);
|
|
303
|
+
}
|
|
304
|
+
return deduped;
|
|
305
|
+
}
|
|
306
|
+
async function existingRawFile(backend) {
|
|
307
|
+
const rawFile = typeof backend.artifact_paths?.raw_json === "string" ? backend.artifact_paths.raw_json : null;
|
|
308
|
+
return rawFile && await fileExists(rawFile) ? rawFile : null;
|
|
309
|
+
}
|
|
310
|
+
async function effectiveBackendEvidenceStatus(backend) {
|
|
311
|
+
const status = String(backend.evidence_status ?? "partial");
|
|
312
|
+
if (status === "raw_structured" && !await existingRawFile(backend)) return "partial";
|
|
313
|
+
return status;
|
|
314
|
+
}
|
|
315
|
+
async function repairIssue(item) {
|
|
316
|
+
const issue = {
|
|
317
|
+
kind: String(item.rule ?? "code-issue"),
|
|
318
|
+
file: item.file ?? null,
|
|
319
|
+
line: item.line ?? null,
|
|
320
|
+
column: item.column ?? null,
|
|
321
|
+
rule: item.rule ?? null,
|
|
322
|
+
problem: item.message ?? "",
|
|
323
|
+
fix: repairHint(item)
|
|
324
|
+
};
|
|
325
|
+
const evidence = await sanitizeIssueEvidence(isObject(item.evidence) ? item.evidence : null);
|
|
326
|
+
if (evidence) issue.evidence = evidence;
|
|
327
|
+
return issue;
|
|
328
|
+
}
|
|
329
|
+
function repairSummary(hardFindings) {
|
|
330
|
+
const fileCount = new Set(hardFindings.map((item) => item.file).filter(Boolean)).size;
|
|
331
|
+
return `Fix ${hardFindings.length} blocking issue(s) across ${fileCount || 1} file(s).`;
|
|
332
|
+
}
|
|
333
|
+
function blockingEnvironmentSummary(count) {
|
|
334
|
+
return `Resolve ${count} blocking environment issue(s).`;
|
|
335
|
+
}
|
|
336
|
+
function degradedSummary(count) {
|
|
337
|
+
return `No blocking code issues, but ${count} profile(s) were not fully checked.`;
|
|
338
|
+
}
|
|
339
|
+
function overallStatus(summaryCounts, backendResults) {
|
|
340
|
+
if (summaryCounts.hard_fixable) return "fail";
|
|
341
|
+
const hasCheckedBackend = Object.values(backendResults).some((backend) => String(backend?.tool_status ?? "") === "ok");
|
|
342
|
+
if (summaryCounts.not_checked) return hasCheckedBackend ? "warn" : "fail";
|
|
343
|
+
return "pass";
|
|
344
|
+
}
|
|
345
|
+
async function resultPayload(payload) {
|
|
346
|
+
const rawFiles = (await Promise.all(Object.values(payload.backend_results).map((backend) => existingRawFile(backend)))).filter((value) => Boolean(value));
|
|
347
|
+
const evidenceStatuses = new Set(await Promise.all(Object.values(payload.backend_results).map((backend) => effectiveBackendEvidenceStatus(backend))));
|
|
348
|
+
let evidenceStatus = "partial";
|
|
349
|
+
if (!evidenceStatuses.size) {
|
|
350
|
+
evidenceStatus = "partial";
|
|
351
|
+
} else if (evidenceStatuses.size === 1 && evidenceStatuses.has("raw_structured")) {
|
|
352
|
+
evidenceStatus = "raw_structured";
|
|
353
|
+
} else if (evidenceStatuses.has("derived_from_text")) {
|
|
354
|
+
evidenceStatus = "derived_from_text";
|
|
355
|
+
}
|
|
356
|
+
const hardFindings = payload.findings.filter((item) => item.classification === "hard_fixable");
|
|
357
|
+
const blockingFindings = payload.findings.filter((item) => item.classification === "not_checked");
|
|
358
|
+
const blockingIssues = dedupeIssues(blockingFindings.map((item) => warningIssue(item)));
|
|
359
|
+
if (hardFindings.length) {
|
|
360
|
+
const issues = dedupeIssues((await Promise.all(hardFindings.map((item) => repairIssue(item)))).concat(blockingIssues));
|
|
361
|
+
let summary = repairSummary(hardFindings);
|
|
362
|
+
if (blockingIssues.length) summary = `${summary} ${blockingEnvironmentSummary(blockingIssues.length)}`;
|
|
363
|
+
return {
|
|
364
|
+
run_id: payload.run_id,
|
|
365
|
+
summary,
|
|
366
|
+
evidence_status: evidenceStatus,
|
|
367
|
+
raw_files: rawFiles,
|
|
368
|
+
issues
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (payload.status === "warn") {
|
|
372
|
+
return {
|
|
373
|
+
ok: true,
|
|
374
|
+
degraded: true,
|
|
375
|
+
run_id: payload.run_id,
|
|
376
|
+
status: "warn",
|
|
377
|
+
summary: degradedSummary(blockingIssues.length || 1),
|
|
378
|
+
evidence_status: evidenceStatus,
|
|
379
|
+
raw_files: rawFiles,
|
|
380
|
+
issues: blockingIssues
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (payload.status === "pass") return { ok: true };
|
|
384
|
+
return {
|
|
385
|
+
run_id: payload.run_id,
|
|
386
|
+
summary: blockingEnvironmentSummary(blockingIssues.length || 1),
|
|
387
|
+
evidence_status: evidenceStatus,
|
|
388
|
+
raw_files: rawFiles,
|
|
389
|
+
issues: blockingIssues
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function compactResult(payload) {
|
|
393
|
+
const hasHard = payload.findings.some((item) => item.classification === "hard_fixable");
|
|
394
|
+
if (hasHard || payload.status === "fail") {
|
|
395
|
+
const result = { ok: false };
|
|
396
|
+
if (payload.artifact_paths.json) result.read_file = payload.artifact_paths.json;
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
if (payload.status === "warn") {
|
|
400
|
+
const result = { ok: true, degraded: true };
|
|
401
|
+
if (payload.artifact_paths.json) result.read_file = payload.artifact_paths.json;
|
|
402
|
+
return result;
|
|
403
|
+
}
|
|
404
|
+
return { ok: true };
|
|
405
|
+
}
|
|
406
|
+
async function writeSummary(payload, pathname) {
|
|
407
|
+
const findings = Array.isArray(payload.findings) ? payload.findings : [];
|
|
408
|
+
const hard = findings.filter((item) => item.classification === "hard_fixable");
|
|
409
|
+
const lines = [
|
|
410
|
+
"# HMOS Precheck Summary",
|
|
411
|
+
"",
|
|
412
|
+
`- ok: ${String(payload.ok ?? false)}`,
|
|
413
|
+
`- compact_summary: ${String(payload.compact_summary ?? "")}`,
|
|
414
|
+
`- profiles: ${Array.isArray(payload.profiles) ? payload.profiles.join(", ") : ""}`,
|
|
415
|
+
`- hard_fixable: ${String(payload.summary_counts?.hard_fixable ?? 0)}`,
|
|
416
|
+
`- strong_warn_hidden_from_fixer: ${String(payload.summary_counts?.strong_warn ?? 0)}`,
|
|
417
|
+
`- not_checked: ${String(payload.summary_counts?.not_checked ?? 0)}`,
|
|
418
|
+
"",
|
|
419
|
+
"## Fixer Findings"
|
|
420
|
+
];
|
|
421
|
+
if (!hard.length) lines.push("- none");
|
|
422
|
+
for (const item of hard.slice(0, 40)) {
|
|
423
|
+
lines.push(`- [${String(item.profile ?? "")}] ${String(item.file ?? "<unknown>")}:${String(item.line ?? "?")}:${String(item.column ?? "?")} [${String(item.rule ?? "no-rule")}] ${String(item.message ?? "")}`);
|
|
424
|
+
}
|
|
425
|
+
await writeFile(pathname, `${lines.join("\n")}
|
|
426
|
+
`, "utf8");
|
|
427
|
+
}
|
|
428
|
+
async function writeArktsSummary(payload, pathname, maxErrors = 30) {
|
|
429
|
+
const errors = (Array.isArray(payload.errors) ? payload.errors : []).filter((item) => item.severity === "error");
|
|
430
|
+
const warnings = (Array.isArray(payload.errors) ? payload.errors : []).filter((item) => item.severity === "warning");
|
|
431
|
+
const suppressed = Array.isArray(payload.suppressed_diagnostics) ? payload.suppressed_diagnostics : [];
|
|
432
|
+
const lines = [
|
|
433
|
+
"# ArkTS Precheck Fix Summary",
|
|
434
|
+
"",
|
|
435
|
+
`- status: ${String(payload.tool_status ?? "")}`,
|
|
436
|
+
`- checked_files: ${Array.isArray(payload.checked_files) ? payload.checked_files.length : 0}`,
|
|
437
|
+
`- errors: ${errors.length}`,
|
|
438
|
+
`- warnings_hidden_from_fixer: ${warnings.length}`,
|
|
439
|
+
`- suppressed_project_config_diagnostics: ${suppressed.length}`,
|
|
440
|
+
"",
|
|
441
|
+
"## Blocking Errors"
|
|
442
|
+
];
|
|
443
|
+
if (!errors.length) lines.push("- none");
|
|
444
|
+
for (const item of errors.slice(0, maxErrors)) {
|
|
445
|
+
const issue = item;
|
|
446
|
+
lines.push(`- ${String(issue.file ?? "<unknown>")}:${String(issue.line ?? "?")}:${String(issue.column ?? "?")} [${String(issue.rule ?? "no-rule")}] ${String(issue.message ?? "")}`);
|
|
447
|
+
}
|
|
448
|
+
await writeFile(pathname, `${lines.join("\n")}
|
|
449
|
+
`, "utf8");
|
|
450
|
+
}
|
|
451
|
+
async function runArktsDiff(options) {
|
|
452
|
+
const precheckOutputDir = path.join(options.outputDir, "arkts-precheck");
|
|
453
|
+
await mkdir(precheckOutputDir, { recursive: true });
|
|
454
|
+
const resultPath = path.join(options.outputDir, "arkts-precheck-result.json");
|
|
455
|
+
const summaryPath = path.join(options.outputDir, "arkts-precheck-fix-summary.md");
|
|
456
|
+
const rawOutput = {
|
|
457
|
+
raw_json: path.join(precheckOutputDir, "arkts-raw-diagnostics.json"),
|
|
458
|
+
runner: path.join(precheckOutputDir, "arkts-standalone-check-runner.js"),
|
|
459
|
+
stdout: path.join(precheckOutputDir, "arkts-checker-stdout.txt"),
|
|
460
|
+
stderr: path.join(precheckOutputDir, "arkts-checker-stderr.txt")
|
|
461
|
+
};
|
|
462
|
+
let payload;
|
|
463
|
+
try {
|
|
464
|
+
const diffArgs = ["diff", "--name-only", options.baseRef];
|
|
465
|
+
if (options.headRef) diffArgs.push(options.headRef);
|
|
466
|
+
diffArgs.push("--", "*.ets");
|
|
467
|
+
const diffResult = await runGit(options.projectRoot, diffArgs);
|
|
468
|
+
if (diffResult.code !== 0) {
|
|
469
|
+
throw new Error(`git diff failed: ${(diffResult.stderr || diffResult.stdout || `exit_code=${diffResult.code}`).trim()}`);
|
|
470
|
+
}
|
|
471
|
+
const checkedFiles = diffResult.stdout.split(/\r?\n/).filter((line) => line.endsWith(".ets")).filter(Boolean).map((line) => line.replace(/\\/g, "/")).filter((line, index, all) => all.indexOf(line) === index);
|
|
472
|
+
if (options.headRef === null) {
|
|
473
|
+
const untrackedResult = await runGit(options.projectRoot, ["ls-files", "--others", "--exclude-standard", "--", "*.ets"]);
|
|
474
|
+
if (untrackedResult.code !== 0) {
|
|
475
|
+
throw new Error(`git ls-files failed: ${(untrackedResult.stderr || untrackedResult.stdout || `exit_code=${untrackedResult.code}`).trim()}`);
|
|
476
|
+
}
|
|
477
|
+
for (const line of untrackedResult.stdout.split(/\r?\n/)) {
|
|
478
|
+
if (!line.endsWith(".ets")) continue;
|
|
479
|
+
const normalized = line.replace(/\\/g, "/");
|
|
480
|
+
if (!checkedFiles.includes(normalized)) checkedFiles.push(normalized);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const lineArgs = ["diff", "--unified=0", options.baseRef];
|
|
484
|
+
if (options.headRef) lineArgs.push(options.headRef);
|
|
485
|
+
lineArgs.push("--", "*.ets");
|
|
486
|
+
const lineResult = await runGit(options.projectRoot, lineArgs);
|
|
487
|
+
if (lineResult.code !== 0) {
|
|
488
|
+
throw new Error(`git diff failed: ${(lineResult.stderr || lineResult.stdout || `exit_code=${lineResult.code}`).trim()}`);
|
|
489
|
+
}
|
|
490
|
+
const changedLineRanges = {};
|
|
491
|
+
let currentFile = null;
|
|
492
|
+
for (const line of lineResult.stdout.split(/\r?\n/)) {
|
|
493
|
+
if (line.startsWith("+++ b/")) {
|
|
494
|
+
currentFile = line.replace("+++ b/", "");
|
|
495
|
+
if (!changedLineRanges[currentFile]) changedLineRanges[currentFile] = [];
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (!currentFile || !line.startsWith("@@")) continue;
|
|
499
|
+
const match = /@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
500
|
+
if (!match) continue;
|
|
501
|
+
const start = Number.parseInt(match[1], 10);
|
|
502
|
+
const count = match[2] !== undefined ? Number.parseInt(match[2], 10) : 1;
|
|
503
|
+
if (count > 0) changedLineRanges[currentFile].push([start, start + count - 1]);
|
|
504
|
+
}
|
|
505
|
+
if (!checkedFiles.length) {
|
|
506
|
+
payload = {
|
|
507
|
+
tool: "arkts_standalone_precheck",
|
|
508
|
+
tool_status: "ok",
|
|
509
|
+
success: true,
|
|
510
|
+
mode: options.headRef === null ? "diff-working-tree" : "diff",
|
|
511
|
+
checked_files: [],
|
|
512
|
+
errors: [],
|
|
513
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
514
|
+
reason: "no changed ETS files",
|
|
515
|
+
scope: "diff",
|
|
516
|
+
changed_files_only: true,
|
|
517
|
+
changed_line_ranges: {}
|
|
518
|
+
};
|
|
519
|
+
} else if (!options.commandlineTools) {
|
|
520
|
+
payload = {
|
|
521
|
+
tool: "arkts_standalone_precheck",
|
|
522
|
+
tool_status: "unavailable",
|
|
523
|
+
success: true,
|
|
524
|
+
mode: options.headRef === null ? "diff-working-tree" : "diff",
|
|
525
|
+
checked_files: checkedFiles,
|
|
526
|
+
errors: [],
|
|
527
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
528
|
+
reason: "commandline_tools not configured",
|
|
529
|
+
scope: "diff",
|
|
530
|
+
changed_files_only: true,
|
|
531
|
+
changed_line_ranges: changedLineRanges
|
|
532
|
+
};
|
|
533
|
+
} else {
|
|
534
|
+
const files = checkedFiles.map((file) => path.join(options.projectRoot, file));
|
|
535
|
+
const raw = await runArktsStandalonePrecheck({
|
|
536
|
+
projectRoot: options.projectRoot,
|
|
537
|
+
commandlineTools: options.commandlineTools,
|
|
538
|
+
outputDir: precheckOutputDir,
|
|
539
|
+
files
|
|
540
|
+
});
|
|
541
|
+
const normalizedCheckedFiles = new Set((Array.isArray(raw.checked_files) ? raw.checked_files : []).map((item) => String(item).replace(/\\/g, "/")));
|
|
542
|
+
const filteredErrors = (Array.isArray(raw.errors) ? raw.errors : []).filter((item) => {
|
|
543
|
+
const file = String(item.file ?? "").replace(/\\/g, "/");
|
|
544
|
+
const ranges = Array.isArray(changedLineRanges[file]) ? changedLineRanges[file] : null;
|
|
545
|
+
const line = item.line == null ? null : Number(item.line);
|
|
546
|
+
if (ranges && line !== null && !ranges.some(([start, end]) => start <= line && line <= end)) return false;
|
|
547
|
+
if (normalizedCheckedFiles.size && !normalizedCheckedFiles.has(file)) return false;
|
|
548
|
+
return true;
|
|
549
|
+
});
|
|
550
|
+
payload = {
|
|
551
|
+
...raw,
|
|
552
|
+
errors: filteredErrors,
|
|
553
|
+
summary: {
|
|
554
|
+
errorCount: filteredErrors.filter((item) => item.severity === "error").length,
|
|
555
|
+
warnCount: filteredErrors.filter((item) => item.severity === "warning").length
|
|
556
|
+
},
|
|
557
|
+
success: filteredErrors.filter((item) => item.severity === "error").length === 0,
|
|
558
|
+
scope: "diff",
|
|
559
|
+
changed_files_only: true,
|
|
560
|
+
mode: options.headRef === null ? "diff-working-tree" : "diff",
|
|
561
|
+
changed_line_ranges: changedLineRanges
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
payload = {
|
|
566
|
+
tool: "arkts_standalone_precheck",
|
|
567
|
+
tool_status: "unavailable",
|
|
568
|
+
success: true,
|
|
569
|
+
mode: options.headRef === null ? "diff-working-tree" : "diff",
|
|
570
|
+
checked_files: [],
|
|
571
|
+
errors: [],
|
|
572
|
+
summary: { errorCount: 0, warnCount: 0 },
|
|
573
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
574
|
+
scope: "diff",
|
|
575
|
+
changed_files_only: true,
|
|
576
|
+
changed_line_ranges: {},
|
|
577
|
+
failure_rule: "git-diff-failed"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
payload.artifact_paths = {
|
|
581
|
+
json: resultPath,
|
|
582
|
+
summary: summaryPath,
|
|
583
|
+
raw_json: typeof payload.artifact_paths?.raw_json === "string" ? payload.artifact_paths.raw_json : rawOutput.raw_json,
|
|
584
|
+
runner: typeof payload.artifact_paths?.runner === "string" ? payload.artifact_paths.runner : rawOutput.runner,
|
|
585
|
+
stdout: typeof payload.artifact_paths?.stdout === "string" ? payload.artifact_paths.stdout : rawOutput.stdout,
|
|
586
|
+
stderr: typeof payload.artifact_paths?.stderr === "string" ? payload.artifact_paths.stderr : rawOutput.stderr
|
|
587
|
+
};
|
|
588
|
+
await writeJson(resultPath, payload);
|
|
589
|
+
await writeArktsSummary(payload, summaryPath);
|
|
590
|
+
return payload;
|
|
591
|
+
}
|
|
592
|
+
async function runChecks(options) {
|
|
593
|
+
const runId = randomUUID().replace(/-/g, "");
|
|
594
|
+
await mkdir(path.resolve(options.outputDir), { recursive: true });
|
|
595
|
+
const runDir = prepareRunOutputDir(path.resolve(options.outputDir), runId);
|
|
596
|
+
await mkdir(runDir, { recursive: true });
|
|
597
|
+
const selectedProfiles = [...new Set(options.profiles)];
|
|
598
|
+
const unknownProfiles = selectedProfiles.filter((profile) => !SUPPORTED_PROFILES.has(profile));
|
|
599
|
+
if (unknownProfiles.length) throw new Error(`unsupported profile(s): ${unknownProfiles.join(", ")}`);
|
|
600
|
+
const backendResults = {};
|
|
601
|
+
const jobs = [];
|
|
602
|
+
if (selectedProfiles.includes("arkts-diff")) {
|
|
603
|
+
jobs.push((async () => {
|
|
604
|
+
try {
|
|
605
|
+
backendResults["arkts-diff"] = await runArktsDiff({
|
|
606
|
+
projectRoot: path.resolve(options.projectRoot),
|
|
607
|
+
outputDir: runDir,
|
|
608
|
+
commandlineTools: options.commandlineTools.sdkRoot,
|
|
609
|
+
baseRef: options.baseRef,
|
|
610
|
+
headRef: options.headRef
|
|
611
|
+
});
|
|
612
|
+
} catch (error) {
|
|
613
|
+
backendResults["arkts-diff"] = backendCrashPayload("arkts-diff", error);
|
|
614
|
+
}
|
|
615
|
+
})());
|
|
616
|
+
}
|
|
617
|
+
if (selectedProfiles.includes("codelinter-diff")) {
|
|
618
|
+
jobs.push((async () => {
|
|
619
|
+
try {
|
|
620
|
+
backendResults["codelinter-diff"] = await runCodelinterDiffScan({
|
|
621
|
+
projectRoot: path.resolve(options.projectRoot),
|
|
622
|
+
validationOutputDir: runDir,
|
|
623
|
+
commandlineTools: options.commandlineTools.sdkRoot,
|
|
624
|
+
baseRef: options.baseRef,
|
|
625
|
+
headRef: options.headRef
|
|
626
|
+
});
|
|
627
|
+
} catch (error) {
|
|
628
|
+
backendResults["codelinter-diff"] = backendCrashPayload("codelinter-diff", error);
|
|
629
|
+
}
|
|
630
|
+
})());
|
|
631
|
+
}
|
|
632
|
+
await Promise.all(jobs);
|
|
633
|
+
const findings = [];
|
|
634
|
+
const summaryCounts = { hard_fixable: 0, strong_warn: 0, risk_only: 0, not_checked: 0 };
|
|
635
|
+
const arkts = backendResults["arkts-diff"];
|
|
636
|
+
if (arkts) {
|
|
637
|
+
if (String(arkts.tool_status ?? "") !== "ok") {
|
|
638
|
+
findings.push({
|
|
639
|
+
profile: "arkts-diff",
|
|
640
|
+
category: "tooling",
|
|
641
|
+
classification: "not_checked",
|
|
642
|
+
file: null,
|
|
643
|
+
line: null,
|
|
644
|
+
column: null,
|
|
645
|
+
rule: String(arkts.failure_rule ?? "backend-unavailable"),
|
|
646
|
+
message: String(arkts.reason ?? "ArkTS backend unavailable")
|
|
647
|
+
});
|
|
648
|
+
summaryCounts.not_checked += 1;
|
|
649
|
+
} else {
|
|
650
|
+
for (const item of Array.isArray(arkts.errors) ? arkts.errors : []) {
|
|
651
|
+
if (item.severity !== "error") continue;
|
|
652
|
+
findings.push({
|
|
653
|
+
profile: "arkts-diff",
|
|
654
|
+
category: "compile",
|
|
655
|
+
classification: "hard_fixable",
|
|
656
|
+
file: item.file ?? null,
|
|
657
|
+
line: item.line ?? null,
|
|
658
|
+
column: item.column ?? null,
|
|
659
|
+
rule: item.rule || "no-rule",
|
|
660
|
+
message: item.message ?? "",
|
|
661
|
+
evidence: { status: arkts.evidence_status ?? "partial", raw_file: arkts.artifact_paths?.raw_json }
|
|
662
|
+
});
|
|
663
|
+
summaryCounts.hard_fixable += 1;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const codelinter = backendResults["codelinter-diff"];
|
|
668
|
+
if (codelinter) {
|
|
669
|
+
if (String(codelinter.tool_status ?? "") !== "ok") {
|
|
670
|
+
findings.push({
|
|
671
|
+
profile: "codelinter-diff",
|
|
672
|
+
category: "tooling",
|
|
673
|
+
classification: "not_checked",
|
|
674
|
+
file: null,
|
|
675
|
+
line: null,
|
|
676
|
+
column: null,
|
|
677
|
+
rule: String(codelinter.failure_rule ?? "backend-unavailable"),
|
|
678
|
+
message: String(codelinter.reason ?? "CodeLinter backend unavailable")
|
|
679
|
+
});
|
|
680
|
+
summaryCounts.not_checked += 1;
|
|
681
|
+
} else {
|
|
682
|
+
for (const item of Array.isArray(codelinter.diagnostics) ? codelinter.diagnostics : []) {
|
|
683
|
+
const severity = String(item.severity ?? "");
|
|
684
|
+
const classification = severity === "error" ? "hard_fixable" : severity === "warning" ? "strong_warn" : "risk_only";
|
|
685
|
+
findings.push({
|
|
686
|
+
profile: "codelinter-diff",
|
|
687
|
+
category: "lint",
|
|
688
|
+
classification,
|
|
689
|
+
file: item.file ?? null,
|
|
690
|
+
line: item.line ?? null,
|
|
691
|
+
column: item.column ?? null,
|
|
692
|
+
rule: item.rule || "no-rule",
|
|
693
|
+
message: item.message ?? "",
|
|
694
|
+
evidence: { status: codelinter.evidence_status ?? "partial", raw_file: codelinter.artifact_paths?.raw_json }
|
|
695
|
+
});
|
|
696
|
+
if (classification === "hard_fixable") summaryCounts.hard_fixable += 1;
|
|
697
|
+
else if (classification === "strong_warn") summaryCounts.strong_warn += 1;
|
|
698
|
+
else summaryCounts.risk_only += 1;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const status = overallStatus(summaryCounts, backendResults);
|
|
703
|
+
const compactSummary = summaryCounts.hard_fixable
|
|
704
|
+
? `${summaryCounts.hard_fixable} blocking finding(s) across ${selectedProfiles.length} profile(s)`
|
|
705
|
+
: summaryCounts.not_checked
|
|
706
|
+
? `no blocking findings; ${summaryCounts.not_checked} profile(s) not fully checked`
|
|
707
|
+
: "no blocking findings";
|
|
708
|
+
const payload = {
|
|
709
|
+
tool: "hmos-precheck",
|
|
710
|
+
run_id: runId,
|
|
711
|
+
schema_version: "hmos_precheck.v1",
|
|
712
|
+
status,
|
|
713
|
+
ok: status !== "fail",
|
|
714
|
+
profiles: selectedProfiles,
|
|
715
|
+
project_root: path.resolve(options.projectRoot),
|
|
716
|
+
summary: summaryCounts,
|
|
717
|
+
summary_counts: summaryCounts,
|
|
718
|
+
findings,
|
|
719
|
+
fixable: Boolean(summaryCounts.hard_fixable),
|
|
720
|
+
compact_summary: compactSummary,
|
|
721
|
+
backend_results: backendResults
|
|
722
|
+
};
|
|
723
|
+
const artifactPaths = {
|
|
724
|
+
json: path.join(runDir, "hmos-precheck-result.json"),
|
|
725
|
+
summary: path.join(runDir, "hmos-precheck-summary.md")
|
|
726
|
+
};
|
|
727
|
+
if (options.debugArtifact) artifactPaths.debug = path.join(runDir, "hmos-precheck-debug.json");
|
|
728
|
+
if (artifactPaths.debug) await writeJson(artifactPaths.debug, payload);
|
|
729
|
+
await writeSummary(payload, artifactPaths.summary);
|
|
730
|
+
await writeJson(artifactPaths.json, await resultPayload({ run_id: runId, status, backend_results: backendResults, findings }));
|
|
731
|
+
return { run_id: runId, status, backend_results: backendResults, findings, artifact_paths: artifactPaths };
|
|
732
|
+
}
|
|
733
|
+
function splitCsv(values) {
|
|
734
|
+
const items = [];
|
|
735
|
+
for (const value of values) {
|
|
736
|
+
items.push(...value.split(",").map((item) => item.trim()).filter(Boolean));
|
|
737
|
+
}
|
|
738
|
+
return items;
|
|
739
|
+
}
|
|
740
|
+
function parseFlags(argv, flag) {
|
|
741
|
+
const values = [];
|
|
742
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
743
|
+
if (argv[i] === flag && argv[i + 1]) values.push(argv[i + 1]);
|
|
744
|
+
}
|
|
745
|
+
return values;
|
|
746
|
+
}
|
|
747
|
+
function singleFlag(argv, flag) {
|
|
748
|
+
const index = argv.indexOf(flag);
|
|
749
|
+
return index >= 0 ? argv[index + 1] : void 0;
|
|
750
|
+
}
|
|
751
|
+
async function writeCliFailure(baseOutputDir, problem, fix, kind) {
|
|
752
|
+
const runId = randomUUID().replace(/-/g, "");
|
|
753
|
+
const resolvedBase = path.resolve(baseOutputDir);
|
|
754
|
+
await mkdir(resolvedBase, { recursive: true });
|
|
755
|
+
const runDir = path.join(resolvedBase, `run-${runId}`);
|
|
756
|
+
await mkdir(runDir, { recursive: true });
|
|
757
|
+
const resultPath = path.join(runDir, "hmos-precheck-result.json");
|
|
758
|
+
await writeJson(resultPath, {
|
|
759
|
+
run_id: runId,
|
|
760
|
+
summary: "Fix the blocking environment issue(s) first.",
|
|
761
|
+
evidence_status: "partial",
|
|
762
|
+
raw_files: [],
|
|
763
|
+
issues: [{ kind, problem, fix }]
|
|
764
|
+
});
|
|
765
|
+
return { ok: false, read_file: resultPath };
|
|
766
|
+
}
|
|
767
|
+
function fallbackOutputDir(argv) {
|
|
768
|
+
const explicit = singleFlag(argv, "--output-dir");
|
|
769
|
+
if (explicit) return path.resolve(explicit);
|
|
770
|
+
return path.join(os.tmpdir(), "hmos-precheck-cli-failures");
|
|
771
|
+
}
|
|
772
|
+
async function cmdRun(argv) {
|
|
773
|
+
try {
|
|
774
|
+
const projectRoot = singleFlag(argv, "--project-root");
|
|
775
|
+
const outputDir = singleFlag(argv, "--output-dir");
|
|
776
|
+
if (!projectRoot || !outputDir) throw new Error("--project-root and --output-dir are required");
|
|
777
|
+
const commandlineToolsInfo = await resolveCommandlineToolsInfo({
|
|
778
|
+
projectRoot: path.resolve(projectRoot),
|
|
779
|
+
explicitPath: singleFlag(argv, "--commandline-tools")
|
|
780
|
+
});
|
|
781
|
+
const explicitProfiles = splitCsv(parseFlags(argv, "--profile"));
|
|
782
|
+
const profiles = await resolveProfiles({
|
|
783
|
+
explicitProfiles: explicitProfiles.length ? explicitProfiles : null,
|
|
784
|
+
projectRoot: path.resolve(projectRoot)
|
|
785
|
+
}) ?? DEFAULT_PROFILES;
|
|
786
|
+
const payload = await runChecks({
|
|
787
|
+
projectRoot: path.resolve(projectRoot),
|
|
788
|
+
outputDir: path.resolve(outputDir),
|
|
789
|
+
commandlineTools: commandlineToolsInfo,
|
|
790
|
+
profiles,
|
|
791
|
+
baseRef: singleFlag(argv, "--base-ref") ?? "HEAD",
|
|
792
|
+
headRef: singleFlag(argv, "--head-ref") ?? null,
|
|
793
|
+
debugArtifact: argv.includes("--debug-artifact")
|
|
794
|
+
});
|
|
795
|
+
const result = compactResult(payload);
|
|
796
|
+
console.log(JSON.stringify(result));
|
|
797
|
+
return result.ok ? 0 : 1;
|
|
798
|
+
} catch (error) {
|
|
799
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
800
|
+
const invalidProfile = message.startsWith("unsupported profile(s):");
|
|
801
|
+
const result = await writeCliFailure(
|
|
802
|
+
fallbackOutputDir(argv),
|
|
803
|
+
invalidProfile ? `invalid profile configuration: ${message}` : message,
|
|
804
|
+
invalidProfile ? "Remove unsupported profiles from CLI/config and use only supported profiles." : "Resolve the reported blocking issue and rerun hmos-precheck.",
|
|
805
|
+
invalidProfile ? "invalid-profile" : "internal-error"
|
|
806
|
+
);
|
|
807
|
+
console.log(JSON.stringify(result));
|
|
808
|
+
return 1;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
async function cmdDoctor(argv) {
|
|
812
|
+
const projectRoot = singleFlag(argv, "--project-root") ? path.resolve(singleFlag(argv, "--project-root")) : void 0;
|
|
813
|
+
const info = await resolveCommandlineToolsInfo({
|
|
814
|
+
projectRoot,
|
|
815
|
+
explicitPath: singleFlag(argv, "--commandline-tools")
|
|
816
|
+
});
|
|
817
|
+
const configuredProfiles = await resolveProfiles({ projectRoot });
|
|
818
|
+
const codelinter = await findCodelinterTool(info.sdkRoot);
|
|
819
|
+
const arktsTool = await findArktsTool(info.sdkRoot);
|
|
820
|
+
const hmsSdkEts = await findHmsSdkEts(info.sdkRoot);
|
|
821
|
+
const systemResourceFile = await findSystemResourceFile(info.sdkRoot);
|
|
822
|
+
const payload = {
|
|
823
|
+
tool: "hmos-precheck",
|
|
824
|
+
status: info.available ? "pass" : "warn",
|
|
825
|
+
commandline_tools: info.path,
|
|
826
|
+
commandline_tools_source: info.source,
|
|
827
|
+
node: process.execPath,
|
|
828
|
+
codelinter,
|
|
829
|
+
checks: {
|
|
830
|
+
commandline_tools_configured: info.configured,
|
|
831
|
+
commandline_tools_found: info.available,
|
|
832
|
+
node_found: true,
|
|
833
|
+
ets_loader_found: Boolean(arktsTool.etsLoader),
|
|
834
|
+
hms_sdk_found: Boolean(hmsSdkEts),
|
|
835
|
+
system_resource_file_found: Boolean(systemResourceFile),
|
|
836
|
+
codelinter_found: Boolean(codelinter)
|
|
837
|
+
},
|
|
838
|
+
config_paths: {
|
|
839
|
+
project: projectRoot ? path.join(projectRoot, PROJECT_CONFIG_NAME) : null,
|
|
840
|
+
hometrans: defaultHomeTransConfigPath(),
|
|
841
|
+
user: defaultUserConfigPath()
|
|
842
|
+
},
|
|
843
|
+
profiles: {
|
|
844
|
+
default: DEFAULT_PROFILES,
|
|
845
|
+
supported: DEFAULT_PROFILES,
|
|
846
|
+
configured: configuredProfiles
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
if (info.message) payload.toolchain_warning = { message: info.message, hint: info.hint };
|
|
850
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
851
|
+
return 0;
|
|
852
|
+
}
|
|
853
|
+
async function cmdInit(argv) {
|
|
854
|
+
const projectRoot = singleFlag(argv, "--project-root") ? path.resolve(singleFlag(argv, "--project-root")) : void 0;
|
|
855
|
+
const info = await resolveCommandlineToolsInfo({
|
|
856
|
+
projectRoot,
|
|
857
|
+
explicitPath: singleFlag(argv, "--commandline-tools")
|
|
858
|
+
});
|
|
859
|
+
if (!info.sdkRoot) throw new Error("commandline_tools could not be resolved; pass --commandline-tools");
|
|
860
|
+
const scope = singleFlag(argv, "--scope") ?? "project";
|
|
861
|
+
const target = scope === "project" ? path.join(projectRoot ?? path.resolve("."), PROJECT_CONFIG_NAME) : defaultUserConfigPath();
|
|
862
|
+
await initConfig(target, info.sdkRoot);
|
|
863
|
+
console.log(JSON.stringify({ written: target, commandline_tools: info.sdkRoot, source: info.source }, null, 2));
|
|
864
|
+
return 0;
|
|
865
|
+
}
|
|
866
|
+
async function main(argv = process.argv.slice(2)) {
|
|
867
|
+
try {
|
|
868
|
+
const [command, ...rest] = argv;
|
|
869
|
+
if (!command) {
|
|
870
|
+
const result2 = await writeCliFailure(
|
|
871
|
+
fallbackOutputDir(argv),
|
|
872
|
+
"usage: hmos-precheck <run|doctor|init> ...",
|
|
873
|
+
"Invoke one supported command and rerun hmos-precheck.",
|
|
874
|
+
"invalid-command"
|
|
875
|
+
);
|
|
876
|
+
console.log(JSON.stringify(result2));
|
|
877
|
+
return 1;
|
|
878
|
+
}
|
|
879
|
+
if (command === "run") return await cmdRun(rest);
|
|
880
|
+
if (command === "doctor") return await cmdDoctor(rest);
|
|
881
|
+
if (command === "init") return await cmdInit(rest);
|
|
882
|
+
const result = await writeCliFailure(
|
|
883
|
+
fallbackOutputDir(argv),
|
|
884
|
+
`unknown command: ${command}`,
|
|
885
|
+
"Use one of: run, doctor, init.",
|
|
886
|
+
"invalid-command"
|
|
887
|
+
);
|
|
888
|
+
console.log(JSON.stringify(result));
|
|
889
|
+
return 1;
|
|
890
|
+
} catch (error) {
|
|
891
|
+
const result = await writeCliFailure(
|
|
892
|
+
fallbackOutputDir(argv),
|
|
893
|
+
error instanceof Error ? error.message : String(error),
|
|
894
|
+
"Resolve the reported blocking issue and rerun hmos-precheck.",
|
|
895
|
+
"internal-error"
|
|
896
|
+
);
|
|
897
|
+
console.log(JSON.stringify(result));
|
|
898
|
+
return 1;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
const __test__ = {
|
|
902
|
+
runChecks,
|
|
903
|
+
prepareRunOutputDir
|
|
904
|
+
};
|
|
905
|
+
export {
|
|
906
|
+
__test__,
|
|
907
|
+
main
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
911
|
+
main().then(
|
|
912
|
+
(code) => {
|
|
913
|
+
process.exitCode = code;
|
|
914
|
+
},
|
|
915
|
+
async (error) => {
|
|
916
|
+
const result = await writeCliFailure(
|
|
917
|
+
fallbackOutputDir(process.argv.slice(2)),
|
|
918
|
+
error instanceof Error ? error.message : String(error),
|
|
919
|
+
"Resolve the reported blocking issue and rerun hmos-precheck.",
|
|
920
|
+
"internal-error"
|
|
921
|
+
);
|
|
922
|
+
console.log(JSON.stringify(result));
|
|
923
|
+
process.exitCode = 1;
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
}
|