deveco-harness 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.en.md +71 -0
- package/README.md +71 -0
- package/deveco-harness.example.jsonc +32 -0
- package/dist/index.js +758 -0
- package/package.json +48 -0
- package/prompts/build.txt +164 -0
- package/prompts/plan.txt +118 -0
- package/skills/arkts-error-fixes/README.md +151 -0
- package/skills/arkts-error-fixes/SKILL.md +150 -0
- package/skills/arkts-error-fixes/assets/AnyTypeError.ets +269 -0
- package/skills/arkts-error-fixes/assets/AppStorageError.ets +22 -0
- package/skills/arkts-error-fixes/assets/ArrowFunctionConversionError.ets +42 -0
- package/skills/arkts-error-fixes/assets/AvoidAreaTypeError.ets +43 -0
- package/skills/arkts-error-fixes/assets/BreakpointTypeError.ets +71 -0
- package/skills/arkts-error-fixes/assets/CatchClauseTypeError.ets +54 -0
- package/skills/arkts-error-fixes/assets/ColorConsistencyError.ets +88 -0
- package/skills/arkts-error-fixes/assets/ColorPropertyError.ets +39 -0
- package/skills/arkts-error-fixes/assets/ContextTypeError.ets +105 -0
- package/skills/arkts-error-fixes/assets/DecoratorStateError.ets +64 -0
- package/skills/arkts-error-fixes/assets/DisplayListenerTypeError.ets +129 -0
- package/skills/arkts-error-fixes/assets/DuplicateEntryError.ets +35 -0
- package/skills/arkts-error-fixes/assets/ESObjectTypeError.ets +137 -0
- package/skills/arkts-error-fixes/assets/FontColorPropertyError.ets +42 -0
- package/skills/arkts-error-fixes/assets/FunctionReturnTypeError.ets +41 -0
- package/skills/arkts-error-fixes/assets/IDataSourceError.ets +79 -0
- package/skills/arkts-error-fixes/assets/ImplementationNotAllowedError.ets +38 -0
- package/skills/arkts-error-fixes/assets/InterfaceMethodSignatureError.ets +43 -0
- package/skills/arkts-error-fixes/assets/NotificationError.ets +34 -0
- package/skills/arkts-error-fixes/assets/ObjectLiteralInterfaceError.ets +49 -0
- package/skills/arkts-error-fixes/assets/ObjectLiteralTypeError.ets +54 -0
- package/skills/arkts-error-fixes/assets/ObjectSpreadError.ets +29 -0
- package/skills/arkts-error-fixes/assets/PossiblyNullError.ets +32 -0
- package/skills/arkts-error-fixes/assets/ResourceConversionError.ets +159 -0
- package/skills/arkts-error-fixes/assets/StandaloneFunctionContext.ets +116 -0
- package/skills/arkts-error-fixes/assets/StandaloneFunctionError.ets +85 -0
- package/skills/arkts-error-fixes/assets/StorageLinkDefaultError.ets +41 -0
- package/skills/arkts-error-fixes/assets/TitleButtonRectTypeError.ets +86 -0
- package/skills/arkts-error-fixes/assets/UnusedVariableWarning.ets +102 -0
- package/skills/arkts-error-fixes/assets/UtilityTypeError.ets +176 -0
- package/skills/arkts-error-fixes/assets/WindowRectSizeError.ets +65 -0
- package/skills/arkts-error-fixes/assets/WindowTypeError.ets +37 -0
- package/skills/arkts-error-fixes/reference/any_type_errors.md +433 -0
- package/skills/arkts-error-fixes/reference/appstorage_errors.md +95 -0
- package/skills/arkts-error-fixes/reference/arrow_function_conversion_errors.md +163 -0
- package/skills/arkts-error-fixes/reference/avoid_area_type_errors.md +59 -0
- package/skills/arkts-error-fixes/reference/breakpoint_type_errors.md +122 -0
- package/skills/arkts-error-fixes/reference/catch_clause_type_errors.md +89 -0
- package/skills/arkts-error-fixes/reference/color_consistency_errors.md +209 -0
- package/skills/arkts-error-fixes/reference/color_property_errors.md +136 -0
- package/skills/arkts-error-fixes/reference/context_type_errors.md +260 -0
- package/skills/arkts-error-fixes/reference/decorator_state_errors.md +109 -0
- package/skills/arkts-error-fixes/reference/display_listener_type_errors.md +149 -0
- package/skills/arkts-error-fixes/reference/duplicate_entry_errors.md +193 -0
- package/skills/arkts-error-fixes/reference/esobject_type_errors.md +110 -0
- package/skills/arkts-error-fixes/reference/fontcolor_property_errors.md +104 -0
- package/skills/arkts-error-fixes/reference/function_return_type_errors.md +194 -0
- package/skills/arkts-error-fixes/reference/idata_source_errors.md +77 -0
- package/skills/arkts-error-fixes/reference/implementation_not_allowed_errors.md +191 -0
- package/skills/arkts-error-fixes/reference/interface_method_signature_errors.md +143 -0
- package/skills/arkts-error-fixes/reference/notification_errors.md +110 -0
- package/skills/arkts-error-fixes/reference/object_literal_interface_errors.md +156 -0
- package/skills/arkts-error-fixes/reference/object_literal_type_errors.md +110 -0
- package/skills/arkts-error-fixes/reference/object_spread_errors.md +105 -0
- package/skills/arkts-error-fixes/reference/possibly_null_errors.md +156 -0
- package/skills/arkts-error-fixes/reference/resource_conversion_errors.md +154 -0
- package/skills/arkts-error-fixes/reference/standalone_function_errors.md +68 -0
- package/skills/arkts-error-fixes/reference/storage_link_default_errors.md +124 -0
- package/skills/arkts-error-fixes/reference/title_button_rect_type_errors.md +167 -0
- package/skills/arkts-error-fixes/reference/unused_variable_warnings.md +295 -0
- package/skills/arkts-error-fixes/reference/utility_type_errors.md +290 -0
- package/skills/arkts-error-fixes/reference/window_rect_size_errors.md +70 -0
- package/skills/arkts-error-fixes/reference/window_type_errors.md +95 -0
- package/skills/arkts-grammar-standards/SKILL.md +71 -0
- package/skills/arkts-grammar-standards/references/basic-syntax.md +103 -0
- package/skills/arkts-grammar-standards/references/restrictions.md +100 -0
- package/skills/arkts-grammar-standards/references/topic-aliases.json +93 -0
- package/skills/arkts-grammar-standards/references/ts-diff.md +108 -0
- package/skills/arkts-runtime-fix/SKILL.md +152 -0
- package/skills/arkts-runtime-fix/SKILL_CN.md +123 -0
- package/skills/arkts-runtime-fix/evals/evals.json +17 -0
- package/skills/arkts-runtime-fix/scripts/collect-hilog.mjs +116 -0
- package/skills/arkts-runtime-fix/scripts/collect-hilog.ts +115 -0
- package/skills/arkts-runtime-fix/scripts/fetch-faultlog.mjs +117 -0
- package/skills/arkts-runtime-fix/scripts/fetch-faultlog.ts +116 -0
- package/skills/arkts-runtime-fix/scripts/jscrash-report.mjs +149 -0
- package/skills/arkts-runtime-fix/scripts/parse-jscrash-log.mjs +126 -0
- package/skills/arkts-runtime-fix/scripts/parse-jscrash-log.ts +126 -0
- package/skills/arkts-runtime-fix/scripts/probe-faultlogger.mjs +160 -0
- package/skills/arkts-runtime-fix/scripts/probe-faultlogger.ts +160 -0
- package/skills/arkts-runtime-fix/scripts/shared/hdc.mjs +107 -0
- package/skills/arkts-runtime-fix/scripts/shared/hdc.ts +58 -0
- package/skills/arkts-runtime-fix/scripts/shared/jscrash-faultlogger.mjs +143 -0
- package/skills/arkts-runtime-fix/scripts/shared/jscrash-faultlogger.ts +148 -0
- package/skills/arkts-runtime-fix/scripts/shared/jscrash-parse.mjs +389 -0
- package/skills/arkts-runtime-fix/scripts/shared/jscrash-parse.ts +409 -0
- package/skills/arkts-runtime-fix/scripts/shared/utils.mjs +27 -0
- package/skills/arkts-runtime-fix/scripts/shared/utils.ts +27 -0
- package/skills/arkui-knowledge/SKILL.md +90 -0
- package/skills/arkui-knowledge/references/api-guardrails.md +60 -0
- package/skills/arkui-knowledge/references/common-mistakes.md +119 -0
- package/skills/arkui-knowledge/references/component-cookbook.md +135 -0
- package/skills/arkui-knowledge/references/ui-quality-checklist.md +36 -0
- package/skills/deveco-create-project/SKILL.md +144 -0
- package/skills/deveco-create-project/application/AppScope/app.json5 +10 -0
- package/skills/deveco-create-project/application/AppScope/resources/base/element/string.json +8 -0
- package/skills/deveco-create-project/application/AppScope/resources/base/media/background.png +0 -0
- package/skills/deveco-create-project/application/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/deveco-create-project/application/AppScope/resources/base/media/layered_image.json +7 -0
- package/skills/deveco-create-project/application/build-profile.json5 +42 -0
- package/skills/deveco-create-project/application/code-linter.json5 +32 -0
- package/skills/deveco-create-project/application/entry/build-profile.json5 +33 -0
- package/skills/deveco-create-project/application/entry/hvigorfile.ts +7 -0
- package/skills/deveco-create-project/application/entry/obfuscation-rules.txt +23 -0
- package/skills/deveco-create-project/application/entry/oh-package.json5 +10 -0
- package/skills/deveco-create-project/application/entry/src/main/ets/entryability/EntryAbility.ets +63 -0
- package/skills/deveco-create-project/application/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +31 -0
- package/skills/deveco-create-project/application/entry/src/main/ets/pages/Index.ets +38 -0
- package/skills/deveco-create-project/application/entry/src/main/module.json5 +50 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/element/color.json +8 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/element/float.json +8 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/element/string.json +16 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/media/layered_image.json +7 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/profile/backup_config.json +3 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/base/profile/main_pages.json +5 -0
- package/skills/deveco-create-project/application/entry/src/main/resources/dark/element/color.json +8 -0
- package/skills/deveco-create-project/application/hvigor/hvigor-config.json5 +23 -0
- package/skills/deveco-create-project/application/hvigorfile.ts +7 -0
- package/skills/deveco-create-project/application/oh-package.json5 +10 -0
- package/skills/deveco-create-project/scripts/copy-template.mjs +216 -0
- package/skills/deveco-create-project/scripts/copy-template.ts +214 -0
- package/skills/deveco-create-project/scripts/detect-sdk.mjs +82 -0
- package/skills/deveco-create-project/scripts/detect-sdk.ts +95 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import process from 'node:process';
|
|
17
|
+
import {
|
|
18
|
+
extractJscrashFaultlogNames,
|
|
19
|
+
filterFaultlogsByBundle,
|
|
20
|
+
parseFaultlogEntry,
|
|
21
|
+
selectWithinMaxAge,
|
|
22
|
+
sortFaultlogsByRecency,
|
|
23
|
+
} from './shared/jscrash-faultlogger';
|
|
24
|
+
import { resolveHdcOrThrow, runHdc, targetArgs } from './shared/hdc';
|
|
25
|
+
import { printKv, toErrorMessage } from './shared/utils';
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv: string[]) {
|
|
28
|
+
const map = new Map<string, string>();
|
|
29
|
+
const optionalKeys = new Set(['bundle-name', 'device-id']);
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
32
|
+
const t = argv[i];
|
|
33
|
+
if (!t.startsWith('--')) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const key = t.slice(2);
|
|
37
|
+
const val = argv[i + 1];
|
|
38
|
+
if (!val || val.startsWith('--')) {
|
|
39
|
+
if (optionalKeys.has(key)) {
|
|
40
|
+
map.set(key, '');
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new Error(`Missing value for --${key}`);
|
|
45
|
+
}
|
|
46
|
+
map.set(key, val);
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
bundleName: map.get('bundle-name') ?? '',
|
|
52
|
+
deviceId: map.get('device-id') ?? '',
|
|
53
|
+
maxAgeMinutes: Number(map.get('max-age-minutes') ?? '30'),
|
|
54
|
+
limit: Number(map.get('limit') ?? '10'),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readFaultloggerViaHidumper(hdc: string, deviceId: string) {
|
|
59
|
+
const args = [
|
|
60
|
+
hdc,
|
|
61
|
+
...targetArgs(deviceId),
|
|
62
|
+
'shell',
|
|
63
|
+
'hidumper',
|
|
64
|
+
'-s',
|
|
65
|
+
'1201',
|
|
66
|
+
'-a',
|
|
67
|
+
'-p Faultlogger LogSuffixWithMs',
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
return runHdc(args);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function readFaultloggerViaLs(hdc: string, deviceId: string) {
|
|
74
|
+
const remote = '/data/log/faultlog/faultlogger';
|
|
75
|
+
const args = [hdc, ...targetArgs(deviceId), 'shell', 'ls', '-1', remote];
|
|
76
|
+
|
|
77
|
+
return runHdc(args);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function formatCandidates(names: string[], limit: number) {
|
|
81
|
+
return names.slice(0, limit).join('|');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printProbeFailed(bundleName: string, nextAction: string) {
|
|
85
|
+
printKv({
|
|
86
|
+
status: 'probe_failed',
|
|
87
|
+
bundle_name: bundleName,
|
|
88
|
+
latest_faultlog: '',
|
|
89
|
+
latest_timestamp: '',
|
|
90
|
+
matched_count: '0',
|
|
91
|
+
candidates: '',
|
|
92
|
+
next_action: nextAction,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function printProbeNotFound(bundleName: string) {
|
|
97
|
+
printKv({
|
|
98
|
+
status: 'not_found',
|
|
99
|
+
bundle_name: bundleName,
|
|
100
|
+
latest_faultlog: '',
|
|
101
|
+
latest_timestamp: '',
|
|
102
|
+
matched_count: '0',
|
|
103
|
+
candidates: '',
|
|
104
|
+
next_action: 'No recent matching faultlog was found. You may collect hilog if you still need more runtime evidence, or reproduce and retry.',
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printProbeFound(bundleName: string, names: string[], limit: number) {
|
|
109
|
+
const latest = names[0];
|
|
110
|
+
const ts = parseFaultlogEntry(latest).timestampMs;
|
|
111
|
+
printKv({
|
|
112
|
+
status: 'found',
|
|
113
|
+
bundle_name: bundleName,
|
|
114
|
+
latest_faultlog: latest,
|
|
115
|
+
latest_timestamp: ts === null ? '' : String(ts),
|
|
116
|
+
matched_count: String(names.length),
|
|
117
|
+
candidates: formatCandidates(names, limit),
|
|
118
|
+
next_action: `A recent faultlog is available. Fetch it with fetch-faultlog.ts --faultlog-name "${latest}" if you want a cleaner crash anchor.`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function loadCandidateNames(hdc: string, deviceId: string, bundleName: string, maxAgeMinutes: number) {
|
|
123
|
+
const hidumper = await readFaultloggerViaHidumper(hdc, deviceId);
|
|
124
|
+
let combined = hidumper.exitCode === 0 ? hidumper.stdout : '';
|
|
125
|
+
|
|
126
|
+
const ls = await readFaultloggerViaLs(hdc, deviceId);
|
|
127
|
+
if (ls.exitCode === 0) {
|
|
128
|
+
combined = `${combined}\n${ls.stdout}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let names = extractJscrashFaultlogNames(combined);
|
|
132
|
+
names = filterFaultlogsByBundle(names, bundleName);
|
|
133
|
+
names = selectWithinMaxAge(names, maxAgeMinutes, Date.now());
|
|
134
|
+
return sortFaultlogsByRecency(names);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function main() {
|
|
138
|
+
let bundleName = '';
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const args = parseArgs(process.argv.slice(2));
|
|
142
|
+
bundleName = args.bundleName;
|
|
143
|
+
const hdc = await resolveHdcOrThrow();
|
|
144
|
+
const names = await loadCandidateNames(hdc, args.deviceId, args.bundleName, args.maxAgeMinutes);
|
|
145
|
+
|
|
146
|
+
if (!names.length) {
|
|
147
|
+
printProbeNotFound(args.bundleName);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
printProbeFound(args.bundleName, names, args.limit);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
printProbeFailed(bundleName, toErrorMessage(err));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
await main();
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import process from 'node:process';
|
|
19
|
+
import { spawn } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
function binary(name) {
|
|
22
|
+
return process.platform === 'win32' ? `${name}.exe` : name;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function envPath() {
|
|
26
|
+
return String(process.env.DEVECO_HOME || '').trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function isDir(file) {
|
|
30
|
+
if (!file) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return fs.stat(file).then((info) => info.isDirectory()).catch(() => false);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function nodePath(home) {
|
|
37
|
+
return process.platform === 'win32'
|
|
38
|
+
? path.join(home, 'tools', 'node', 'node.exe')
|
|
39
|
+
: path.join(home, 'tools', 'node', 'bin', 'node');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hdcPath(home) {
|
|
43
|
+
return path.join(home, 'sdk', 'default', 'openharmony', 'toolchains', binary('hdc'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function exists(file) {
|
|
47
|
+
return fs.access(file).then(() => true).catch(() => false);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function findDevEcoHome() {
|
|
51
|
+
const env = envPath();
|
|
52
|
+
if (env && (await isDir(env)) && (await exists(nodePath(env)))) {
|
|
53
|
+
return env;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function targetArgs(deviceId) {
|
|
58
|
+
return deviceId ? ['-t', deviceId] : [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function resolveHdcBinary() {
|
|
62
|
+
const home = await findDevEcoHome();
|
|
63
|
+
if (!home) {
|
|
64
|
+
return { hdc: '', home: '', error: 'DevEco Studio path not found. Set DEVECO_HOME and retry.' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const hdc = hdcPath(home);
|
|
68
|
+
if (!(await exists(hdc))) {
|
|
69
|
+
return { hdc: '', home, error: `hdc not found: ${hdc}` };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { hdc, home, error: '' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function resolveHdcOrThrow() {
|
|
76
|
+
const resolved = await resolveHdcBinary();
|
|
77
|
+
if (resolved.error) {
|
|
78
|
+
throw new Error(resolved.error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return resolved.hdc;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function runHdc(cmd) {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
const proc = spawn(cmd[0], cmd.slice(1), {
|
|
87
|
+
windowsHide: true,
|
|
88
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
89
|
+
});
|
|
90
|
+
let stdout = '';
|
|
91
|
+
let stderr = '';
|
|
92
|
+
proc.stdout.setEncoding('utf8');
|
|
93
|
+
proc.stderr.setEncoding('utf8');
|
|
94
|
+
proc.stdout.on('data', (chunk) => {
|
|
95
|
+
stdout += chunk;
|
|
96
|
+
});
|
|
97
|
+
proc.stderr.on('data', (chunk) => {
|
|
98
|
+
stderr += chunk;
|
|
99
|
+
});
|
|
100
|
+
proc.on('error', (error) => {
|
|
101
|
+
resolve({ stdout, stderr: error.message || stderr, exitCode: 1 });
|
|
102
|
+
});
|
|
103
|
+
proc.on('close', (code) => {
|
|
104
|
+
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { findDevEcoHome, hdcPath } from '../../../../../src/tool/lib/env';
|
|
17
|
+
|
|
18
|
+
export function targetArgs(deviceId: string | undefined) {
|
|
19
|
+
return deviceId ? ['-t', deviceId] : [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function resolveHdcBinary() {
|
|
23
|
+
const home = await findDevEcoHome();
|
|
24
|
+
if (!home) {
|
|
25
|
+
return { hdc: '', home: '', error: 'DevEco Studio path not found. Set DEVECO_HOME and retry.' };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const hdc = hdcPath(home);
|
|
29
|
+
if (!(await Bun.file(hdc).exists())) {
|
|
30
|
+
return { hdc: '', home, error: `hdc not found: ${hdc}` };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { hdc, home, error: '' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function resolveHdcOrThrow() {
|
|
37
|
+
const resolved = await resolveHdcBinary();
|
|
38
|
+
if (resolved.error) {
|
|
39
|
+
throw new Error(resolved.error);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return resolved.hdc;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function runHdc(cmd: string[]) {
|
|
46
|
+
const proc = Bun.spawn({
|
|
47
|
+
cmd,
|
|
48
|
+
stdout: 'pipe',
|
|
49
|
+
stderr: 'pipe',
|
|
50
|
+
});
|
|
51
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
52
|
+
proc.stdout ? Bun.readableStreamToText(proc.stdout) : Promise.resolve(''),
|
|
53
|
+
proc.stderr ? Bun.readableStreamToText(proc.stderr) : Promise.resolve(''),
|
|
54
|
+
proc.exited,
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return { stdout, stderr, exitCode };
|
|
58
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const JSCRASH_FILE_RE = /\bjscrash-[A-Za-z0-9._-]+\.log\b/g;
|
|
17
|
+
const JSCRASH_NAME_RE = /^jscrash-(.+)-(\d{10,17})\.log$/i;
|
|
18
|
+
|
|
19
|
+
export function extractJscrashFaultlogNames(text) {
|
|
20
|
+
const matches = text.match(JSCRASH_FILE_RE);
|
|
21
|
+
if (!matches?.length) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return [...new Set(matches.map((item) => item.trim()))];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function parseTrailingTimestampMs(raw) {
|
|
29
|
+
const n = Number(raw);
|
|
30
|
+
if (!Number.isFinite(n)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (raw.length <= 11) {
|
|
35
|
+
return n * 1000;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractBundlePartFromBody(body) {
|
|
42
|
+
const uidSep = body.lastIndexOf('-');
|
|
43
|
+
if (uidSep <= 0) {
|
|
44
|
+
return body;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const maybeUid = body.slice(uidSep + 1);
|
|
48
|
+
if (/^\d{5,}$/.test(maybeUid)) {
|
|
49
|
+
return body.slice(0, uidSep);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return body;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseJscrashFaultlogName(name) {
|
|
56
|
+
const match = JSCRASH_NAME_RE.exec(name);
|
|
57
|
+
if (!match?.[1] || !match[2]) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
bundlePart: extractBundlePartFromBody(match[1]),
|
|
63
|
+
timestampMs: parseTrailingTimestampMs(match[2]),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseFaultlogEntry(name) {
|
|
68
|
+
const parsed = parseJscrashFaultlogName(name);
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
timestampMs: parsed?.timestampMs ?? null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeBundleToken(bundleName) {
|
|
76
|
+
return bundleName.trim().toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function faultlogMatchesBundle(name, bundleName) {
|
|
80
|
+
const token = normalizeBundleToken(bundleName);
|
|
81
|
+
if (!token) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const parsed = parseJscrashFaultlogName(name);
|
|
86
|
+
if (parsed?.bundlePart) {
|
|
87
|
+
return parsed.bundlePart.toLowerCase() === token;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return name.toLowerCase().startsWith(`jscrash-${token}-`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function filterFaultlogsByBundle(names, bundleName) {
|
|
94
|
+
if (!bundleName.trim()) {
|
|
95
|
+
return names;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const filtered = names.filter((name) => faultlogMatchesBundle(name, bundleName));
|
|
99
|
+
return filtered.length ? filtered : names;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function sortFaultlogsByRecency(names) {
|
|
103
|
+
const withMeta = names.map((name) => {
|
|
104
|
+
const entry = parseFaultlogEntry(name);
|
|
105
|
+
return { name, timestampMs: entry.timestampMs };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
withMeta.sort((a, b) => {
|
|
109
|
+
const ta = a.timestampMs;
|
|
110
|
+
const tb = b.timestampMs;
|
|
111
|
+
if (ta !== null && tb !== null && ta !== tb) {
|
|
112
|
+
return tb - ta;
|
|
113
|
+
}
|
|
114
|
+
if (ta !== null && tb === null) {
|
|
115
|
+
return -1;
|
|
116
|
+
}
|
|
117
|
+
if (ta === null && tb !== null) {
|
|
118
|
+
return 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return b.name.localeCompare(a.name);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return withMeta.map((item) => item.name);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function selectWithinMaxAge(names, maxAgeMinutes, nowMs) {
|
|
128
|
+
if (!Number.isFinite(maxAgeMinutes) || maxAgeMinutes <= 0) {
|
|
129
|
+
return names;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const windowMs = maxAgeMinutes * 60 * 1000;
|
|
133
|
+
const filtered = names.filter((name) => {
|
|
134
|
+
const ts = parseFaultlogEntry(name).timestampMs;
|
|
135
|
+
if (ts === null) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return nowMs - ts <= windowMs;
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return filtered.length ? filtered : names;
|
|
143
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026 Huawei Device Co., Ltd.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type JscrashFaultlogEntry = {
|
|
17
|
+
name: string;
|
|
18
|
+
timestampMs: number | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const JSCRASH_FILE_RE = /\bjscrash-[A-Za-z0-9._-]+\.log\b/g;
|
|
22
|
+
const JSCRASH_NAME_RE = /^jscrash-(.+)-(\d{10,17})\.log$/i;
|
|
23
|
+
|
|
24
|
+
export function extractJscrashFaultlogNames(text: string): string[] {
|
|
25
|
+
const matches = text.match(JSCRASH_FILE_RE);
|
|
26
|
+
if (!matches?.length) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return [...new Set(matches.map((item) => item.trim()))];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseTrailingTimestampMs(raw: string): number | null {
|
|
34
|
+
const n = Number(raw);
|
|
35
|
+
if (!Number.isFinite(n)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (raw.length <= 11) {
|
|
40
|
+
return n * 1000;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return n;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractBundlePartFromBody(body: string) {
|
|
47
|
+
const uidSep = body.lastIndexOf('-');
|
|
48
|
+
if (uidSep <= 0) {
|
|
49
|
+
return body;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const maybeUid = body.slice(uidSep + 1);
|
|
53
|
+
if (/^\d{5,}$/.test(maybeUid)) {
|
|
54
|
+
return body.slice(0, uidSep);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return body;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseJscrashFaultlogName(name: string) {
|
|
61
|
+
const match = JSCRASH_NAME_RE.exec(name);
|
|
62
|
+
if (!match?.[1] || !match[2]) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
bundlePart: extractBundlePartFromBody(match[1]),
|
|
68
|
+
timestampMs: parseTrailingTimestampMs(match[2]),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parseFaultlogEntry(name: string): JscrashFaultlogEntry {
|
|
73
|
+
const parsed = parseJscrashFaultlogName(name);
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
timestampMs: parsed?.timestampMs ?? null,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeBundleToken(bundleName: string) {
|
|
81
|
+
return bundleName.trim().toLowerCase();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function faultlogMatchesBundle(name: string, bundleName: string) {
|
|
85
|
+
const token = normalizeBundleToken(bundleName);
|
|
86
|
+
if (!token) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const parsed = parseJscrashFaultlogName(name);
|
|
91
|
+
if (parsed?.bundlePart) {
|
|
92
|
+
return parsed.bundlePart.toLowerCase() === token;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return name.toLowerCase().startsWith(`jscrash-${token}-`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function filterFaultlogsByBundle(names: string[], bundleName: string) {
|
|
99
|
+
if (!bundleName.trim()) {
|
|
100
|
+
return names;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const filtered = names.filter((name) => faultlogMatchesBundle(name, bundleName));
|
|
104
|
+
return filtered.length ? filtered : names;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function sortFaultlogsByRecency(names: string[]) {
|
|
108
|
+
const withMeta = names.map((name) => {
|
|
109
|
+
const entry = parseFaultlogEntry(name);
|
|
110
|
+
return { name, timestampMs: entry.timestampMs };
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
withMeta.sort((a, b) => {
|
|
114
|
+
const ta = a.timestampMs;
|
|
115
|
+
const tb = b.timestampMs;
|
|
116
|
+
if (ta !== null && tb !== null && ta !== tb) {
|
|
117
|
+
return tb - ta;
|
|
118
|
+
}
|
|
119
|
+
if (ta !== null && tb === null) {
|
|
120
|
+
return -1;
|
|
121
|
+
}
|
|
122
|
+
if (ta === null && tb !== null) {
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return b.name.localeCompare(a.name);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return withMeta.map((item) => item.name);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function selectWithinMaxAge(names: string[], maxAgeMinutes: number, nowMs: number) {
|
|
133
|
+
if (!Number.isFinite(maxAgeMinutes) || maxAgeMinutes <= 0) {
|
|
134
|
+
return names;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const windowMs = maxAgeMinutes * 60 * 1000;
|
|
138
|
+
const filtered = names.filter((name) => {
|
|
139
|
+
const ts = parseFaultlogEntry(name).timestampMs;
|
|
140
|
+
if (ts === null) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return nowMs - ts <= windowMs;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
return filtered.length ? filtered : names;
|
|
148
|
+
}
|