已开启
chore: update 静态扫描工具 #11
fengf3ng创建于 7月22日
chore: update 静态扫描工具 #11
已开启
共 41 个文件变更+1443-22
| @@ -0,0 +1,10 @@ | |||
| 1 | +--- | ||
| 2 | +name: hyperstartup-static-check | ||
| 3 | +description: Run and troubleshoot HyperStartup command-line source checks, target-file Top-Level checks, and MR startup gate baseline or incremental checks. Use to locate the packaged tools, validate project and HarmonyOS SDK paths, assemble or execute homecheck or mr-gate commands, inspect generated reports and HomeCheck.log, explain gate blocking results, or guide a user through baseline creation and gate artifacts. | ||
| 4 | +--- | ||
| 5 | + | ||
| 6 | +# HyperStartup Static Check | ||
| 7 | + | ||
| 8 | +Read `../../../skills/hyperstartup-static-check/SKILL.md` completely and follow it as the authoritative instructions for this skill. | ||
| 9 | + | ||
| 10 | +Resolve every relative reference and script path in those instructions from `../../../skills/hyperstartup-static-check`, not from this compatibility directory. | ||
| @@ -0,0 +1,10 @@ | |||
| 1 | +--- | ||
| 2 | +name: hyperstartup-static-check | ||
| 3 | +description: Run and troubleshoot HyperStartup command-line source checks, target-file Top-Level checks, and MR startup gate baseline or incremental checks. Use to locate the packaged tools, validate project and HarmonyOS SDK paths, assemble or execute homecheck or mr-gate commands, inspect generated reports and HomeCheck.log, explain gate blocking results, or guide a user through baseline creation and gate artifacts. | ||
| 4 | +--- | ||
| 5 | + | ||
| 6 | +# HyperStartup Static Check | ||
| 7 | + | ||
| 8 | +Read `../../../skills/hyperstartup-static-check/SKILL.md` completely and follow it as the authoritative instructions for this skill. | ||
| 9 | + | ||
| 10 | +Resolve every relative reference and script path in those instructions from `../../../skills/hyperstartup-static-check`, not from this compatibility directory. | ||
| @@ -69,7 +69,7 @@ To explicitly demonstrate the necessity and key points of hyper startup adaptati | |||
| 69 | 2. View **entry/src/main/ets/MyAbilityStage.ets** to observe the risky operations in the application version that has not been adapted to hyper startup. | 69 | 2. View **entry/src/main/ets/MyAbilityStage.ets** to observe the risky operations in the application version that has not been adapted to hyper startup. |
| 70 | 3. View **entry/src/main/ets/MyAbilityStageAdapted.ets** to compare the adaptation method of moving risky operations to after the **onAboutToCreateAbility** method. | 70 | 3. View **entry/src/main/ets/MyAbilityStageAdapted.ets** to compare the adaptation method of moving risky operations to after the **onAboutToCreateAbility** method. |
| 71 | 4. Modify **srcEntry** in **entry/src/main/module.json5**, switch between the adapted and non-adapted versions, and verify the differences. | 71 | 4. Modify **srcEntry** in **entry/src/main/module.json5**, switch between the adapted and non-adapted versions, and verify the differences. |
| 72 | -5. View the [README file of the application adaptation point scanning tool](https://gitcode.com/HarmonyOS_Samples/HyperStartup/blob/master/tools/hyperstartupcheck-user-manual.md) to learn how to use the IDE plugin to scan for hyper startup risks. | 72 | +5. View the [README for the hyper startup coding-rule checker](https://gitcode.com/HarmonyOS_Samples/HyperStartup/blob/master/tools/hyperstartupcheck-static.md) to learn how to use the IDE plugin to check system-interface calls during hyper startup initialization. |
| 73 | 73 | ||
| 74 | **Project Structure** | 74 | **Project Structure** |
| 75 | 75 | ||
| @@ -118,7 +118,8 @@ HyperStartup/ | |||
| 118 | │ ├──DevEco plugin | 118 | │ ├──DevEco plugin |
| 119 | │ ├──images | 119 | │ ├──images |
| 120 | │ ├──Static scanning tool | 120 | │ ├──Static scanning tool |
| 121 | -│ └──hyperstartupcheck-user-manual.md // README file for the tool for checking the hyper startup adaptation points | 121 | +│ ├──hyperstartupcheck-static.md // README for the hyper startup coding-rule checker |
| 122 | +│ └──hyperstartupcheck-user-manual.md // Combined user manual for the checking tools | ||
| 122 | ├──build-profile.json5 // Project-level build and signature configurations | 123 | ├──build-profile.json5 // Project-level build and signature configurations |
| 123 | ├──code-linter.json5 | 124 | ├──code-linter.json5 |
| 124 | ├──hvigorfile.ts | 125 | ├──hvigorfile.ts |
| @@ -165,7 +166,7 @@ The following APIs are involved in the adaptation: | |||
| 165 | 166 | ||
| 166 | **Obtaining the Hyper Startup Adaptation Points** | 167 | **Obtaining the Hyper Startup Adaptation Points** |
| 167 | 168 | ||
| 168 | -The adaptation points for hyper startup refer to risky operations that may be performed by an application during hyper startup initialization, such as file read/write, IPC calls, and network access. If these adaptation points are not handled, the application may have issues such as dirty data. You can use the DevEco Studio code scanning tool to obtain the adaptation points. For details, see [README file of the adaptation point scanning tool](https://gitcode.com/HarmonyOS_Samples/HyperStartup/blob/master/tools/hyperstartupcheck-user-manual.md). | 169 | +The adaptation points for hyper startup refer to risky operations that may be performed by an application during hyper startup initialization, such as file read/write, IPC calls, and network access. If these adaptation points are not handled, the application may have issues such as dirty data. You can use the static checker to enforce hyper startup coding rules. For details, see the [README for the hyper startup coding-rule checker](https://gitcode.com/HarmonyOS_Samples/HyperStartup/blob/master/tools/hyperstartupcheck-static.md). |
| 169 | 170 | ||
| 170 | **Comparison Before and After Adaptation** | 171 | **Comparison Before and After Adaptation** |
| 171 | 172 | ||
| @@ -35,7 +35,7 @@ | |||
| 35 | 2. 查看 `entry/src/main/ets/MyAbilityStage.ets`,观察未适配版本中快启风险操作的位置。 | 35 | 2. 查看 `entry/src/main/ets/MyAbilityStage.ets`,观察未适配版本中快启风险操作的位置。 |
| 36 | 3. 查看 `entry/src/main/ets/MyAbilityStageAdapted.ets`,对比风险操作后置到 `onAboutToCreateAbility` 后的适配方式。 | 36 | 3. 查看 `entry/src/main/ets/MyAbilityStageAdapted.ets`,对比风险操作后置到 `onAboutToCreateAbility` 后的适配方式。 |
| 37 | 4. 修改 `entry/src/main/module.json5` 中的 `srcEntry`,切换未适配/已适配版本并验证差异。 | 37 | 4. 修改 `entry/src/main/module.json5` 中的 `srcEntry`,切换未适配/已适配版本并验证差异。 |
| 38 | -5. 查看 [应用待适配点扫描工具README](tools/hyperstartupcheck-user-manual.md),了解如何使用IDE插件扫描快启启动风险点 | 38 | +5. 查看 [快启编码规则静态检查工具 README](tools/hyperstartupcheck-static.md),了解如何使用 IDE 插件检查快启初始化阶段的系统接口调用 |
| 39 | 39 | ||
| 40 | ## 工程结构 | 40 | ## 工程结构 |
| 41 | 41 | ||
| @@ -84,7 +84,8 @@ HyperStartup/ | |||
| 84 | │ ├──DevEco插件 | 84 | │ ├──DevEco插件 |
| 85 | │ ├──images | 85 | │ ├──images |
| 86 | │ ├──静态扫描工具 | 86 | │ ├──静态扫描工具 |
| 87 | -│ └──hyperstartupcheck-user-manual.md // 快启待适配点检查工具说明 README | 87 | +│ ├──hyperstartupcheck-static.md // 快启编码规则静态检查工具说明 README |
| 88 | +│ └──hyperstartupcheck-user-manual.md // 快启检查工具总说明 README | ||
| 88 | ├──build-profile.json5 // 工程级构建配置和签名配置 | 89 | ├──build-profile.json5 // 工程级构建配置和签名配置 |
| 89 | ├──code-linter.json5 | 90 | ├──code-linter.json5 |
| 90 | ├──hvigorfile.ts | 91 | ├──hvigorfile.ts |
| @@ -132,7 +133,7 @@ HyperStartup/ | |||
| 132 | 133 | ||
| 133 | ### 获取快启待适配点 | 134 | ### 获取快启待适配点 |
| 134 | 135 | ||
| 135 | -快启待适配点主要指应用在快启初始化阶段可能执行的风险操作,例如文件读写、IPC 调用、访问网络等,如不适配可能导致应用存在脏数据等问题。可通过代码扫描 IDE 工具获取待适配点,详见 [待适配点扫描工具 README](tools/hyperstartupcheck-user-manual.md)。 | 136 | +快启待适配点主要指应用在快启初始化阶段可能执行的风险操作,例如文件读写、IPC 调用、访问网络等,如不适配可能导致应用存在脏数据等问题。可通过静态检查工具检查快启编码规则,详见 [快启编码规则静态检查工具 README](tools/hyperstartupcheck-static.md)。 |
| 136 | 137 | ||
| 137 | ### 适配前后对比 | 138 | ### 适配前后对比 |
| 138 | 139 | ||
| @@ -0,0 +1,54 @@ | |||
| 1 | +--- | ||
| 2 | +name: hyperstartup-static-check | ||
| 3 | +description: Run and troubleshoot HyperStartup command-line source checks, target-file Top-Level checks, and MR startup gate baseline or incremental checks. Use to locate the packaged tools, validate project and HarmonyOS SDK paths, assemble or execute homecheck or mr-gate commands, inspect generated reports and HomeCheck.log, explain gate blocking results, or guide a user through baseline creation and gate artifacts. | ||
| 4 | +--- | ||
| 5 | + | ||
| 6 | +# HyperStartup Static Check | ||
| 7 | + | ||
| 8 | +Use the packaged HyperStartup tools safely and explain their observable results. Treat `tools/hyperstartupcheck-static.md` in the repository as the authoritative guide for capabilities, commands, rule semantics, reports, and troubleshooting. | ||
| 9 | + | ||
| 10 | +## Read the guide | ||
| 11 | + | ||
| 12 | +Read only the relevant sections of `tools/hyperstartupcheck-static.md`: | ||
| 13 | + | ||
| 14 | +- command-line checks: `1.2 命令行方式` and the applicable subsection under `4. 进阶使用`; | ||
| 15 | +- baseline initialization or incremental gate checks: `1.3 门禁方式`; | ||
| 16 | +- issue interpretation and handling: `2. 检查规则与示例` and `3. 检查报告与处理`; | ||
| 17 | +- failed runs, empty reports, or SDK recognition problems: the matching subsection under `FAQ`. | ||
| 18 | + | ||
| 19 | +Search by heading or relevant option name instead of reading the entire guide. Do not reproduce the guide inside this skill. | ||
| 20 | + | ||
| 21 | +## Core workflow | ||
| 22 | + | ||
| 23 | +1. Establish the requested mode and locate the application project, extracted tool directory, and SDK paths. Reuse paths already supplied by the user. | ||
| 24 | +2. Run the read-only preflight before assembling a command: | ||
| 25 | + | ||
| 26 | + ```text | ||
| 27 | + node <skill-dir>/scripts/preflight.mjs --mode <cli|gate> --project <project> --tool-root <tool-root> --sdk-root <sdk-root> | ||
| 28 | + ``` | ||
| 29 | + | ||
| 30 | + Pass `--ohos-sdk` or `--hms-sdk` instead of `--sdk-root` when the user supplies explicit `ets` directories. For a non-default gate output location, also pass `--report-dir`. | ||
| 31 | +3. Resolve every placeholder to an absolute path. Never present a command containing an unresolved sample path as ready to run. | ||
| 32 | +4. Show the exact command and a one-sentence description of its effect. Run it when the user requested execution; otherwise provide it without mutating state. | ||
| 33 | +5. Preserve the process exit code and locate the generated result directory. | ||
| 34 | +6. Summarize results with the bundled script appropriate to the workflow, then inspect raw artifacts if the summary indicates ambiguity. | ||
| 35 | +7. Report the command mode, result status, artifact path, important warnings, and the next safe action. | ||
| 36 | + | ||
| 37 | +## Guardrails | ||
| 38 | + | ||
| 39 | +- Do not run `--force-update` unless the user explicitly requests it after seeing the blocking result. | ||
| 40 | +- Do not replace or publish `visitedFiles.json`; a gate-produced `visitedFiles.next.json` is only a candidate for the surrounding pipeline. | ||
| 41 | +- Do not initialize a gate baseline when the user requested only an incremental check. | ||
| 42 | +- Require committed changes and a clean working tree before treating a gate result as reliable. | ||
| 43 | +- Do not add whitelist rules or modify application source unless the user explicitly requests those changes. | ||
| 44 | +- Do not treat missing source, unknown SDK functions, or an unrecognized entry as a code defect before completing the relevant diagnostics. | ||
| 45 | +- Do not expose internal `--debug` as a supported user option. | ||
| 46 | +- Preserve existing project files and unrelated working-tree changes. | ||
| 47 | + | ||
| 48 | +## Bundled scripts | ||
| 49 | + | ||
| 50 | +- `scripts/preflight.mjs`: validate paths, Git state, tool entry points, SDK directories, and gate baseline availability. It performs no writes. | ||
| 51 | +- `scripts/summarize-check-report.mjs`: summarize the latest command-line JSON report and recognized entries from `HomeCheck.log`. | ||
| 52 | +- `scripts/summarize-gate-report.mjs`: summarize gate status, commits, changed files, scan roots, issues, and baseline artifacts. | ||
| 53 | + | ||
| 54 | +The scripts print JSON so their output can be quoted precisely or processed further. | ||
| @@ -0,0 +1,4 @@ | |||
| 1 | +interface: | ||
| 2 | + display_name: "HyperStartup Static Check" | ||
| 3 | + short_description: "运行快启命令行检查、MR 门禁、解析结果并诊断常见问题" | ||
| 4 | + default_prompt: "Use $hyperstartup-static-check to run a HyperStartup command-line or MR gate check and explain the result." | ||
| @@ -0,0 +1,145 @@ | |||
| 1 | +#!/usr/bin/env node | ||
| 2 | + | ||
| 3 | +import fs from 'node:fs'; | ||
| 4 | +import path from 'node:path'; | ||
| 5 | +import { spawnSync } from 'node:child_process'; | ||
| 6 | + | ||
| 7 | +function parseArgs(argv) { | ||
| 8 | + const values = {}; | ||
| 9 | + for (let i = 0; i < argv.length; i += 1) { | ||
| 10 | + const key = argv[i]; | ||
| 11 | + if (!key.startsWith('--') || i + 1 >= argv.length) { | ||
| 12 | + throw new Error(`Expected --name value, received: ${key}`); | ||
| 13 | + } | ||
| 14 | + values[key.slice(2)] = argv[++i]; | ||
| 15 | + } | ||
| 16 | + return values; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +function absolute(value) { | ||
| 20 | + return value ? path.resolve(value) : undefined; | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +function firstExisting(candidates) { | ||
| 24 | + return candidates.find(candidate => fs.existsSync(candidate)); | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +function runGit(project, args) { | ||
| 28 | + return spawnSync('git', ['-C', project, ...args], { | ||
| 29 | + encoding: 'utf8', | ||
| 30 | + windowsHide: true, | ||
| 31 | + }); | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +function main() { | ||
| 35 | + const args = parseArgs(process.argv.slice(2)); | ||
| 36 | + const mode = args.mode; | ||
| 37 | + if (mode !== 'cli' && mode !== 'gate') { | ||
| 38 | + throw new Error('--mode must be cli or gate'); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + const project = absolute(args.project); | ||
| 42 | + const toolRoot = absolute(args['tool-root']); | ||
| 43 | + const sdkRoot = absolute(args['sdk-root']); | ||
| 44 | + const reportDir = absolute(args['report-dir']) ?? (project ? path.join(project, 'result') : undefined); | ||
| 45 | + const errors = []; | ||
| 46 | + const warnings = []; | ||
| 47 | + | ||
| 48 | + if (!project || !fs.existsSync(project) || !fs.statSync(project).isDirectory()) { | ||
| 49 | + errors.push('Project directory does not exist.'); | ||
| 50 | + } | ||
| 51 | + if (!toolRoot || !fs.existsSync(toolRoot) || !fs.statSync(toolRoot).isDirectory()) { | ||
| 52 | + errors.push('Extracted tool directory does not exist.'); | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + let cliEntry; | ||
| 56 | + let bundledNode; | ||
| 57 | + let gateScript; | ||
| 58 | + if (toolRoot && fs.existsSync(toolRoot)) { | ||
| 59 | + cliEntry = firstExisting([ | ||
| 60 | + path.join(toolRoot, 'homecheck.cmd'), | ||
| 61 | + path.join(toolRoot, 'homecheck'), | ||
| 62 | + ]); | ||
| 63 | + bundledNode = firstExisting([ | ||
| 64 | + path.join(toolRoot, 'node', 'node.exe'), | ||
| 65 | + path.join(toolRoot, 'node', 'bin', 'node'), | ||
| 66 | + ]); | ||
| 67 | + gateScript = firstExisting([ | ||
| 68 | + path.join(toolRoot, 'mr-gate.js'), | ||
| 69 | + path.join(toolRoot, 'runtime', 'scripts', 'dist', 'mr-startup-gate.js'), | ||
| 70 | + path.join(toolRoot, 'lib', 'scripts', 'dist', 'mr-startup-gate.js'), | ||
| 71 | + ]); | ||
| 72 | + if (mode === 'cli' && !cliEntry) { | ||
| 73 | + errors.push('homecheck entry point was not found in the tool directory.'); | ||
| 74 | + } | ||
| 75 | + if (mode === 'gate' && (!bundledNode || !gateScript)) { | ||
| 76 | + errors.push('Bundled Node or MR gate script was not found in the tool directory.'); | ||
| 77 | + } | ||
| 78 | + if (mode === 'gate' && gateScript && path.basename(gateScript) !== 'mr-gate.js') { | ||
| 79 | + warnings.push('Public mr-gate.js wrapper is absent; a bundled implementation script was found.'); | ||
| 80 | + } | ||
| 81 | + } | ||
| 82 | + | ||
| 83 | + const ohosSdk = absolute(args['ohos-sdk']) ?? (sdkRoot ? path.join(sdkRoot, 'default', 'openharmony', 'ets') : undefined); | ||
| 84 | + const hmsSdk = absolute(args['hms-sdk']) ?? (sdkRoot ? path.join(sdkRoot, 'default', 'hms', 'ets') : undefined); | ||
| 85 | + const validOhosSdk = Boolean(ohosSdk && fs.existsSync(ohosSdk) && fs.statSync(ohosSdk).isDirectory()); | ||
| 86 | + const validHmsSdk = Boolean(hmsSdk && fs.existsSync(hmsSdk) && fs.statSync(hmsSdk).isDirectory()); | ||
| 87 | + if (!validOhosSdk && !validHmsSdk) { | ||
| 88 | + errors.push('Neither an OpenHarmony nor an HMS SDK ets directory exists.'); | ||
| 89 | + } | ||
| 90 | + | ||
| 91 | + let git = { repository: false, clean: undefined, head: undefined }; | ||
| 92 | + if (project && fs.existsSync(project)) { | ||
| 93 | + const inside = runGit(project, ['rev-parse', '--is-inside-work-tree']); | ||
| 94 | + git.repository = inside.status === 0 && inside.stdout.trim() === 'true'; | ||
| 95 | + if (git.repository) { | ||
| 96 | + const status = runGit(project, ['status', '--porcelain']); | ||
| 97 | + const head = runGit(project, ['rev-parse', 'HEAD']); | ||
| 98 | + git.clean = status.status === 0 && status.stdout.trim() === ''; | ||
| 99 | + git.head = head.status === 0 ? head.stdout.trim() : undefined; | ||
| 100 | + } | ||
| 101 | + } | ||
| 102 | + if (mode === 'gate') { | ||
| 103 | + if (!git.repository) { | ||
| 104 | + errors.push('Gate project is not a Git working tree.'); | ||
| 105 | + } else if (!git.clean) { | ||
| 106 | + errors.push('Gate project has uncommitted changes.'); | ||
| 107 | + } | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + const baselinePath = reportDir ? path.join(reportDir, 'visitedFiles.json') : undefined; | ||
| 111 | + const result = { | ||
| 112 | + mode, | ||
| 113 | + ready: errors.length === 0, | ||
| 114 | + project, | ||
| 115 | + toolRoot, | ||
| 116 | + cliEntry, | ||
| 117 | + bundledNode, | ||
| 118 | + gateScript, | ||
| 119 | + runtimeNodeModules: toolRoot ? path.join(toolRoot, 'runtime', 'node_modules') : undefined, | ||
| 120 | + sdk: { | ||
| 121 | + sdkRoot, | ||
| 122 | + ohosSdk, | ||
| 123 | + hmsSdk, | ||
| 124 | + validOhosSdk, | ||
| 125 | + validHmsSdk, | ||
| 126 | + }, | ||
| 127 | + git, | ||
| 128 | + gate: mode === 'gate' ? { | ||
| 129 | + reportDir, | ||
| 130 | + baselinePath, | ||
| 131 | + baselineExists: Boolean(baselinePath && fs.existsSync(baselinePath)), | ||
| 132 | + } : undefined, | ||
| 133 | + errors, | ||
| 134 | + warnings, | ||
| 135 | + }; | ||
| 136 | + console.log(JSON.stringify(result, null, 2)); | ||
| 137 | + process.exitCode = result.ready ? 0 : 1; | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +try { | ||
| 141 | + main(); | ||
| 142 | +} catch (error) { | ||
| 143 | + console.error(JSON.stringify({ ready: false, errors: [error.message] }, null, 2)); | ||
| 144 | + process.exitCode = 2; | ||
| 145 | +} | ||
| @@ -0,0 +1,99 @@ | |||
| 1 | +#!/usr/bin/env node | ||
| 2 | + | ||
| 3 | +import fs from 'node:fs'; | ||
| 4 | +import path from 'node:path'; | ||
| 5 | + | ||
| 6 | +function valueAfter(name) { | ||
| 7 | + const index = process.argv.indexOf(name); | ||
| 8 | + return index >= 0 ? process.argv[index + 1] : undefined; | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +function latestDirectory(root) { | ||
| 12 | + if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) { | ||
| 13 | + throw new Error(`Result path is not a directory: ${root}`); | ||
| 14 | + } | ||
| 15 | + if (fs.readdirSync(root).some(name => /^index\..+\.json$/.test(name))) { | ||
| 16 | + return root; | ||
| 17 | + } | ||
| 18 | + const candidates = fs.readdirSync(root, { withFileTypes: true }) | ||
| 19 | + .filter(entry => entry.isDirectory() && entry.name.startsWith('result.')) | ||
| 20 | + .map(entry => path.join(root, entry.name)) | ||
| 21 | + .sort((left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs); | ||
| 22 | + if (candidates.length === 0) { | ||
| 23 | + throw new Error(`No result.<timestamp> directory found under: ${root}`); | ||
| 24 | + } | ||
| 25 | + return candidates[0]; | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +function newestFile(directory, pattern) { | ||
| 29 | + return fs.readdirSync(directory) | ||
| 30 | + .filter(name => pattern.test(name)) | ||
| 31 | + .map(name => path.join(directory, name)) | ||
| 32 | + .sort((left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs)[0]; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +function displayType(issue) { | ||
| 36 | + const type = String(issue.issueType ?? issue.hitIssue?.issueType ?? issue.messages?.[0] ?? '').toLowerCase(); | ||
| 37 | + if (type.includes('unrecognized import')) return '未知文件导入'; | ||
| 38 | + if (type.includes('impl not found') || type.includes('unknown method')) return '未知函数实现'; | ||
| 39 | + if (type.includes('bad method call') || type.includes('bad import') || type.includes('entry non-import')) return '错误调用'; | ||
| 40 | + return type || '未分类'; | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +function recognizedEntries(logPath) { | ||
| 44 | + if (!logPath || !fs.existsSync(logPath)) return []; | ||
| 45 | + const entries = new Set(); | ||
| 46 | + for (const line of fs.readFileSync(logPath, 'utf8').split(/\r?\n/)) { | ||
| 47 | + const framework = line.match(/Checking (AbilityStage|UIAbility|StartupTask)(?: import-only)? entries?\s+(.+)$/); | ||
| 48 | + if (framework) entries.add(`${framework[1]}: ${framework[2].trim()}`); | ||
| 49 | + const custom = line.match(/Checking custom(?: import-only)? entr(?:y|ies):?\s+(.+)$/i); | ||
| 50 | + if (custom) entries.add(`custom: ${custom[1].trim()}`); | ||
| 51 | + } | ||
| 52 | + return [...entries]; | ||
| 53 | +} | ||
| 54 | + | ||
| 55 | +function main() { | ||
| 56 | + const input = valueAfter('--result'); | ||
| 57 | + if (!input) throw new Error('--result is required'); | ||
| 58 | + const resultDir = latestDirectory(path.resolve(input)); | ||
| 59 | + const jsonPath = newestFile(resultDir, /^index\..+\.json$/); | ||
| 60 | + if (!jsonPath) throw new Error(`No index.<timestamp>.json found in: ${resultDir}`); | ||
| 61 | + const htmlPath = newestFile(resultDir, /^index\..+\.html$/); | ||
| 62 | + const logPath = fs.existsSync(path.join(resultDir, 'HomeCheck.log')) | ||
| 63 | + ? path.join(resultDir, 'HomeCheck.log') | ||
| 64 | + : undefined; | ||
| 65 | + const report = JSON.parse(fs.readFileSync(jsonPath, 'utf8')); | ||
| 66 | + const issues = Array.isArray(report.issues) ? report.issues : []; | ||
| 67 | + const byType = {}; | ||
| 68 | + for (const issue of issues) { | ||
| 69 | + const type = displayType(issue); | ||
| 70 | + byType[type] = (byType[type] ?? 0) + 1; | ||
| 71 | + } | ||
| 72 | + const entries = recognizedEntries(logPath); | ||
| 73 | + const total = Number(report.stats?.total ?? issues.length); | ||
| 74 | + console.log(JSON.stringify({ | ||
| 75 | + resultDir, | ||
| 76 | + jsonReport: jsonPath, | ||
| 77 | + htmlReport: htmlPath, | ||
| 78 | + homeCheckLog: logPath, | ||
| 79 | + stats: { | ||
| 80 | + total, | ||
| 81 | + errors: Number(report.stats?.errorCount ?? 0), | ||
| 82 | + warnings: Number(report.stats?.warnCount ?? 0), | ||
| 83 | + byType, | ||
| 84 | + }, | ||
| 85 | + recognizedEntries: entries, | ||
| 86 | + diagnostics: { | ||
| 87 | + emptyReport: total === 0, | ||
| 88 | + entryLogAvailable: Boolean(logPath), | ||
| 89 | + noRecognizedEntriesInLog: Boolean(logPath) && entries.length === 0, | ||
| 90 | + }, | ||
| 91 | + }, null, 2)); | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +try { | ||
| 95 | + main(); | ||
| 96 | +} catch (error) { | ||
| 97 | + console.error(JSON.stringify({ error: error.message }, null, 2)); | ||
| 98 | + process.exitCode = 1; | ||
| 99 | +} | ||
| @@ -0,0 +1,74 @@ | |||
| 1 | +#!/usr/bin/env node | ||
| 2 | + | ||
| 3 | +import fs from 'node:fs'; | ||
| 4 | +import path from 'node:path'; | ||
| 5 | + | ||
| 6 | +function valueAfter(name) { | ||
| 7 | + const index = process.argv.indexOf(name); | ||
| 8 | + return index >= 0 ? process.argv[index + 1] : undefined; | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +function collectReports(root, depth = 0) { | ||
| 12 | + if (depth > 3 || !fs.existsSync(root)) return []; | ||
| 13 | + const stat = fs.statSync(root); | ||
| 14 | + if (stat.isFile()) return path.basename(root) === 'mr-startup-gate-report.json' ? [root] : []; | ||
| 15 | + const direct = path.join(root, 'mr-startup-gate-report.json'); | ||
| 16 | + if (fs.existsSync(direct)) return [direct]; | ||
| 17 | + return fs.readdirSync(root, { withFileTypes: true }) | ||
| 18 | + .filter(entry => entry.isDirectory() && entry.name !== 'node_modules') | ||
| 19 | + .flatMap(entry => collectReports(path.join(root, entry.name), depth + 1)); | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +function readJson(filePath) { | ||
| 23 | + return fs.existsSync(filePath) ? JSON.parse(fs.readFileSync(filePath, 'utf8')) : undefined; | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +function main() { | ||
| 27 | + const input = valueAfter('--report'); | ||
| 28 | + if (!input) throw new Error('--report is required'); | ||
| 29 | + const reports = collectReports(path.resolve(input)) | ||
| 30 | + .sort((left, right) => fs.statSync(right).mtimeMs - fs.statSync(left).mtimeMs); | ||
| 31 | + if (reports.length === 0) throw new Error(`No mr-startup-gate-report.json found under: ${path.resolve(input)}`); | ||
| 32 | + const reportPath = reports[0]; | ||
| 33 | + const artifactDir = path.dirname(reportPath); | ||
| 34 | + const report = readJson(reportPath); | ||
| 35 | + const metadata = readJson(path.join(artifactDir, 'metadata.json')); | ||
| 36 | + const diff = readJson(path.join(artifactDir, 'diff.json')); | ||
| 37 | + const scanPlan = readJson(path.join(artifactDir, 'scan-plan.json')); | ||
| 38 | + const issues = Array.isArray(report.issues) ? report.issues.map(issue => ({ | ||
| 39 | + severity: issue.severity, | ||
| 40 | + scanKind: issue.scanKind, | ||
| 41 | + filePath: issue.filePath, | ||
| 42 | + line: issue.line, | ||
| 43 | + issueType: issue.issueType, | ||
| 44 | + message: issue.message, | ||
| 45 | + addedLineMatches: issue.addedLineMatches, | ||
| 46 | + })) : []; | ||
| 47 | + const status = report.forcedUpdate ? 'FORCED ACCEPTED' : report.passed ? 'PASSED' : 'BLOCKED'; | ||
| 48 | + console.log(JSON.stringify({ | ||
| 49 | + status, | ||
| 50 | + expectedExitCode: report.passed ? 0 : 1, | ||
| 51 | + mode: report.mode, | ||
| 52 | + reportPath, | ||
| 53 | + artifactDir, | ||
| 54 | + revisions: { | ||
| 55 | + baseline: metadata?.baselineCommit ?? metadata?.oldCommit, | ||
| 56 | + head: metadata?.newCommit, | ||
| 57 | + }, | ||
| 58 | + changedFiles: scanPlan?.changedFiles ?? diff?.changedFiles ?? [], | ||
| 59 | + scanRoots: report.scanRoots ?? scanPlan?.scanRoots ?? [], | ||
| 60 | + baselineInvalidatingFiles: report.baselineInvalidatingFiles ?? scanPlan?.baselineInvalidatingFiles ?? [], | ||
| 61 | + stats: report.stats, | ||
| 62 | + issues, | ||
| 63 | + baselinePath: report.baselinePath, | ||
| 64 | + nextBaselineCandidate: report.nextVisitedFilesPath, | ||
| 65 | + nextBaselineGenerated: report.nextBaselineGenerated === true, | ||
| 66 | + }, null, 2)); | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +try { | ||
| 70 | + main(); | ||
| 71 | +} catch (error) { | ||
| 72 | + console.error(JSON.stringify({ error: error.message }, null, 2)); | ||
| 73 | + process.exitCode = 1; | ||
| 74 | +} | ||
| @@ -146,7 +146,7 @@ HyperStartup Checker 支持插件和命令行两种运行方式。两种方式 | |||
| 146 | PowerShell 或者 CMD 中运行命令行工具 | 146 | PowerShell 或者 CMD 中运行命令行工具 |
| 147 | 147 | ||
| 148 | ```powershell | 148 | ```powershell |
| 149 | -PS .\homecheck.exe --project "/path/to/project" --ohosSdk "D:\DevEco Studio\sdk\default\openharmony\ets" --hmsSdk "D:\DevEco Studio\sdk\default\hms\ets" | 149 | +PS .\homecheck.cmd --project "/path/to/project" --ohosSdk "D:\DevEco Studio\sdk\default\openharmony\ets" --hmsSdk "D:\DevEco Studio\sdk\default\hms\ets" |
| 150 | ``` | 150 | ``` |
| 151 | 151 | ||
| 152 | ### 4.3 查看扫描结果 | 152 | ### 4.3 查看扫描结果 |
| @@ -513,7 +513,7 @@ CLI 中的采集时长、风险事件类型、hstack 与 sourcemap 等参数, | |||
| 513 | 命令行运行时,可通过 `--memory` 参数调整内存大小 | 513 | 命令行运行时,可通过 `--memory` 参数调整内存大小 |
| 514 | 514 | ||
| 515 | ```text | 515 | ```text |
| 516 | -PS .\homecheck.exe --project "/path/to/project" --memory 32768 --ohosSdk "D:\DevEco Studio\sdk\default\openharmony\ets" --hmsSdk "D:\DevEco Studio\sdk\default\hms\ets" | 516 | +PS .\homecheck.cmd --project "/path/to/project" --memory 32768 --ohosSdk "D:\DevEco Studio\sdk\default\openharmony\ets" --hmsSdk "D:\DevEco Studio\sdk\default\hms\ets" |
| 517 | ``` | 517 | ``` |
| 518 | 518 | ||
| 519 | ### 动态采集无调用栈或结果为空 | 519 | ### 动态采集无调用栈或结果为空 |