android2harmony 0.1.0 → 0.1.2
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 -407
- package/agents/code-reviewer.md +16 -17
- package/agents/scripts/hmos-precheck/index.mjs +0 -8
- package/dist/index.js +10 -70
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
- package/skills/hmos-batch-ui-align/SKILL.md +5 -5
- package/skills/hmos-batch-ui-align/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +373 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +158 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +750 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +1704 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1012 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +164 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +840 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +856 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +83 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +294 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/PersistenceV2/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 +1220 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
- package/skills/hmos-batch-ui-align/references/mvvm-v2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md +840 -0
- package/skills/hmos-convert-pipeline/SKILL.md +37 -42
- package/skills/hmos-fix-build-errors/SKILL.md +20 -35
- package/skills/hmos-resources-convert/SKILL.md +2 -2
- package/skills/hmos-spec-generate/SKILL.md +10 -21
- package/agents/build-fixer.md +0 -385
- package/skills/hmos-spec-generate/references/android-platform-tokens.md +0 -105
- package/skills/hmos-spec-generate/references/spec-sample-1.md +0 -78
- package/skills/hmos-spec-generate/references/spec-sample-2.md +0 -58
- package/skills/hmos-spec-generate/references/spec-sample-3.md +0 -116
- package/skills/hmos-spec-generate/references/step4-report-template.md +0 -33
package/agents/code-reviewer.md
CHANGED
|
@@ -46,7 +46,7 @@ Before starting the review, extract the code context for the commit by running t
|
|
|
46
46
|
- `--commit`: the git commit to analyze — diffs against its first parent.
|
|
47
47
|
- `--mode default`: builds the full call graph for call-chain context.
|
|
48
48
|
- **Timeout (required).** Building the full call graph can hang or run very long on large projects, and the first `npx` run also has to download the package — so always cap this command. When you invoke it via the Bash tool, pass an explicit `timeout` of **600000** ms (10 minutes). Do **not** rely on the tool's implicit default. If the command exceeds this limit and is killed, treat it exactly like a failure and take the fallback in step 3.
|
|
49
|
-
- **SDK paths** are resolved by ArkAnalysis itself
|
|
49
|
+
- **SDK paths** are resolved by ArkAnalysis itself from the OS environment — `OHOS_SDK_PATH` / `HMS_SDK_PATH` — so you normally pass nothing. Only if it exits with an error saying the SDK paths were not found, resolve them yourself and pass `--ohos-sdk "<...>/default/openharmony/ets"` and `--hms-sdk "<...>/default/hms/ets"` (deriving from `DEVECO_SDK_HOME` if set), or ask the user for the SDK root and suggest they set `OHOS_SDK_PATH` / `HMS_SDK_PATH` (or `DEVECO_SDK_HOME`) as machine environment variables.
|
|
50
50
|
|
|
51
51
|
2. **On success** — it prints a JSON array to stdout: one entry per affected file (`{ path, kind, ranges?, resourceNames? }`). Build the **code context** from it as follows, reading only what the output points at — **do not read whole files**:
|
|
52
52
|
|
|
@@ -71,7 +71,7 @@ Before starting the review, extract the code context for the commit by running t
|
|
|
71
71
|
- If `test_case_path` is not provided, use `<output_path>/test_case.md`
|
|
72
72
|
|
|
73
73
|
1. **Read the code context** (from Step 0):
|
|
74
|
-
- Parse the code context produced by ArkAnalysis (`npx arkanalysis`) (or the manually assembled fallback)
|
|
74
|
+
- Parse the code context produced by ArkAnalysis (`npx --yes arkanalysis`) (or the manually assembled fallback)
|
|
75
75
|
- This contains the diff and relevant code context for commit `commit_id`
|
|
76
76
|
- Understand the scope of changes: which files were modified, added, or deleted
|
|
77
77
|
- Build a map of the changed code areas — these are the primary focus of the review
|
|
@@ -182,7 +182,7 @@ Sort all defects into this fix order:
|
|
|
182
182
|
5. **PARTIAL scenarios** — in report order
|
|
183
183
|
6. **Cross-cutting: State management** and other quality issues — lowest priority
|
|
184
184
|
|
|
185
|
-
**Maximum 2 effective attempts** per defect (an effective attempt = code was modified AND compiles successfully;
|
|
185
|
+
**Maximum 2 effective attempts** per defect (an effective attempt = code was modified AND compiles successfully; compile-fix retries inside `hmos-fix-build-errors` do NOT count as an attempt).
|
|
186
186
|
|
|
187
187
|
### 5b. Fix strategies
|
|
188
188
|
|
|
@@ -192,16 +192,14 @@ Apply the strategy that matches the defect category.
|
|
|
192
192
|
|
|
193
193
|
**When**: `module.json5` is missing a required permission.
|
|
194
194
|
|
|
195
|
-
1. **Verify the permission name is valid** — use
|
|
195
|
+
1. **Verify the permission name is valid** — first use `npx --yes devecocli docs search` / `npx --yes devecocli docs read` to confirm the permission exists in the HarmonyOS docs; fall back to WebSearch only if the local docs flow does not surface the needed reference.
|
|
196
196
|
2. Read `module.json5` and locate the `requestPermissions` array (create it if absent).
|
|
197
197
|
3. Add the missing permission entry:
|
|
198
198
|
```json
|
|
199
|
-
{ "name": "ohos.permission.XXX" }
|
|
200
|
-
```
|
|
201
199
|
4. If the feature requires **runtime permission** (e.g., camera, location, media), also add the runtime request code:
|
|
202
200
|
- Search the Android source (if available) to see how the permission is requested.
|
|
203
201
|
- Add `abilityAccessCtrl.createAtManager().requestPermissionsFromUser()` in the appropriate lifecycle (`onWindowStageCreate` or `aboutToAppear`).
|
|
204
|
-
-
|
|
202
|
+
- Prefer `npx --yes devecocli docs search` / `npx --yes devecocli docs read` for the HarmonyOS API reference, then fall back to WebSearch if the local docs are insufficient.
|
|
205
203
|
|
|
206
204
|
#### Page/Component Creation
|
|
207
205
|
|
|
@@ -210,7 +208,7 @@ Apply the strategy that matches the defect category.
|
|
|
210
208
|
1. **Read the Android source** (if `android_project_dir` is provided):
|
|
211
209
|
- Find the corresponding Android Activity/Fragment/View.
|
|
212
210
|
- Document: layout structure, event handlers, data sources, navigation targets.
|
|
213
|
-
2. **Use
|
|
211
|
+
2. **Use `npx --yes devecocli docs search` / `npx --yes devecocli docs read` first** to look up the HarmonyOS equivalent UI components (e.g., `Grid` for `RecyclerView`, `List` for `ListView`). Fall back to WebSearch only when the local docs flow is insufficient.
|
|
214
212
|
3. **Create the page file** under `entry/src/main/ets/pages/`:
|
|
215
213
|
- Implement a **minimal viable page**: basic UI structure + essential state variables + placeholder data.
|
|
216
214
|
- Do NOT attempt to implement all business logic — focus on making the scenario's happy path work.
|
|
@@ -223,8 +221,8 @@ Apply the strategy that matches the defect category.
|
|
|
223
221
|
|
|
224
222
|
1. **Identify the correct API**:
|
|
225
223
|
- If Android source is available, find the corresponding Android API call and determine the HarmonyOS equivalent.
|
|
226
|
-
-
|
|
227
|
-
- Use WebSearch as fallback
|
|
224
|
+
- Prefer `npx --yes devecocli docs search` / `npx --yes devecocli docs read` for HarmonyOS API documentation.
|
|
225
|
+
- Use WebSearch as fallback when the local docs flow does not provide enough detail.
|
|
228
226
|
2. **Do NOT guess API signatures** — always verify import path, function name, parameter types, and return type from documentation.
|
|
229
227
|
3. Add the import statement and the API call in the correct location.
|
|
230
228
|
|
|
@@ -274,7 +272,7 @@ Apply the strategy that matches the defect category.
|
|
|
274
272
|
- **V1**: `@State` on a non-primitive without `@Observed` on the class; `@Link` without a `$variable` binding from the parent; missing `@Watch` when a side-effect is needed on state change; `@Observed` class property used in UI but missing `@Track`.
|
|
275
273
|
- **V2**: `@ObservedV2` class property observed in UI but missing `@Trace` (both must be used together — either alone is inert); `@Param` mutated inside the child (it is input-only — emit changes via `@Event`); `@Local` expected to receive external input (use `@Param` instead).
|
|
276
274
|
- **Mixing**: V1 and V2 decorators in the same component (e.g. `@Local` inside `@Component`, or `@State` inside `@ComponentV2`) — align to the detected paradigm.
|
|
277
|
-
3. Use
|
|
275
|
+
3. Use `npx --yes devecocli docs search` / `npx --yes devecocli docs read` first to verify the correct decorator combination if unsure. Fall back to WebSearch only when the local docs are insufficient.
|
|
278
276
|
|
|
279
277
|
---
|
|
280
278
|
|
|
@@ -282,10 +280,10 @@ Apply the strategy that matches the defect category.
|
|
|
282
280
|
|
|
283
281
|
After completing fixes for a group of related defects (e.g., all defects in one scenario, or all permission defects), verify the project still compiles:
|
|
284
282
|
|
|
285
|
-
1.
|
|
286
|
-
2. If compilation fails,
|
|
283
|
+
1. Invoke the `hmos-fix-build-errors` skill with the current HarmonyOS project path.
|
|
284
|
+
2. If compilation fails, let `hmos-fix-build-errors` handle the compile-error fix loop — these retries do **not** count as an effective attempt.
|
|
287
285
|
3. If compilation succeeds, proceed to the next group of defects.
|
|
288
|
-
4. If compilation cannot be fixed after build-
|
|
286
|
+
4. If compilation still cannot be fixed after the build-fix skill completes, **revert the most recent changes** for that group and record the defect as "failed to fix — compilation error".
|
|
289
287
|
|
|
290
288
|
---
|
|
291
289
|
|
|
@@ -302,7 +300,7 @@ Write the merged review + fix report to `<output_path>/code-review-report.md` wi
|
|
|
302
300
|
- **Commit ID**: <commit_id>
|
|
303
301
|
- **Scenario Doc**: <test_case_path>
|
|
304
302
|
- **Android Source**: <android_project_dir or "not provided">
|
|
305
|
-
- **Code Context**: ArkAnalysis (npx arkanalysis) (or git-diff fallback)
|
|
303
|
+
- **Code Context**: ArkAnalysis (npx --yes arkanalysis) (or git-diff fallback)
|
|
306
304
|
- **Review Date**: <date>
|
|
307
305
|
- **Total Scenarios**: <N>
|
|
308
306
|
- **Scenario Results**: <X> PASS | <Y> PARTIAL | <Z> FAIL | <W> UNABLE TO VERIFY
|
|
@@ -341,7 +339,7 @@ Write the merged review + fix report to `<output_path>/code-review-report.md` wi
|
|
|
341
339
|
- Android Reference: <description, if `android_project_dir` was used>
|
|
342
340
|
- Files Modified:
|
|
343
341
|
- `<file path>`: <what was changed>
|
|
344
|
-
- API Documentation Used: <
|
|
342
|
+
- API Documentation Used: <npx --yes devecocli docs query/doc title or WebSearch query, if used>
|
|
345
343
|
- Compilation: PASS / FAIL
|
|
346
344
|
- Notes: <caveats, follow-up needed>
|
|
347
345
|
|
|
@@ -466,7 +464,8 @@ Total defects: {T}, Fixed: {A}, Failed: {B}
|
|
|
466
464
|
- **Be fair**: If the code works for a scenario, give it PASS — don't nitpick style in a scenario review.
|
|
467
465
|
- **Derive implicit scenarios**: If the scenario document describes architecture (pages, modules, data flows) rather than explicit user stories, derive the scenarios yourself.
|
|
468
466
|
- **No re-verification**: You found the defects; do not launch a second verification pass before fixing. Trust your own findings.
|
|
469
|
-
- **Look up before you guess**:
|
|
467
|
+
- **Look up before you guess**: Prefer `npx --yes devecocli docs search` / `npx --yes devecocli docs read` for HarmonyOS API usage, parameter types, and patterns. Fall back to WebSearch only when the local docs flow is insufficient. Do not assume API signatures.
|
|
468
|
+
- **Use device logs for runtime-only clues**: When a scenario failure appears device-specific or cannot be closed statically, prefer `npx --yes devecocli log --level E` and `npx --yes devecocli log --crash --bundle-name <bundle>` before broader manual diagnosis.
|
|
470
469
|
- **Read before you edit**: Always read a file before modifying it. Understand the surrounding context.
|
|
471
470
|
- **Android as specification**: When available, treat the Android implementation as the ground truth for expected behavior.
|
|
472
471
|
- **Minimal changes**: Only fix identified defects. Do not refactor, add comments to unrelated code, or "improve" working code.
|
|
@@ -49,9 +49,6 @@ function defaultUserConfigPath() {
|
|
|
49
49
|
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
50
50
|
return path.join(xdg, "hmos-precheck", USER_CONFIG_BASENAME);
|
|
51
51
|
}
|
|
52
|
-
function defaultHomeTransConfigPath() {
|
|
53
|
-
return path.join(os.homedir(), ".hometrans", "config.json");
|
|
54
|
-
}
|
|
55
52
|
function commandlineToolsHelp() {
|
|
56
53
|
return "Run `hmos-precheck init --scope user --commandline-tools /abs/path/to/command-line-tools`, or pass `--commandline-tools`.";
|
|
57
54
|
}
|
|
@@ -135,10 +132,6 @@ async function resolveCommandlineToolsInfo(options) {
|
|
|
135
132
|
const projectResult = await wrap(typeof value === "string" ? value : null, "project_config");
|
|
136
133
|
if (projectResult) return projectResult;
|
|
137
134
|
}
|
|
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
135
|
const userConfig = await loadJson(options.userConfigPath ?? defaultUserConfigPath());
|
|
143
136
|
const userValue = isObject(userConfig?.toolchain) ? userConfig.toolchain.commandline_tools : void 0;
|
|
144
137
|
const userResult = await wrap(typeof userValue === "string" ? userValue : null, "user_config");
|
|
@@ -837,7 +830,6 @@ async function cmdDoctor(argv) {
|
|
|
837
830
|
},
|
|
838
831
|
config_paths: {
|
|
839
832
|
project: projectRoot ? path.join(projectRoot, PROJECT_CONFIG_NAME) : null,
|
|
840
|
-
hometrans: defaultHomeTransConfigPath(),
|
|
841
833
|
user: defaultUserConfigPath()
|
|
842
834
|
},
|
|
843
835
|
profiles: {
|
package/dist/index.js
CHANGED
|
@@ -20,67 +20,16 @@ function parseFrontmatter(content) {
|
|
|
20
20
|
const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
|
|
21
21
|
if (!match) return null;
|
|
22
22
|
const lines = match[1].split(/\r?\n/);
|
|
23
|
-
let name = null;
|
|
24
|
-
let hidden = false;
|
|
25
|
-
let inMetadata = false;
|
|
26
|
-
let metadataIndent = -1;
|
|
27
23
|
for (const line of lines) {
|
|
28
24
|
if (!line.trim() || line.trim().startsWith("#")) continue;
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
if (!kv) continue;
|
|
35
|
-
const value = kv[2].trim();
|
|
36
|
-
if (kv[1] === "name") {
|
|
37
|
-
name = value.replace(/^["']|["']$/g, "");
|
|
38
|
-
} else if (kv[1] === "metadata") {
|
|
39
|
-
if (value === "") {
|
|
40
|
-
inMetadata = true;
|
|
41
|
-
metadataIndent = -1;
|
|
42
|
-
} else if (/visible\s*:\s*false/.test(value)) {
|
|
43
|
-
hidden = true;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
} else if (inMetadata) {
|
|
47
|
-
if (metadataIndent === -1) metadataIndent = indent;
|
|
48
|
-
if (indent < metadataIndent) {
|
|
49
|
-
inMetadata = false;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (kv && kv[1] === "visible" && kv[2].trim() === "false") {
|
|
53
|
-
hidden = true;
|
|
54
|
-
}
|
|
25
|
+
if (line.length - line.trimStart().length !== 0) continue;
|
|
26
|
+
const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
|
|
27
|
+
if (!kv) continue;
|
|
28
|
+
if (kv[1] === "name") {
|
|
29
|
+
return kv[2].trim().replace(/^["']|["']$/g, "");
|
|
55
30
|
}
|
|
56
31
|
}
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
function discoverA2HSkills(skillsRoot) {
|
|
60
|
-
const hidden = [];
|
|
61
|
-
const all = [];
|
|
62
|
-
let entries;
|
|
63
|
-
try {
|
|
64
|
-
entries = fs.readdirSync(skillsRoot, { withFileTypes: true });
|
|
65
|
-
} catch {
|
|
66
|
-
return { hidden, all };
|
|
67
|
-
}
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (!entry.isDirectory()) continue;
|
|
70
|
-
const skillFile = path2.join(skillsRoot, entry.name, "SKILL.md");
|
|
71
|
-
let raw;
|
|
72
|
-
try {
|
|
73
|
-
raw = fs.readFileSync(skillFile, "utf-8");
|
|
74
|
-
} catch {
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
const info = parseFrontmatter(raw);
|
|
78
|
-
if (info) {
|
|
79
|
-
all.push(info.name);
|
|
80
|
-
if (info.hidden) hidden.push(info.name);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return { hidden, all };
|
|
32
|
+
return null;
|
|
84
33
|
}
|
|
85
34
|
function discoverA2HAgents(agentsRoot) {
|
|
86
35
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -99,9 +48,9 @@ function discoverA2HAgents(agentsRoot) {
|
|
|
99
48
|
} catch {
|
|
100
49
|
continue;
|
|
101
50
|
}
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
104
|
-
result.set(
|
|
51
|
+
const name = parseFrontmatter(raw);
|
|
52
|
+
if (!name) continue;
|
|
53
|
+
result.set(name, stripFrontmatter(raw));
|
|
105
54
|
}
|
|
106
55
|
return result;
|
|
107
56
|
}
|
|
@@ -131,7 +80,7 @@ function createServer(deps) {
|
|
|
131
80
|
dispose: async () => {
|
|
132
81
|
agentPrompts.clear();
|
|
133
82
|
},
|
|
134
|
-
// ── config hook: register skills search paths
|
|
83
|
+
// ── config hook: register skills search paths and agents ──
|
|
135
84
|
config: async (cfg) => {
|
|
136
85
|
const skills = cfg.skills || {};
|
|
137
86
|
cfg.skills = skills;
|
|
@@ -140,15 +89,6 @@ function createServer(deps) {
|
|
|
140
89
|
if (!paths.includes(skillsRoot)) {
|
|
141
90
|
paths.push(skillsRoot);
|
|
142
91
|
}
|
|
143
|
-
const { hidden: a2hHidden, all: a2hAll } = discoverA2HSkills(skillsRoot);
|
|
144
|
-
const currentHidden = skills.hidden || [];
|
|
145
|
-
const cleaned = currentHidden.filter((name) => !a2hAll.includes(name));
|
|
146
|
-
for (const name of a2hHidden) {
|
|
147
|
-
if (!cleaned.includes(name)) {
|
|
148
|
-
cleaned.push(name);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
skills.hidden = cleaned;
|
|
152
92
|
const agent = cfg.agent || {};
|
|
153
93
|
cfg.agent = agent;
|
|
154
94
|
for (const [name, prompt] of agentPrompts) {
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts"],
|
|
4
|
-
"sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents, discoverA2HSkills } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths, hidden skills, and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n \r\n\r\n // Register the bundled skills/ directory as an additional search path\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // Manage hidden skill names, discovered from each SKILL.md's\r\n // metadata.visible: false frontmatter. DevEco Code's available()\r\n // filters these out of the user-facing /skills menu; get()/require()\r\n // still resolve them so other skills can invoke them by name.\r\n //\r\n // We fully manage A2H entries in the hidden list: stale entries from\r\n // skills that are renamed, removed, or changed to visible are cleaned\r\n // up, while non-A2H entries (from other plugins) are left untouched.\r\n const { hidden: a2hHidden, all: a2hAll } = discoverA2HSkills(skillsRoot)\r\n const currentHidden = (skills.hidden as string[]) || []\r\n // Remove any A2H-managed skills that are no longer hidden / no longer exist\r\n const cleaned = currentHidden.filter((name) => !a2hAll.includes(name))\r\n // Add currently-hidden A2H skills\r\n for (const name of a2hHidden) {\r\n if (!cleaned.includes(name)) {\r\n cleaned.push(name)\r\n }\r\n }\r\n skills.hidden = cleaned\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio paths as env vars \u2500\u2500\r\n // NOTE: deliberately uses the *base* env \u2014 the multimodal model apiKey\r\n // is NOT injected into LLM shell sessions (would leak into every command\r\n // and the transcript).\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * two values: the top-level `name` (a scalar) and whether `metadata.visible`\r\n * is false. This avoids pulling a YAML dependency into the plugin.\r\n *\r\n * Returns `{ name, hidden }` or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): { name: string; hidden: boolean } | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n let name: string | null = null\r\n let hidden = false\r\n let inMetadata = false\r\n let metadataIndent = -1\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n\r\n const indent = line.length - line.trimStart().length\r\n const trimmed = line.trim()\r\n const kv = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n\r\n if (indent === 0) {\r\n inMetadata = false\r\n if (!kv) continue\r\n const value = kv[2].trim()\r\n if (kv[1] === \"name\") {\r\n name = value.replace(/^[\"']|[\"']$/g, \"\")\r\n } else if (kv[1] === \"metadata\") {\r\n if (value === \"\") {\r\n // Block-style metadata: following indented lines belong to it\r\n inMetadata = true\r\n metadataIndent = -1\r\n } else if (/visible\\s*:\\s*false/.test(value)) {\r\n // Inline/flow-style metadata: metadata: { visible: false }\r\n hidden = true\r\n }\r\n }\r\n } else if (inMetadata) {\r\n if (metadataIndent === -1) metadataIndent = indent\r\n if (indent < metadataIndent) {\r\n inMetadata = false\r\n continue\r\n }\r\n if (kv && kv[1] === \"visible\" && kv[2].trim() === \"false\") {\r\n hidden = true\r\n }\r\n }\r\n }\r\n\r\n return name ? { name, hidden } : null\r\n}\r\n\r\n/**\r\n * Scan the bundled skills/ directory and return A2H skill visibility info.\r\n *\r\n * Returns two lists:\r\n * - `hidden`: names of skills whose SKILL.md frontmatter declares metadata.visible: false\r\n * - `all`: names of ALL A2H skill directories found (used to clean up stale hidden entries)\r\n *\r\n * Visibility lives with each skill definition (best practice): adding a new\r\n * hidden skill only needs a frontmatter edit, with no code change here. The\r\n * returned names are pushed into cfg.skills.hidden, which DevEco Code's\r\n * available() already filters on - while get()/require() still resolve\r\n * them so other skills can invoke them by name.\r\n */\r\nexport function discoverA2HSkills(skillsRoot: string): { hidden: string[]; all: string[] } {\r\n const hidden: string[] = []\r\n const all: string[] = []\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(skillsRoot, { withFileTypes: true })\r\n } catch {\r\n return { hidden, all }\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isDirectory()) continue\r\n const skillFile = path.join(skillsRoot, entry.name, \"SKILL.md\")\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(skillFile, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const info = parseFrontmatter(raw)\r\n if (info) {\r\n all.push(info.name)\r\n if (info.hidden) hidden.push(info.name)\r\n }\r\n }\r\n return { hidden, all }\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const info = parseFrontmatter(raw)\r\n if (!info) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(info.name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\r\nimport type { A2HHostDeps } from \"./types\"\r\n\r\n/**\r\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\r\n * hook (so every shell session sees these).\r\n * Env vars are the single source of truth in the plugin context.\r\n *\r\n * This base set contains NO secrets \u2014 only DevEco/SDK paths. The multimodal\r\n * model apiKey is intentionally NOT included: the `shell.env` hook uses this\r\n * base set so the key never leaks into every LLM shell command (and thus the\r\n * transcript).\r\n */\r\nexport async function buildA2HEnvBase(\r\n deps: A2HHostDeps,\r\n): Promise<Record<string, string>> {\r\n const env: Record<string, string> = {}\r\n\r\n const home = await deps.resolveDevEcoHome()\r\n if (home) {\r\n const sdk = deps.resolveSdkPath(home)\r\n env[\"DEVECO_HOME\"] = home\r\n env[\"DEVECO_SDK_HOME\"] = sdk\r\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\r\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\r\n }\r\n\r\n return env\r\n}\r\n"],
|
|
5
|
-
"mappings": ";AAAA,OAAOA,WAAU;;;ACAjB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAM9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAGlC,IAAM,WAAW,KAAK,QAAQ,WAAW,IAAI;AAG7C,IAAM,YAAY;;;ACdzB,OAAOC,WAAU;AACjB,OAAO,QAAQ;AAOR,SAAS,iBAAiB,SAAyB;AACxD,QAAM,QAAQ,QAAQ,MAAM,+BAA+B;AAC3D,SAAO,QAAQ,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI;AACzD;AAWO,SAAS,iBAAiB,
|
|
4
|
+
"sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n\r\n // Register the bundled skills/ directory as an additional search path.\r\n // All bundled skills are user-visible \u2014 there is no partial-visibility\r\n // (hidden) layer anymore, so we only register the search path here.\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio paths as env vars \u2500\u2500\r\n // NOTE: deliberately uses the *base* env \u2014 the multimodal model apiKey\r\n // is NOT injected into LLM shell sessions (would leak into every command\r\n // and the transcript).\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * the top-level `name` (a scalar). This avoids pulling a YAML dependency\r\n * into the plugin.\r\n *\r\n * Returns the parsed `name`, or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): string | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n if (line.length - line.trimStart().length !== 0) continue // skip indented (e.g. metadata block) lines\r\n\r\n const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n if (!kv) continue\r\n if (kv[1] === \"name\") {\r\n return kv[2].trim().replace(/^[\"']|[\"']$/g, \"\")\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const name = parseFrontmatter(raw)\r\n if (!name) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\r\nimport type { A2HHostDeps } from \"./types\"\r\n\r\n/**\r\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\r\n * hook (so every shell session sees these).\r\n * Env vars are the single source of truth in the plugin context.\r\n *\r\n * This base set contains NO secrets \u2014 only DevEco/SDK paths. The multimodal\r\n * model apiKey is intentionally NOT included: the `shell.env` hook uses this\r\n * base set so the key never leaks into every LLM shell command (and thus the\r\n * transcript).\r\n */\r\nexport async function buildA2HEnvBase(\r\n deps: A2HHostDeps,\r\n): Promise<Record<string, string>> {\r\n const env: Record<string, string> = {}\r\n\r\n const home = await deps.resolveDevEcoHome()\r\n if (home) {\r\n const sdk = deps.resolveSdkPath(home)\r\n env[\"DEVECO_HOME\"] = home\r\n env[\"DEVECO_SDK_HOME\"] = sdk\r\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\r\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\r\n }\r\n\r\n return env\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["path", "path", "path", "path"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "android2harmony",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A2H (Android-to-HarmonyOS) Plugin for DevEco Code — standalone plugin containing A2H skills, agents, and tools for converting Android apps to HarmonyOS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,13 +9,13 @@ Batch-convert a set of Android page snapshots (each snapshot is a `page_NNNN_Act
|
|
|
9
9
|
|
|
10
10
|
## Step 0 — Environment Variables Check (run first)
|
|
11
11
|
|
|
12
|
-
The final build (Step 6, via `hmos_fix_build_errors`) resolves the DevEco location
|
|
12
|
+
The final build (Step 6, via `hmos_fix_build_errors`) resolves the DevEco location from the **OS environment variable** — then asks the user if unset (read env vars with `echo "$VAR"` on macOS/Linux; `$env:VAR` in PowerShell on Windows):
|
|
13
13
|
|
|
14
|
-
| Variable |
|
|
15
|
-
|
|
16
|
-
| `
|
|
14
|
+
| Variable | Needed for | Valid when |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `DEVECO_SDK_HOME` | Step 6 unified build | resolves to a valid DevEco install (derive the install root as its parent dir) |
|
|
17
17
|
|
|
18
|
-
If
|
|
18
|
+
If the env var does not yield a valid DevEco install, **ask the user** for the DevEco Studio install path before proceeding (suggest setting `DEVECO_SDK_HOME` as a machine environment variable).
|
|
19
19
|
|
|
20
20
|
## Step 1 — Parse User Request
|
|
21
21
|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
在状态管理V1中,推荐使用[$$](../$$语法:系统组件双向同步/arkts-two-way-sync.md)实现系统组件的双向绑定。
|
|
2
|
+
|
|
3
|
+
在状态管理V2中,推荐使用!!语法糖统一处理双向绑定。
|
|
4
|
+
|
|
5
|
+
说明
|
|
6
|
+
|
|
7
|
+
!!语法从API version 12开始支持。
|
|
8
|
+
|
|
9
|
+
## 概述
|
|
10
|
+
|
|
11
|
+
!!双向绑定语法,是一个语法糖方便开发者实现数据双向绑定,用于初始化子组件的[@Param](../../状态管理(V2)/管理组件拥有的状态/@Param:组件外部输入/arkts-new-param.md)和[@Event](../../状态管理(V2)/管理组件拥有的状态/@Event装饰器:规范组件输出/arkts-new-event.md)。其中@Event方法名需要声明为“$”+ @Param属性名,详见[使用场景](arkts-new-binding.md#使用场景)。
|
|
12
|
+
|
|
13
|
+
* 如果使用了!!双向绑定语法,表明父组件的变化会同步给子组件,子组件的变化也会同步给父组件。
|
|
14
|
+
* 父组件未使用!!时,变化是单向的。
|
|
15
|
+
|
|
16
|
+
## 使用场景
|
|
17
|
+
|
|
18
|
+
### 自定义组件间双向绑定
|
|
19
|
+
|
|
20
|
+
1. 在Index中构造Star子组件,双向绑定父子组件中的value属性,并初始化子组件的@Param value和@Event $value。
|
|
21
|
+
|
|
22
|
+
@Param与@Event装饰器配合使用的双向绑定语法糖。
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
1. Child({ value: this.value, $value: (val: number) => { this.value = val; } })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
[Binding\_Star\_Param\_Event.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/ArkUI_Binding/entry/src/main/ets/pages/Binding_Star_Param_Event.ets#L29-L31)
|
|
29
|
+
|
|
30
|
+
上述语法可以简化为!!双向绑定语法糖。
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
1. Star({ value: this.value!! })
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
[Binding\_Star.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/ArkUI_Binding/entry/src/main/ets/pages/Binding_Star.ets#L29-L31)
|
|
37
|
+
2. 使用@Param value与@Event $value语法实现自定义组件双向绑定。
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
1. @Entry
|
|
41
|
+
2. @ComponentV2
|
|
42
|
+
3. struct Parent {
|
|
43
|
+
4. @Local value: number = 0;
|
|
44
|
+
|
|
45
|
+
6. build() {
|
|
46
|
+
7. Column() {
|
|
47
|
+
8. Text(`${this.value}`)
|
|
48
|
+
9. // 点击Index中的Button改变value值,父组件Parent和子组件Child中的Text将同步更新。
|
|
49
|
+
10. Button(`change value in parent component`).onClick(() => {
|
|
50
|
+
11. this.value++;
|
|
51
|
+
12. })
|
|
52
|
+
13. // 使用@Param与@Event语法实现自定义组件双向绑定。
|
|
53
|
+
14. Child({ value: this.value, $value: (val: number) => { this.value = val; } })
|
|
54
|
+
15. // ...
|
|
55
|
+
16. // ···
|
|
56
|
+
17. }
|
|
57
|
+
18. }
|
|
58
|
+
19. }
|
|
59
|
+
|
|
60
|
+
21. @ComponentV2
|
|
61
|
+
22. struct Child {
|
|
62
|
+
23. @Param value: number = 0;
|
|
63
|
+
24. @Event $value: (val: number) => void = (val: number) => {};
|
|
64
|
+
|
|
65
|
+
26. build() {
|
|
66
|
+
27. Column() {
|
|
67
|
+
28. Text(`${this.value}`)
|
|
68
|
+
29. // 点击子组件Child中的Button,调用`this.$value(10)`方法,父组件Parent和子组件Child中的Text将同步更新。
|
|
69
|
+
30. Button(`change value in child component`).onClick(() => {
|
|
70
|
+
31. this.$value(10);
|
|
71
|
+
32. })
|
|
72
|
+
33. }
|
|
73
|
+
34. }
|
|
74
|
+
35. }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
[Binding\_Star\_Param\_Event.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/ArkUI_Binding/entry/src/main/ets/pages/Binding_Star_Param_Event.ets#L15-L55)
|
|
78
|
+
3. 使用!!语法糖实现自定义组件双向绑定。
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
1. @Entry
|
|
82
|
+
2. @ComponentV2
|
|
83
|
+
3. struct Index {
|
|
84
|
+
4. @Local value: number = 0;
|
|
85
|
+
|
|
86
|
+
6. build() {
|
|
87
|
+
7. Column() {
|
|
88
|
+
8. Text(`${this.value}`)
|
|
89
|
+
9. // 点击Index中的Button改变value值,父组件Index和子组件Star中的Text将同步更新。
|
|
90
|
+
10. Button(`change value in parent component`).onClick(() => {
|
|
91
|
+
11. this.value++;
|
|
92
|
+
12. })
|
|
93
|
+
13. // 使用!!语法糖实现自定义组件双向绑定。
|
|
94
|
+
14. Star({ value: this.value!! })
|
|
95
|
+
15. // ...
|
|
96
|
+
16. }
|
|
97
|
+
17. }
|
|
98
|
+
18. }
|
|
99
|
+
|
|
100
|
+
20. @ComponentV2
|
|
101
|
+
21. struct Star {
|
|
102
|
+
22. @Param value: number = 0;
|
|
103
|
+
23. @Event $value: (val: number) => void = (val: number) => {};
|
|
104
|
+
|
|
105
|
+
25. build() {
|
|
106
|
+
26. Column() {
|
|
107
|
+
27. Text(`${this.value}`)
|
|
108
|
+
28. // 点击子组件Star中的Button,调用`this.$value(10)`方法,父组件Index和子组件Star中的Text将同步更新。
|
|
109
|
+
29. Button(`change value in child component`).onClick(() => {
|
|
110
|
+
30. this.$value(10);
|
|
111
|
+
31. })
|
|
112
|
+
32. }
|
|
113
|
+
33. }
|
|
114
|
+
34. }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
[Binding\_Star.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/ArkUI_Binding/entry/src/main/ets/pages/Binding_Star.ets#L15-L54)
|
|
118
|
+
|
|
119
|
+
**使用限制**
|
|
120
|
+
|
|
121
|
+
* !!双向绑定语法不支持多层父子组件传递。
|
|
122
|
+
* 不支持与@Event混用。从API version 18开始,当使用!!双向绑定语法给子组件传递参数时,给对应的@Event方法传参会编译报错。
|
|
123
|
+
* 当使用3个或更多感叹号(!!!、!!!!、!!!!!等)时,不支持双向绑定功能。
|
|
124
|
+
|
|
125
|
+
### 系统组件参数双向绑定
|
|
126
|
+
|
|
127
|
+
!!运算符为系统组件提供TS变量的引用,使得TS变量和系统组件的内部状态保持同步。添加方式是在变量名后添加,例如isShow!!。
|
|
128
|
+
|
|
129
|
+
内部状态的含义由组件决定。例如:[bindMenu](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/弹窗控制/菜单控制/ts-universal-attributes-menu.md#bindmenu11)组件的isShow参数。
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
1. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
133
|
+
|
|
134
|
+
3. const TAG: string = 'click show Menu';
|
|
135
|
+
4. const DOMAIN = 0xFF00;
|
|
136
|
+
|
|
137
|
+
6. @Entry
|
|
138
|
+
7. @ComponentV2
|
|
139
|
+
8. struct BindMenuInterface {
|
|
140
|
+
9. @Local isShow: boolean = false;
|
|
141
|
+
|
|
142
|
+
11. build() {
|
|
143
|
+
12. Column() {
|
|
144
|
+
13. Row() {
|
|
145
|
+
14. Text('click show Menu')
|
|
146
|
+
15. .bindMenu(this.isShow!!, // 双向绑定。
|
|
147
|
+
16. [
|
|
148
|
+
17. {
|
|
149
|
+
18. value: 'Menu1',
|
|
150
|
+
19. action: () => {
|
|
151
|
+
20. hilog.info(DOMAIN, TAG, 'handle Menu1 click');
|
|
152
|
+
21. }
|
|
153
|
+
22. },
|
|
154
|
+
23. {
|
|
155
|
+
24. value: 'Menu2',
|
|
156
|
+
25. action: () => {
|
|
157
|
+
26. hilog.info(DOMAIN, TAG, 'handle Menu2 click');
|
|
158
|
+
27. }
|
|
159
|
+
28. },
|
|
160
|
+
29. ])
|
|
161
|
+
30. }.height('50%')
|
|
162
|
+
|
|
163
|
+
32. Text('isShow: ' + this.isShow).fontSize(18).fontColor(Color.Red)
|
|
164
|
+
33. Row() {
|
|
165
|
+
34. Button('Click')
|
|
166
|
+
35. .onClick(() => {
|
|
167
|
+
36. this.isShow = true;
|
|
168
|
+
37. })
|
|
169
|
+
38. .width(100)
|
|
170
|
+
39. .fontSize(20)
|
|
171
|
+
40. .margin(10)
|
|
172
|
+
41. }
|
|
173
|
+
42. }.width('100%')
|
|
174
|
+
43. }
|
|
175
|
+
44. }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
[Sys\_Binding.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/ArkUI_Binding/entry/src/main/ets/pages/Sys_Binding.ets#L15-L59)
|
|
179
|
+
|
|
180
|
+

|
|
181
|
+
|
|
182
|
+
**使用规则**
|
|
183
|
+
|
|
184
|
+
* 当前!!双向绑定支持基础类型变量,当该变量使用[@State](../../状态管理(V1)/管理组件拥有的状态/@State装饰器:组件内状态/arkts-state.md)等状态管理V1装饰器装饰,或者[@Local](../../状态管理(V2)/管理组件拥有的状态/@Local装饰器:组件内部状态/arkts-new-local.md)等状态管理V2装饰器装饰时,变量值的变化会触发UI刷新。
|
|
185
|
+
|
|
186
|
+
| 属性 | 支持的参数 | 起始API版本 |
|
|
187
|
+
| --- | --- | --- |
|
|
188
|
+
| [bindMenu](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/弹窗控制/菜单控制/ts-universal-attributes-menu.md#bindmenu11) | isShow | 18 |
|
|
189
|
+
| [bindContextMenu](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/弹窗控制/菜单控制/ts-universal-attributes-menu.md#bindcontextmenu12) | isShown | 18 |
|
|
190
|
+
| [bindPopup](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/弹窗控制/Popup控制/ts-universal-attributes-popup.md#bindpopup) | show | 18 |
|
|
191
|
+
| [TextInput](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/文本与输入/TextInput/ts-basic-components-textinput.md#textinputoptions对象说明) | text | 18 |
|
|
192
|
+
| [TextArea](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/文本与输入/TextArea/ts-basic-components-textarea.md#textareaoptions对象说明) | text | 18 |
|
|
193
|
+
| [Search](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/文本与输入/Search/ts-basic-components-search.md#searchoptions18对象说明) | value | 18 |
|
|
194
|
+
| [BindSheet](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/模态转场设置/半模态转场/ts-universal-attributes-sheet-transition.md#bindsheet) | isShow | 18 |
|
|
195
|
+
| [BindContentCover](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/通用属性/模态转场设置/全屏模态转场/ts-universal-attributes-modal-transition.md#bindcontentcover) | isShow | 18 |
|
|
196
|
+
| [SideBarContainer](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/栅格与分栏/SideBarContainer/ts-container-sidebarcontainer.md#sidebarwidth) | sideBarWidth | 18 |
|
|
197
|
+
| [Navigation](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/导航与切换/Navigation/ts-basic-components-navigation.md#navbarwidth9) | navBarWidth | 18 |
|
|
198
|
+
| [Toggle](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Toggle/ts-basic-components-toggle.md#toggleoptions18对象说明) | isOn | 18 |
|
|
199
|
+
| [Checkbox](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Checkbox/ts-basic-components-checkbox.md#select) | select | 18 |
|
|
200
|
+
| [CheckboxGroup](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/CheckboxGroup/ts-basic-components-checkboxgroup.md#selectall) | selectAll | 18 |
|
|
201
|
+
| [Radio](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Radio/ts-basic-components-radio.md#checked) | checked | 18 |
|
|
202
|
+
| [Rating](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Rating/ts-basic-components-rating.md#ratingoptions18对象说明) | rating | 18 |
|
|
203
|
+
| [Slider](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Slider/ts-basic-components-slider.md#slideroptions对象说明) | value | 18 |
|
|
204
|
+
| [Select](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Select/ts-basic-components-select.md#selected) | selected | 18 |
|
|
205
|
+
| [Select](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/按钮与选择/Select/ts-basic-components-select.md#value) | value | 18 |
|
|
206
|
+
| [MenuItem](../../../../../../../harmonyos-references/ArkUI(方舟UI框架)/ArkTS组件/菜单/MenuItem/ts-basic-components-menuitem.md#selected) | selected | 18 |
|