@manohub/app-kit 0.2.0 → 0.2.3
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/CONTRACT.md +174 -9
- package/README.md +9 -5
- package/bin/appkit.mjs +121 -0
- package/lint/__tests__/guardrails.spec.mjs +154 -0
- package/lint/component-audit.mjs +1 -1
- package/lint/guardrails.config.schema.json +42 -0
- package/lint/pre-commit.sample +38 -26
- package/lint/run-all.mjs +63 -59
- package/lint/shared.mjs +462 -347
- package/lint/structure-audit.mjs +1 -1
- package/lint/style-audit.mjs +1 -1
- package/package.json +7 -3
- package/skills/README.md +32 -10
- package/skills/app-kit/SKILL.md +19 -11
- package/skills/app-kit/references/adoption.md +76 -6
- package/skills/app-kit/references/contract-index.md +7 -2
- package/skills/app-kit-dev/SKILL.md +40 -8
- package/skills/app-kit-dev/references/page-recipes.md +53 -5
- package/skills/app-kit-dev/references/style-rules.md +2 -2
- package/skills/app-kit-migrate/SKILL.md +30 -11
- package/skills/app-kit-migrate/references/migration-playbook.md +53 -4
- package/skills/install.mjs +298 -203
package/lint/pre-commit.sample
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
# app-kit 护栏 pre-commit
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# app-kit 护栏 pre-commit 钩子样本:只检查**已改动文件**(含新增但未提交的文件),目标 < 3 秒
|
|
3
|
+
# (否则人会在本机把它关掉)。
|
|
4
|
+
#
|
|
5
|
+
# 启用(不需要改 git 配置,默认 hooks 目录即可):
|
|
6
|
+
# cp node_modules/@manohub/app-kit/lint/pre-commit.sample .git/hooks/pre-commit
|
|
7
|
+
# chmod +x .git/hooks/pre-commit
|
|
8
|
+
#
|
|
9
|
+
# 应用包不是仓库根时(monorepo,如 apps/sub-app):**必须**用环境变量指出包目录,
|
|
10
|
+
# 因为钩子的 cwd 恒为仓库根,而本包通常只装在应用包自己的 node_modules 下:
|
|
11
|
+
# APPKIT_APP_DIR=apps/sub-app (推荐在钩子文件里写死一行,或提交前 export)
|
|
12
|
+
#
|
|
13
|
+
# 说明:护栏以「应用包根」为 cwd 读取 appkit-guardrails.config.json;找不到脚本或配置时
|
|
14
|
+
# 会明确告知并**跳过**本次检查(不阻塞提交)—— 静默通过的假绿比报错更危险。
|
|
15
|
+
set -e
|
|
16
|
+
|
|
17
|
+
# 应用包目录(缺省仓库根)
|
|
18
|
+
APP_DIR="${APPKIT_APP_DIR:-.}"
|
|
19
|
+
|
|
20
|
+
# 护栏脚本按「应用包自己的 node_modules → 仓库根 node_modules」依次找
|
|
21
|
+
if [ -f "$APP_DIR/node_modules/@manohub/app-kit/lint/run-all.mjs" ]; then
|
|
22
|
+
AUDIT="$APP_DIR/node_modules/@manohub/app-kit/lint/run-all.mjs"
|
|
23
|
+
elif [ -f "node_modules/@manohub/app-kit/lint/run-all.mjs" ]; then
|
|
24
|
+
AUDIT="node_modules/@manohub/app-kit/lint/run-all.mjs"
|
|
25
|
+
else
|
|
26
|
+
echo "[guardrails] 未找到护栏脚本(已查 $APP_DIR/node_modules 与 node_modules)。"
|
|
27
|
+
echo "[guardrails] monorepo 请在钩子里设置 APPKIT_APP_DIR=<应用包目录>(如 apps/sub-app),"
|
|
28
|
+
echo "[guardrails] 否则本次提交不检查(不阻塞,但请尽快修好这条路径)。"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if [ ! -f "$APP_DIR/appkit-guardrails.config.json" ]; then
|
|
33
|
+
echo "[guardrails] $APP_DIR 下缺少 appkit-guardrails.config.json,无法确定被审计的应用。"
|
|
34
|
+
echo "[guardrails] 配置片段见 node_modules/@manohub/app-kit/CONTRACT.md §8。"
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
exec node "$AUDIT" --changed --cwd="$APP_DIR"
|
package/lint/run-all.mjs
CHANGED
|
@@ -1,59 +1,63 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* run-all —— 三条护栏的统一入口(接入方只挂这一条,见 CONTRACT.md §8)。
|
|
4
|
-
*
|
|
5
|
-
* 并发跑 style / component / structure,汇总输出与退出码;支持 `--changed`(只跑改动文件,供 pre-commit)。
|
|
6
|
-
* 任一脚本报 error 即整体失败;`--json` 时输出聚合结果。
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* run-all —— 三条护栏的统一入口(接入方只挂这一条,见 CONTRACT.md §8)。
|
|
4
|
+
*
|
|
5
|
+
* 并发跑 style / component / structure,汇总输出与退出码;支持 `--changed`(只跑改动文件,供 pre-commit)。
|
|
6
|
+
* 任一脚本报 error 即整体失败;`--json` 时输出聚合结果。
|
|
7
|
+
*
|
|
8
|
+
* 用法(经包的命令入口;接入方的 `package.json` 只挂前两条,其余是排查时才用的):
|
|
9
|
+
* pnpm exec appkit lint # 全量(挂成 "lint")
|
|
10
|
+
* pnpm exec appkit lint --changed # 只跑改动文件(挂成 "lint:changed")
|
|
11
|
+
* pnpm exec appkit lint --strict # 收口验收:存量挂起与规则豁免一律失效
|
|
12
|
+
* pnpm exec appkit lint:style # 单条排查:style / component / structure
|
|
13
|
+
* pnpm exec appkit lint --json
|
|
14
|
+
*
|
|
15
|
+
* 直接跑本脚本(`node node_modules/@manohub/app-kit/lint/run-all.mjs`)与上面完全等价 ——
|
|
16
|
+
* CLI 只是薄壳,已有钩子/脚本不必改。
|
|
17
|
+
*/
|
|
18
|
+
import { spawn } from 'node:child_process'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
|
+
import { parseArgs } from './shared.mjs'
|
|
22
|
+
|
|
23
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
24
|
+
const AUDITS = ['style-audit.mjs', 'component-audit.mjs', 'structure-audit.mjs']
|
|
25
|
+
|
|
26
|
+
const args = parseArgs()
|
|
27
|
+
const passthrough = process.argv.slice(2).filter((a) => a.startsWith('--app=') || a.startsWith('--cwd='))
|
|
28
|
+
if (args.changed) passthrough.push('--changed')
|
|
29
|
+
if (args.json) passthrough.push('--json')
|
|
30
|
+
|
|
31
|
+
const runOne = (script) =>
|
|
32
|
+
new Promise((resolve) => {
|
|
33
|
+
const child = spawn(process.execPath, [join(HERE, script), ...passthrough], {
|
|
34
|
+
cwd: args.cwd,
|
|
35
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
36
|
+
})
|
|
37
|
+
let stdout = ''
|
|
38
|
+
let stderr = ''
|
|
39
|
+
child.stdout.on('data', (d) => (stdout += d))
|
|
40
|
+
child.stderr.on('data', (d) => (stderr += d))
|
|
41
|
+
child.on('close', (code) => resolve({ script, code, stdout, stderr }))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const started = Date.now()
|
|
45
|
+
const results = await Promise.all(AUDITS.map(runOne))
|
|
46
|
+
const elapsed = ((Date.now() - started) / 1000).toFixed(2)
|
|
47
|
+
|
|
48
|
+
if (args.json) {
|
|
49
|
+
process.stdout.write(JSON.stringify({ audits: results.map((r) => ({ script: r.script, code: r.code })), elapsed }, null, 2) + '\n')
|
|
50
|
+
} else {
|
|
51
|
+
for (const r of results) {
|
|
52
|
+
if (r.stdout) process.stdout.write(r.stdout)
|
|
53
|
+
if (r.stderr) process.stderr.write(r.stderr)
|
|
54
|
+
}
|
|
55
|
+
const failed = results.filter((r) => r.code !== 0)
|
|
56
|
+
process.stdout.write(
|
|
57
|
+
failed.length === 0
|
|
58
|
+
? `✓ 护栏(三条):全绿(${elapsed}s)\n`
|
|
59
|
+
: `✗ 护栏(三条):${failed.map((f) => f.script).join(' / ')} 未通过(${elapsed}s)\n`,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
process.exit(results.some((r) => r.code !== 0) ? 1 : 0)
|