create-dwy 0.14.7 → 0.15.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/CHANGELOG.md +9 -0
- package/bin/index.js +4 -0
- package/package.json +1 -1
- package/src/skills-install.js +113 -0
- package/src/sync-all.js +19 -0
- package/templates/ai-tools/CLAUDE.md +1 -1
- package/templates/ai-tools/hooks/Git/pre-git-commit-sensitive-check.sh +10 -2
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-strict.md +221 -0
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +193 -0
- package/templates/ai-tools/rules/iOS/dwy-ios-pitfalls.md +159 -0
- package/templates/ai-tools/rules/iOS/dwy-swift-core.md +35 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-acceptance/SKILL.md +23 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +28 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-competitor/SKILL.md +22 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-ideate/SKILL.md +21 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-mvp/SKILL.md +23 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-poc/SKILL.md +24 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prd/SKILL.md +22 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/SKILL.md +68 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +103 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +23 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-release/SKILL.md +26 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-design/SKILL.md +23 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-dev/SKILL.md +22 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-launch/SKILL.md +25 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-prd/SKILL.md +22 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-stage-ship/SKILL.md +21 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tasks/SKILL.md +22 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-tdd-dev/SKILL.md +25 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-validate/SKILL.md +28 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-version/SKILL.md +23 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +365 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/electron-release-workflow-template.yml +98 -0
- package/templates/ai-tools/skills//{350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel → 345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish}/references/release-workflow-template.yml +3 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +31 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +31 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +23 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +37 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +23 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +27 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +25 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +32 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/monitor-notify.md +36 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/publish-config.md +65 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/version-sync.md +30 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/scripts/release_config.sh +115 -0
- package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/SKILL.md +31 -21
- package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/references/python-commercial-guide.md +1 -1
- package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/references/sensitive-patterns.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/init_config.sh +91 -0
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-whiteboard-prototype/SKILL.md +70 -0
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-whiteboard-prototype/assets/template.html +353 -0
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-sdk-spec/scripts/init_config.sh +0 -97
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-publish/SKILL.md +0 -50
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-publish/references/application-release.md +0 -106
- package/templates/ai-tools/skills//345/274/200/345/217/221/346/265/201/347/250/213/dwy-publish/references/sdk-release.md +0 -93
- package/templates/ai-tools/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel/SKILL.md +0 -356
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/assets/files-whitelist-snippet.json" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/assets/js-commercial.npmignore" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/assets/python-commercial-MANIFEST.in" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/assets/python-commercial.gitignore" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/references/js-commercial-guide.md" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/references/publish-blacklist.md" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/scripts/check_comments.py" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/scripts/check_js_publish.sh" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/scripts/check_python_publish.sh" +0 -0
- /package/templates/ai-tools/skills//345/{256/211/345/205/250 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-sdk-spec/scripts/detect_sdk.sh" +0 -0
- /package/templates/ai-tools/skills//345/{274/200/345/217/221/346/265/201/347/250/213 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-semver/SKILL.md" +0 -0
- /package/templates/ai-tools/skills//345/{274/200/345/217/221/346/265/201/347/250/213 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-semver/references/semver-spec.md" +0 -0
- /package/templates/ai-tools/skills//345/{274/200/345/217/221/346/265/201/347/250/213 → 217/221/345/270/203/345/217/221/347/211/210}/dwy-semver/scripts/bump.py" +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# create-dwy
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 新增「产品 0→1」三层 skill 体系(总控 `dwy-product-launcher` + 5 个可独立触发的阶段 skill + 13 个产出物级原子 skill):从模糊想法渐进式推进到上线产品,立项阶段三道闸门(需求市场验证为总闸门 → 技术 POC → MVP≤7),廉价验证在前、昂贵投入在后。
|
|
8
|
+
- 新增 `dwy skills install` 命令:把 pm-skills / superpowers 的外部 skill 安装到全局 `~/.dwy/skills/`(一台机一份、全项目共享),含完整 `scripts/` 与 LICENSE;再次运行即覆盖更新。`dwy claude sync` 选中相关 skill 时自动自检安装。
|
|
9
|
+
- 外部 skill 进全局纯数据目录而非 `.claude/skills/`,避免被工具当 skill 加载、与 `dwy-*` 触发冲突。
|
|
10
|
+
- 改造 `dwy-whiteboard-prototype`:配色按产品定位推荐(保留 `:root` 语义变量结构)、尺寸按目标端推荐、图标不再用 emoji。
|
|
11
|
+
|
|
3
12
|
## 0.14.7
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/bin/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync } from 'fs'
|
|
|
4
4
|
import { fileURLToPath } from 'url'
|
|
5
5
|
import { dirname, resolve } from 'path'
|
|
6
6
|
import { syncAll } from '../src/sync-all.js'
|
|
7
|
+
import { installSkills } from '../src/skills-install.js'
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
9
10
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'))
|
|
@@ -16,6 +17,7 @@ function printHelp() {
|
|
|
16
17
|
'Usage:',
|
|
17
18
|
' dwy 运行交互式同步',
|
|
18
19
|
' dwy sync 运行交互式同步',
|
|
20
|
+
' dwy skills install 安装/更新全局外部 skill 到 ~/.dwy/skills/',
|
|
19
21
|
' dwy --help 显示本说明',
|
|
20
22
|
' dwy -h 显示本说明',
|
|
21
23
|
' dwy --version 显示当前版本',
|
|
@@ -29,6 +31,8 @@ if (argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
|
29
31
|
printHelp()
|
|
30
32
|
} else if (argv.includes('--version') || argv.includes('-V')) {
|
|
31
33
|
console.log(pkg.version)
|
|
34
|
+
} else if (argv[0] === 'skills' && argv[1] === 'install') {
|
|
35
|
+
await installSkills()
|
|
32
36
|
} else if (argv.length === 0 || argv[0] === 'sync') {
|
|
33
37
|
await syncAll()
|
|
34
38
|
} else {
|
package/package.json
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import fs from 'fs-extra'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import { getDwyHomeDir, runGit, chalk } from './utils.js'
|
|
5
|
+
|
|
6
|
+
function logAction(label) {
|
|
7
|
+
console.log(chalk.green(` ✓ ${label}`))
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 产品 0→1 流程依赖的外部 skill 清单。
|
|
11
|
+
// 每项:缓存目录名(= 原子 skill 引用的 ~/.dwy/skills/<name>)→ 来源 repo / stable tag / repo 内目录。
|
|
12
|
+
// 只用正式 release tag,不用 main HEAD(定版可复现)。更新版本时改这里的 tag。
|
|
13
|
+
const SKILL_SOURCES = {
|
|
14
|
+
'pm-skills': {
|
|
15
|
+
repo: 'https://github.com/phuryn/pm-skills.git',
|
|
16
|
+
tag: 'v2.0.0',
|
|
17
|
+
licenseFile: 'LICENSE',
|
|
18
|
+
licenseDest: 'LICENSE-pm-skills',
|
|
19
|
+
skills: {
|
|
20
|
+
'competitor-analysis': 'pm-market-research/skills/competitor-analysis',
|
|
21
|
+
'market-sizing': 'pm-market-research/skills/market-sizing',
|
|
22
|
+
'sentiment-analysis': 'pm-market-research/skills/sentiment-analysis',
|
|
23
|
+
'interview-script': 'pm-product-discovery/skills/interview-script',
|
|
24
|
+
'prioritize-features': 'pm-product-discovery/skills/prioritize-features',
|
|
25
|
+
'opportunity-solution-tree': 'pm-product-discovery/skills/opportunity-solution-tree',
|
|
26
|
+
'metrics-dashboard': 'pm-product-discovery/skills/metrics-dashboard',
|
|
27
|
+
'create-prd': 'pm-execution/skills/create-prd',
|
|
28
|
+
'outcome-roadmap': 'pm-execution/skills/outcome-roadmap',
|
|
29
|
+
'test-scenarios': 'pm-execution/skills/test-scenarios',
|
|
30
|
+
'release-notes': 'pm-execution/skills/release-notes',
|
|
31
|
+
'gtm-strategy': 'pm-go-to-market/skills/gtm-strategy',
|
|
32
|
+
'intended-vs-implemented': 'pm-ai-shipping/skills/intended-vs-implemented',
|
|
33
|
+
'north-star-metric': 'pm-marketing-growth/skills/north-star-metric',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
superpowers: {
|
|
37
|
+
repo: 'https://github.com/obra/superpowers.git',
|
|
38
|
+
tag: 'v6.0.3',
|
|
39
|
+
licenseFile: 'LICENSE',
|
|
40
|
+
licenseDest: 'LICENSE-superpowers',
|
|
41
|
+
skills: {
|
|
42
|
+
brainstorming: 'skills/brainstorming',
|
|
43
|
+
'writing-plans': 'skills/writing-plans',
|
|
44
|
+
'test-driven-development': 'skills/test-driven-development',
|
|
45
|
+
'systematic-debugging': 'skills/systematic-debugging',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 全局外部 skill 安装目录:~/.dwy/skills/
|
|
51
|
+
function getGlobalSkillsDir() {
|
|
52
|
+
return path.join(getDwyHomeDir(), 'skills')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 浅克隆指定 tag 到临时目录(--depth 1 + --branch <tag> 只取定版,省带宽)。
|
|
56
|
+
async function shallowCloneTag(repoUrl, tag, destDir) {
|
|
57
|
+
await runGit(['clone', '--depth', '1', '--branch', tag, repoUrl, destDir])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 安装/更新全部外部 skill 到 ~/.dwy/skills/。再次运行即覆盖式更新。
|
|
61
|
+
export async function installSkills() {
|
|
62
|
+
const targetDir = getGlobalSkillsDir()
|
|
63
|
+
await fs.ensureDir(targetDir)
|
|
64
|
+
|
|
65
|
+
const tmpRoot = path.join(os.tmpdir(), `dwy-skills-${process.pid}`)
|
|
66
|
+
await fs.ensureDir(tmpRoot)
|
|
67
|
+
|
|
68
|
+
const versions = {}
|
|
69
|
+
try {
|
|
70
|
+
for (const [sourceName, src] of Object.entries(SKILL_SOURCES)) {
|
|
71
|
+
console.log(chalk.gray(`拉取 ${sourceName} @ ${src.tag} ...`))
|
|
72
|
+
const repoDir = path.join(tmpRoot, sourceName)
|
|
73
|
+
await shallowCloneTag(src.repo, src.tag, repoDir)
|
|
74
|
+
|
|
75
|
+
// 搬 LICENSE(MIT 要求保留原版权声明)
|
|
76
|
+
const licenseSrc = path.join(repoDir, src.licenseFile)
|
|
77
|
+
if (await fs.pathExists(licenseSrc)) {
|
|
78
|
+
await fs.copy(licenseSrc, path.join(targetDir, src.licenseDest), { overwrite: true })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 搬每个 skill 整目录(含 scripts/、配套 .md)
|
|
82
|
+
for (const [skillName, repoPath] of Object.entries(src.skills)) {
|
|
83
|
+
const from = path.join(repoDir, repoPath)
|
|
84
|
+
const to = path.join(targetDir, skillName)
|
|
85
|
+
if (!await fs.pathExists(from)) {
|
|
86
|
+
throw new Error(`${sourceName} @ ${src.tag} 内未找到 ${repoPath}(仓库结构可能变了)`)
|
|
87
|
+
}
|
|
88
|
+
await fs.remove(to)
|
|
89
|
+
await fs.copy(from, to, { overwrite: true })
|
|
90
|
+
logAction(`skills/${skillName}`)
|
|
91
|
+
versions[skillName] = { repo: src.repo, tag: src.tag }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 写版本清单,供查看与后续更新比对
|
|
96
|
+
await fs.writeJson(path.join(targetDir, 'VERSIONS.json'), versions, { spaces: 2 })
|
|
97
|
+
console.log(chalk.green(`\n✓ 外部 skill 已安装到 ${targetDir}`))
|
|
98
|
+
} finally {
|
|
99
|
+
await fs.remove(tmpRoot)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 全局 skill 是否已就绪(供 sync 自检:缺则自动装)。
|
|
104
|
+
export async function skillsInstalled() {
|
|
105
|
+
return fs.pathExists(path.join(getGlobalSkillsDir(), 'VERSIONS.json'))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function ensureSkillsInstalled() {
|
|
109
|
+
if (await skillsInstalled()) return false
|
|
110
|
+
console.log(chalk.gray('未检测到全局外部 skill,自动安装...'))
|
|
111
|
+
await installSkills()
|
|
112
|
+
return true
|
|
113
|
+
}
|
package/src/sync-all.js
CHANGED
|
@@ -24,6 +24,14 @@ import {
|
|
|
24
24
|
} from './sync-codex.js'
|
|
25
25
|
import { scanExistingCursorRules, syncCursor } from './sync-cursor.js'
|
|
26
26
|
import { syncOpenCode } from './sync-opencode.js'
|
|
27
|
+
import { ensureSkillsInstalled } from './skills-install.js'
|
|
28
|
+
|
|
29
|
+
// 产品 0→1 流程里依赖全局外部 skill 的包装型原子 skill。
|
|
30
|
+
// 同步选中其中任一个时,触发全局外部 skill 自检安装。
|
|
31
|
+
const WRAPPER_SKILL_NAMES = new Set([
|
|
32
|
+
'dwy-ideate', 'dwy-competitor', 'dwy-validate', 'dwy-mvp', 'dwy-prd',
|
|
33
|
+
'dwy-version', 'dwy-tasks', 'dwy-tdd-dev', 'dwy-acceptance', 'dwy-release',
|
|
34
|
+
])
|
|
27
35
|
|
|
28
36
|
const PLATFORM_OPTIONS = [
|
|
29
37
|
{ value: 'claude', label: 'Claude Code', hint: '支持 rules / skills / commands / hooks' },
|
|
@@ -690,6 +698,17 @@ export async function syncAll({
|
|
|
690
698
|
}
|
|
691
699
|
await saveSyncState(projectDir, syncState)
|
|
692
700
|
|
|
701
|
+
// 产品 0→1 流程的包装型原子 skill 依赖全局外部 skill(~/.dwy/skills/)。
|
|
702
|
+
// 若本次同步选中了这些 skill 且全局未安装,自动装一次(缺则装、有则跳过)。
|
|
703
|
+
if (selected.skills?.some((s) => WRAPPER_SKILL_NAMES.has(s.name))) {
|
|
704
|
+
try {
|
|
705
|
+
await ensureSkillsInstalled()
|
|
706
|
+
} catch (error) {
|
|
707
|
+
console.log(chalk.yellow(`\n外部 skill 自动安装失败:${error.message}`))
|
|
708
|
+
console.log(chalk.gray('可稍后手动运行 `dwy skills install` 重试。'))
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
693
712
|
if (!syncedAnySupportedPlatform) {
|
|
694
713
|
console.log(chalk.yellow('未选择已接入平台,本次未执行同步。'))
|
|
695
714
|
}
|
|
@@ -37,8 +37,16 @@ PATTERNS=(
|
|
|
37
37
|
# 拼接为正则
|
|
38
38
|
REGEX=$(IFS='|'; echo "${PATTERNS[*]}")
|
|
39
39
|
|
|
40
|
-
#
|
|
41
|
-
|
|
40
|
+
# 只扫「新增行」(+ 开头,排除 +++ 文件头),删除的代码不可能是正在引入的凭证。
|
|
41
|
+
# 跳过文档/规则类文件(.md/.mdc/规则/hook 本身)——它们写的是「如何检测凭证」的模式说明,非真凭证。
|
|
42
|
+
SCAN_FILES=$(printf '%s\n' "$STAGED" | grep -viE '\.(md|mdc|txt)$|rules/|/hooks/|sensitive-check')
|
|
43
|
+
RESULT=""
|
|
44
|
+
for f in $SCAN_FILES; do
|
|
45
|
+
HIT=$(git diff --cached -U0 -- "$f" 2>/dev/null \
|
|
46
|
+
| grep -E '^\+' | grep -vE '^\+\+\+' \
|
|
47
|
+
| grep -inE "$REGEX" 2>/dev/null)
|
|
48
|
+
[ -n "$HIT" ] && RESULT="${RESULT}${f}:\n${HIT}\n"
|
|
49
|
+
done
|
|
42
50
|
|
|
43
51
|
if [ -n "$RESULT" ]; then
|
|
44
52
|
echo "========================================"
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: HarmonyOS ArkTS 严格性避坑(相对 TS/JS 的禁用语法,编译期 arkts-no-* 报错)
|
|
3
|
+
paths:
|
|
4
|
+
- "**/*.ets"
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# ArkTS 严格性避坑
|
|
9
|
+
|
|
10
|
+
ArkTS 是 TypeScript 的**受限子集**,禁掉大量 TS/JS 动态写法以换取性能与静态可分析性。
|
|
11
|
+
从 TS/JS(尤其 iOS/前端转鸿蒙)照搬代码必触发 `arkts-no-*` 编译错误。
|
|
12
|
+
**写 `.ets` 前先过一遍本清单**,别用下面列出的禁用写法。每条配真实报错码 + 错误/正确写法。
|
|
13
|
+
|
|
14
|
+
## 1. 禁止解构声明与解构赋值(arkts-no-destruct-decls / arkts-no-destruct-assignment)
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// 错:解构声明
|
|
18
|
+
let [oldR, r] = [a % m, m];
|
|
19
|
+
// 错:解构赋值
|
|
20
|
+
[oldR, r] = [r, oldR - q * r];
|
|
21
|
+
const { value, next } = readVarint(bytes, i);
|
|
22
|
+
|
|
23
|
+
// 对:拆成单变量 + 临时变量交换
|
|
24
|
+
let oldR = a % m;
|
|
25
|
+
let r = m;
|
|
26
|
+
const tmpR = oldR - q * r;
|
|
27
|
+
oldR = r;
|
|
28
|
+
r = tmpR;
|
|
29
|
+
const res = readVarint(bytes, i);
|
|
30
|
+
const value = res.value;
|
|
31
|
+
const next = res.next;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 2. 对象字面量必须对应显式 class/interface(arkts-no-untyped-obj-literals / arkts-no-obj-literals-as-types)
|
|
35
|
+
|
|
36
|
+
ArkTS 不允许「匿名对象字面量当类型」,也不允许「无类型标注的对象字面量」。
|
|
37
|
+
**两种触发场景都要先声明 interface**:
|
|
38
|
+
|
|
39
|
+
### 2.1 函数返回类型不能写匿名对象
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// 错:返回类型是匿名对象字面量
|
|
43
|
+
private static readVarint(bytes: Uint8Array, i: number): { value: number; next: number } { ... }
|
|
44
|
+
|
|
45
|
+
// 对:先声明 interface
|
|
46
|
+
interface VarintResult {
|
|
47
|
+
value: number;
|
|
48
|
+
next: number;
|
|
49
|
+
}
|
|
50
|
+
private static readVarint(bytes: Uint8Array, i: number): VarintResult { ... }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2.2 返回 / 赋值的对象字面量必须有类型来源
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// 错:裸对象字面量(编译器无法对应到具体类型)
|
|
57
|
+
return { command: c, arg0: a, arg1: b, dataLength: d };
|
|
58
|
+
const out = { header, payload: payload.slice() };
|
|
59
|
+
|
|
60
|
+
// 对:显式标注类型(interface 已声明)
|
|
61
|
+
const header: AdbHeader = { command: c, arg0: a, arg1: b, dataLength: d };
|
|
62
|
+
return header;
|
|
63
|
+
const out: AdbMessageResult = { header, payload: payload.slice() };
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2.3 给 SDK API 传对象参数也要带类型
|
|
67
|
+
|
|
68
|
+
SDK(@ohos.net.socket / cryptoFramework 等)的参数即使是已声明类型,裸字面量仍报错,
|
|
69
|
+
必须用「声明类型的变量」传入:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// 错:裸字面量传参
|
|
73
|
+
await tcp.connect({ address: { address: host, port: 5555, family: 1 }, timeout: 15000 });
|
|
74
|
+
await tcp.send({ data: buf });
|
|
75
|
+
const out = await signer.sign({ data: token });
|
|
76
|
+
|
|
77
|
+
// 对:拆成带 SDK 类型标注的变量
|
|
78
|
+
const netAddr: socket.NetAddress = { address: host, port: 5555, family: 1 };
|
|
79
|
+
const opts: socket.TCPConnectOptions = { address: netAddr, timeout: 15000 };
|
|
80
|
+
await tcp.connect(opts);
|
|
81
|
+
|
|
82
|
+
const sendOpts: socket.TCPSendOptions = { data: buf };
|
|
83
|
+
await tcp.send(sendOpts);
|
|
84
|
+
|
|
85
|
+
const input: cryptoFramework.DataBlob = { data: token };
|
|
86
|
+
const out = await signer.sign(input);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 3. 禁止用下标访问 string 取字符(arkts-no-props-by-index)
|
|
90
|
+
|
|
91
|
+
string 不能 `s[i]` 取字符(被判为「字段索引访问」)。用 `charAt`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// 错
|
|
95
|
+
const table = 'ABCD...';
|
|
96
|
+
out += table[(n >> 18) & 63];
|
|
97
|
+
|
|
98
|
+
// 对
|
|
99
|
+
out += table.charAt((n >> 18) & 63);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> 注意:`Uint8Array[i]` / `Array[i]` 下标访问是允许的,只有 **string 下标取字符** 被禁。
|
|
103
|
+
|
|
104
|
+
## 4. 禁止 String() / Number() / Boolean() 当转换函数调用
|
|
105
|
+
|
|
106
|
+
ArkTS 禁止把内置构造器当函数转换值。catch 错误转文案、数字转字符串都要换写法:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// 错
|
|
110
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
111
|
+
const s = String(num);
|
|
112
|
+
|
|
113
|
+
// 对:catch 变量在 ArkTS 里就是 Error(含 BusinessError extends Error),直接取 message
|
|
114
|
+
function errMsg(e: Error): string {
|
|
115
|
+
return e.message;
|
|
116
|
+
}
|
|
117
|
+
// 数字转字符串用 .toString() 或模板串
|
|
118
|
+
const s = num.toString();
|
|
119
|
+
const s2 = `${num}`;
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 5. Promise executor / 回调形参类型:简单形参标,executor 的 resolve/reject 不标
|
|
123
|
+
|
|
124
|
+
箭头函数当回调,形参不能省类型;executor 里别把函数直接当回调透传:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// 错:resolve 无类型 + 直接把 resolve 透传给 setTimeout
|
|
128
|
+
return new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
129
|
+
|
|
130
|
+
// 对:形参标类型 + 包一层箭头
|
|
131
|
+
return new Promise<void>((resolve: () => void) => {
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
resolve();
|
|
134
|
+
}, ms);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**⚠️ 例外(实测踩坑):给 `Promise` executor 的 resolve/reject 标注「函数类型」反而触发
|
|
139
|
+
`arkts-no-structural-typing`**。executor 的两个形参类型由 `Promise<T>` 自身约束,显式标
|
|
140
|
+
`(reject: (e: Error) => void)` 会与 SDK 内置的 reject 签名做结构比对而报错。这种情况**去掉
|
|
141
|
+
executor 形参的类型注解,让 ArkTS 推断**(reject 内部传 `Error` 子类实例完全正常):
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
// 错:executor 形参标函数类型 → arkts-no-structural-typing
|
|
145
|
+
return new Promise<Uint8Array>(
|
|
146
|
+
(resolve: (v: Uint8Array) => void, reject: (e: Error) => void) => { ... });
|
|
147
|
+
|
|
148
|
+
// 对:executor 形参不标,推断
|
|
149
|
+
return new Promise<Uint8Array>((resolve, reject) => {
|
|
150
|
+
reject(new MyError(...)); // MyError extends Error,正常
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
判据:**普通回调(如自己定义的 `onError: (e: Error) => void`)形参要标;`new Promise(...)`
|
|
155
|
+
的 executor 形参不标**。另:跨文件传回调时,形参类型用基类(`Error`)而非具体子类
|
|
156
|
+
(`MyGrpcError`),避免「子类型 lambda 赋给基类形参」触发同一规则。
|
|
157
|
+
|
|
158
|
+
## 6. 页面路由路径不带 `ets/` 前缀
|
|
159
|
+
|
|
160
|
+
`main_pages.json` 的 `src` 与 `windowStage.loadContent(...)` 的路径都相对 `src/main/ets/` 根,
|
|
161
|
+
**不要带 `ets/` 前缀**,否则编译报 `Page '.../ets/ets/xxx' does not exist`:
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
// 错:resources/base/profile/main_pages.json
|
|
165
|
+
{ "src": ["ets/spike/SpikePage"] }
|
|
166
|
+
// 对
|
|
167
|
+
{ "src": ["spike/SpikePage"] }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// 错
|
|
172
|
+
windowStage.loadContent('ets/spike/SpikePage', cb);
|
|
173
|
+
// 对
|
|
174
|
+
windowStage.loadContent('spike/SpikePage', cb);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## 7. 大整数运算用原生 bigint(无需第三方库)
|
|
178
|
+
|
|
179
|
+
ArkTS 原生支持 ES11 `bigint`(`10n` / `BigInt(x)` / `<< >> % & | ** /`)。
|
|
180
|
+
但无 `.inverse(m)` / `.power(_,modulus:)` / `.serialize()` 这类方法(那是 Swift BigInt 库的),
|
|
181
|
+
模逆/模幂/序列化要**手写**:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// 模幂(快速幂)
|
|
185
|
+
function modPow(base: bigint, exp: bigint, m: bigint): bigint {
|
|
186
|
+
let result = 1n;
|
|
187
|
+
let b = base % m;
|
|
188
|
+
let e = exp;
|
|
189
|
+
while (e > 0n) {
|
|
190
|
+
if ((e & 1n) === 1n) { result = (result * b) % m; }
|
|
191
|
+
e >>= 1n;
|
|
192
|
+
b = (b * b) % m;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
// 模逆(扩展欧几里得,见本仓鸿蒙工程 AndroidPubkey.ets)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## 8. 自检清单(写完 .ets 提交前 grep 一遍)
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
grep -rn 'let \[\|const \[' . # 解构声明
|
|
203
|
+
grep -rn '): {' . # 匿名对象当返回类型
|
|
204
|
+
grep -rn 'return {' . # 裸对象字面量返回(确认有类型标注)
|
|
205
|
+
grep -rn 'String(\|Number(\|Boolean(' . # 构造器当转换函数(排除 String.fromCharCode)
|
|
206
|
+
grep -rn 'new Promise<.*>((.*:.*=>' . # executor 形参标了函数类型(见 §5 例外,应去注解)
|
|
207
|
+
# string 下标取字符需人工核对(table[i] 形式)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## 9. 仍需真机/真实 SDK 确认的(编译期过不代表运行对)
|
|
211
|
+
|
|
212
|
+
ArkTS 编译通过只保证语法合规;以下 SDK 行为编译期无法验证,需真机/模拟器实测:
|
|
213
|
+
- cryptoFramework 的 `createSign(规格串)` 是否支持目标算法组合(如 `RSA2048|PKCS1|NoHash`)
|
|
214
|
+
- `getEncoded()` 返回的公钥 DER 是 PKCS#1 还是 X.509 SPKI(影响解析)
|
|
215
|
+
- `@ohos.net.socket.TCPSocket` 局域网连通性(需 `ohos.permission.INTERNET`,normal 级安装即授予)
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
> 本文件只管**语言语法层**。ArkUI 组件、V2 状态管理、图标资源、工程构建、运行时容错的坑
|
|
220
|
+
> 见同目录 **`dwy-arkts-ui-build.md`**(图标名映射 / 自定义组件链属性 / @BuilderParam 默认值 /
|
|
221
|
+
> @Param 外部初始化 / async 状态快照 / bootstrap 分步容错 / DevEco 命令行构建)。
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: HarmonyOS ArkUI 组件层 / V2 状态管理 / 图标资源 / 工程构建 / 运行时容错避坑(与 dwy-arkts-strict.md 语言层配套)
|
|
3
|
+
paths:
|
|
4
|
+
- "**/*.ets"
|
|
5
|
+
- "**/build-profile.json5"
|
|
6
|
+
- "**/main_pages.json"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ArkUI / 工程 / 运行时 避坑
|
|
10
|
+
|
|
11
|
+
`dwy-arkts-strict.md` 管语言语法层;本文件管 **ArkUI 组件、V2 状态管理、资源(图标)、工程构建、运行时容错**。
|
|
12
|
+
均为真机/DevEco 构建实测踩坑(编译期或运行期暴露,命令行 tsc 查不出)。
|
|
13
|
+
|
|
14
|
+
## 1. sys.symbol 图标名 ≠ SF Symbols 名,必须查鸿蒙清单逐个映射
|
|
15
|
+
|
|
16
|
+
iOS 用 SF Symbols(`tv.and.mediabox`),鸿蒙 `$r('sys.symbol.xxx')` 是**另一套命名体系**,
|
|
17
|
+
大量 SF 名在鸿蒙不存在,写错报 `Unknown resource name 'xxx'`。
|
|
18
|
+
|
|
19
|
+
- **权威清单**:`<DevEco-SDK>/openharmony/toolchains/id_defined.json`(约 4000+ 个,`type=symbol`),
|
|
20
|
+
python 提取后 grep 验证目标名是否存在。
|
|
21
|
+
- **`$r('sys.symbol.x')` 的 x 必须是字面量**,不能用变量动态拼 → 只能逐处改,不能写映射函数。
|
|
22
|
+
- **常见映射**(SF → 鸿蒙):
|
|
23
|
+
|
|
24
|
+
| SF Symbols | 鸿蒙 sys.symbol |
|
|
25
|
+
|---|---|
|
|
26
|
+
| tv / tv.and.mediabox | `TV_tv` |
|
|
27
|
+
| iphone / ipad | `phone` / `pad` |
|
|
28
|
+
| command | `keyboard` |
|
|
29
|
+
| cpu / memorychip | `memory_module` |
|
|
30
|
+
| terminal | `code` |
|
|
31
|
+
| pencil | `doc_plaintext_and_pencil` |
|
|
32
|
+
| globe | `globe_and_keyboard` |
|
|
33
|
+
| tag | `label` |
|
|
34
|
+
| square.and.arrow.up | `share` |
|
|
35
|
+
| checkmark.seal.fill | `checkmark_circle_fill` |
|
|
36
|
+
| stop.fill | `stop_circle_fill` |
|
|
37
|
+
| shippingbox | `card_package` |
|
|
38
|
+
| square.stack.3d.up | `AI_square_stack` |
|
|
39
|
+
| gearshape.fill / house / chevron.left | 同名(`gearshape_fill` / `house` / `chevron_left`,少数直通) |
|
|
40
|
+
|
|
41
|
+
写前先 `grep -ix '<候选名>' id_defined.txt` 确认存在,别想当然。
|
|
42
|
+
|
|
43
|
+
## 2. 自定义组件后不能直接链通用属性(margin/layoutWeight/width…)
|
|
44
|
+
|
|
45
|
+
自定义 `@Component`/`@ComponentV2`(尤其带尾随 `@BuilderParam` 闭包的)**后面直接链通用属性
|
|
46
|
+
会编译失败**:`Declaration or statement expected` + `Cannot find name 'margin'`。
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// 错:HoldRepeatButton 是自定义组件,后链 .margin 解析失败
|
|
50
|
+
HoldRepeatButton({ action: f }) { this.icon() }
|
|
51
|
+
.margin({ bottom: 184 })
|
|
52
|
+
|
|
53
|
+
// 对:外层包系统组件(Column/Row/Stack)再链
|
|
54
|
+
Column() {
|
|
55
|
+
HoldRepeatButton({ action: f }) { this.icon() }
|
|
56
|
+
}.margin({ bottom: 184 })
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
需要偏移/尺寸时,要么外层包系统容器,要么给自定义组件加 `@Param` 参数在其内部应用。
|
|
60
|
+
|
|
61
|
+
## 3. 支持本地初始化的组件属性必须有默认值(mandatory-default-value-for-local-initialization)
|
|
62
|
+
|
|
63
|
+
DevEco **预览器/linter(PreviewBuild)比 assembleHap 更严**,会查这条;assembleHap 不查。
|
|
64
|
+
|
|
65
|
+
- **`@BuilderParam`** 必须给默认值(默认值是一个全局 `@Builder` 函数):
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
@Builder function emptyContent(): void {}
|
|
69
|
+
@ComponentV2
|
|
70
|
+
struct Foo {
|
|
71
|
+
@BuilderParam content: () => void = emptyContent; // 不能写 `: () => void;`
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- **`@CustomDialog` 的 `controller`** 必须可选(否则同报无默认值):
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
@CustomDialog
|
|
79
|
+
struct MyDialog {
|
|
80
|
+
controller?: CustomDialogController; // 不能写 `controller: CustomDialogController;`
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
改可选后调用处要用可选链:`this.controller?.close()`(否则 `Object is possibly undefined`)。
|
|
84
|
+
|
|
85
|
+
- **例外**:`@Require @Param x: T;` 不受此规则约束(`@Require` 表必传,无需默认值)。
|
|
86
|
+
|
|
87
|
+
## 4. @ComponentV2 外部传入的回调属性必须用 @Param
|
|
88
|
+
|
|
89
|
+
父组件给子组件传属性(含回调)时,子组件该属性必须 `@Param` 装饰,否则报
|
|
90
|
+
`The 'regular' property 'xxx' cannot be initialized here (forbidden to specify)`:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// 错:onDone 是普通属性,父层 Foo({ onDone: ... }) 传不进
|
|
94
|
+
struct Foo { onDone: () => void = () => {}; }
|
|
95
|
+
// 对
|
|
96
|
+
struct Foo { @Param onDone: () => void = () => {}; }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 5. async 状态供 UI 同步读:用 @Trace 快照,别在 build 里 await
|
|
100
|
+
|
|
101
|
+
`build()` 是同步的,UI 不能 `await` 一个 async 方法拿状态(`Property 'kind' does not exist on
|
|
102
|
+
type 'Promise<T>'`)。把异步数据在装配/事件时算好,存进 `@Trace` 快照,UI 同步读快照:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
@ObservedV2 class Store {
|
|
106
|
+
@Trace snapshot: State = defaultState; // UI 读这个
|
|
107
|
+
private cache = 0;
|
|
108
|
+
async start(ctx): Promise<void> {
|
|
109
|
+
this.cache = await this.loadFromAsset(ctx); // 异步读一次缓存
|
|
110
|
+
this.refresh(); // 用缓存同步算快照
|
|
111
|
+
}
|
|
112
|
+
private refresh(): void { this.snapshot = compute(this.cache, Date.now()); }
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 6. 跨模块 import 必须从「声明源」取,不能经中转模块隐式重导出
|
|
117
|
+
|
|
118
|
+
ArkTS 不支持 TS 的「A 从 B import 某类型,C 再从 A import 同名」隐式重导出:
|
|
119
|
+
`Module '"../X"' declares 'Y' locally, but it is not exported`。**直接从声明 Y 的那个文件 import**。
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// 错:AdbTransport 只是 import 了 AdbLog(没 re-export),别处从 AdbTransport 取 AdbLog
|
|
123
|
+
import { AdbLog } from '../adb/AdbTransport';
|
|
124
|
+
// 对:从声明 AdbLog 的源文件取
|
|
125
|
+
import { AdbLog } from '../adb/AdbConnection';
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## 7. 工程/资源杂项
|
|
129
|
+
|
|
130
|
+
- **`Stack` 居中用 `.alignContent(Alignment.X)`**,不是构造参数 `Stack({ alignment: X })`
|
|
131
|
+
(`'alignment' does not exist in type 'StackOptions'`)。
|
|
132
|
+
- **`SymbolGlyph` 没有 `.textAlign`**:居中靠外层容器的 `justifyContent`/`alignItems`,别链 textAlign。
|
|
133
|
+
- **`build-profile.json5`(entry 模块)顶层字段固定**:apiType/targets/buildOption/buildOptionSet/
|
|
134
|
+
buildModeBinder/entryModules/apiType 等。塞 `testOption` 之类非法字段报
|
|
135
|
+
`Schema validate failed`。hypium 测试靠 `src/ohosTest/` 目录约定,不需在 build-profile 配。
|
|
136
|
+
- **退役/不编译的代码移出 `entry/src/main/ets`**(如 spike):放在 ets 下仍会被编译。
|
|
137
|
+
移到工程根的 `_archive/` 之类,并从 `main_pages.json` 去掉。
|
|
138
|
+
|
|
139
|
+
## 8. 运行时:全局状态装配(bootstrap)必须分步容错,不能全有或全无
|
|
140
|
+
|
|
141
|
+
app 入口的「装配全局状态」若把多个步骤串成一个 `await` 链,**任一步抛异常(尤其模拟器/无账号
|
|
142
|
+
环境下的 IAP、首次无持久化)会让后面全不执行**,导致全局 router/store 为 null,UI 一进就崩
|
|
143
|
+
(`AppContext 未装配` / `Object is possibly undefined`)。
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// 错:IAP 在模拟器抛错(如华为 IAP 错误码 27656204)→ router 没装配 → 全崩
|
|
147
|
+
async bootstrap(ctx) {
|
|
148
|
+
await store.load(ctx);
|
|
149
|
+
await iap.start(ctx); // ← 模拟器抛错,下面不执行
|
|
150
|
+
this.router = new Router(...); // ← 永远到不了
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 对:每步独立容错,核心对象(router)一定装配成功
|
|
154
|
+
async bootstrap(ctx) {
|
|
155
|
+
await this.safe('存档', () => store.load(ctx));
|
|
156
|
+
await this.safe('内购', () => iap.start(ctx)); // 失败只 log,降级
|
|
157
|
+
this.router = new Router(...); // 一定执行
|
|
158
|
+
}
|
|
159
|
+
private async safe(name, step) {
|
|
160
|
+
try { await step(); } catch (e) { console.error(`[${name}]降级: ${(e as Error).message}`); }
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
模块内部同理:**核心数据(如试用锚点)与非关键外部依赖(如 IAP 查询)分开 try**,
|
|
165
|
+
非关键失败不连累核心,给 UI 一个合理降级默认值。
|
|
166
|
+
|
|
167
|
+
## 9. DevEco 命令行构建(绕开 IDE,拿带 File:行:列 的错误)
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
NODE=/Applications/DevEco-Studio.app/Contents/tools/node/bin/node
|
|
171
|
+
HVIGOR=/Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw.js
|
|
172
|
+
export DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk # 停 daemon 后必设
|
|
173
|
+
|
|
174
|
+
# 配置校验
|
|
175
|
+
$NODE $HVIGOR --sync -p product=default --daemon
|
|
176
|
+
# 编译 + 打 HAP(验 ArkTS 编译;产物 entry/build/.../entry-default-unsigned.hap)
|
|
177
|
+
$NODE $HVIGOR assembleHap -p product=default --mode module -p module=entry@default --daemon \
|
|
178
|
+
2>&1 | sed 's/\x1b\[[0-9;]*m//g' | grep "Error Message:"
|
|
179
|
+
# 预览器构建(linter 更严,查 @BuilderParam/controller 默认值等;用独立 .preview 缓存)
|
|
180
|
+
$NODE $HVIGOR --mode module -p module=entry@default -p product=default -p pageType=page \
|
|
181
|
+
-p previewMode=true -p buildRoot=.preview PreviewBuild --daemon
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **必须 `--daemon`**:DevEco daemon 自动注入 SDK 路径;`--no-daemon` 报 `Invalid DEVECO_SDK_HOME`。
|
|
185
|
+
- **`Error Message:` 才是阻断错误**;`WARN ... may throw exceptions` 是建议,不阻断。
|
|
186
|
+
- **PreviewBuild 用独立 `.preview` 缓存**:改了源码仍报已修错误 → `rm -rf entry/.preview` 重跑。
|
|
187
|
+
- 两种构建都要过:assembleHap(真机包)+ PreviewBuild(linter 严,预览器)。
|
|
188
|
+
|
|
189
|
+
## 10. 仍需真机确认的(编译/预览过 ≠ 运行对)
|
|
190
|
+
|
|
191
|
+
见 `dwy-arkts-strict.md` 第 9 条(cryptoFramework 签名规格 / 公钥 DER 格式 / TCPSocket 局域网)。
|
|
192
|
+
补充:手撸 HTTP/2 的 HPACK 全字面量是否被对端(如 grpc-netty)接受、华为 IAP 需 AGC 后台
|
|
193
|
+
配商品 + 真机华为账号、asset/Preferences 在不同设备的可用性,均真机才能定论。
|