kld-sdd 2.7.8-2 → 2.7.8-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/README.md +2 -0
- package/USABILITY.md +52 -0
- package/lib/hook-install.js +75 -0
- package/lib/init.js +28 -148
- package/lib/managed-hook-hashes.json +24 -0
- package/package.json +2 -2
- package/skywalk-sdd/index.cjs +1 -1
- package/skywalk-sdd/kb-upload.cjs +21 -17
- package/skywalk-sdd/lib/test-execution.cjs +20 -0
- package/templates/git-hooks/commit-msg +17 -99
- package/templates/git-hooks/commit-msg-runner.cjs +44 -0
- package/templates/git-hooks/commit-msg-sdd-trailer.cjs +60 -69
- package/templates/git-hooks/consistency-check-core.cjs +61 -240
- package/templates/git-hooks/delivery-check.cjs +231 -0
- package/templates/git-hooks/delivery-snapshot.cjs +181 -0
- package/templates/git-hooks/hooks.config +8 -6
- package/templates/git-hooks/pre-commit-consistency-check.cjs +13 -31
- package/templates/git-hooks/pre-push-consistency-check.cjs +36 -83
- package/templates/openspec/proposal.md +1 -1
- package/templates/skills/kld-sdd/opsx-apply/SKILL.md +24 -22
- package/templates/skills/kld-sdd/opsx-consistency-check/SKILL.md +34 -112
- package/templates/skills/kld-sdd/opsx-consistency-check/scripts/scripts.cjs +1 -14
|
@@ -1,104 +1,22 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# KLD SDD
|
|
3
|
-
# 在下方 SCRIPTS 列表中声明要加载的 .cjs 脚本
|
|
4
|
-
# 删除某行 = 禁用该插件
|
|
2
|
+
# KLD SDD provenance hook v2
|
|
5
3
|
# marker: KLD SDD quality gate
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
hooks_dir=""
|
|
13
|
-
# 统一反斜杠为正斜杠,保证 Windows 盘符路径(P:\a\b → P:/a/b)能被 case 判定为绝对路径
|
|
14
|
-
normalize_path() {
|
|
15
|
-
printf '%s' "$1" | tr '\\' '/'
|
|
16
|
-
}
|
|
17
|
-
# 绝对路径(/ 开头或盘符 X: 开头)直接使用;相对路径基于 git 根拼接
|
|
18
|
-
resolve_spec_dir() {
|
|
19
|
-
raw=$(normalize_path "$1")
|
|
20
|
-
case "$raw" in
|
|
21
|
-
/*|[A-Za-z]:*) printf '%s' "$raw" ;;
|
|
22
|
-
*) printf '%s' "$git_root/$raw" ;;
|
|
23
|
-
esac
|
|
24
|
-
}
|
|
25
|
-
spec_path=$(git -C "$git_root" config --local sdd.specPath 2>/dev/null)
|
|
26
|
-
if [ -n "$spec_path" ]; then
|
|
27
|
-
hooks_dir="$(resolve_spec_dir "$spec_path")/skywalk-sdd/git-hooks"
|
|
28
|
-
elif [ -f "$git_root/.sdd-spec-root" ]; then
|
|
29
|
-
spec_rel=$(head -1 "$git_root/.sdd-spec-root" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
|
30
|
-
hooks_dir="$(resolve_spec_dir "$spec_rel")/skywalk-sdd/git-hooks"
|
|
4
|
+
# Ordinary commits save work; delivery-check verifies readiness at Apply completion.
|
|
5
|
+
git_root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
6
|
+
spec_path=$(git config --local sdd.specPath 2>/dev/null)
|
|
7
|
+
if [ -z "$spec_path" ] && [ -f "$git_root/.sdd-spec-root" ]; then
|
|
8
|
+
IFS= read -r spec_path < "$git_root/.sdd-spec-root"
|
|
31
9
|
fi
|
|
32
|
-
if [ -
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if [ -
|
|
40
|
-
|
|
41
|
-
while [ -n "$search_dir" ] && [ "$search_dir" != "/" ]; do
|
|
42
|
-
if [ -d "$search_dir/skywalk-sdd/git-hooks" ]; then
|
|
43
|
-
hooks_dir="$search_dir/skywalk-sdd/git-hooks"
|
|
44
|
-
break
|
|
45
|
-
fi
|
|
46
|
-
# Windows 盘符根(P:/)dirname 后不变,需显式收敛,避免死循环
|
|
47
|
-
parent_dir="$(dirname "$search_dir")"
|
|
48
|
-
[ "$parent_dir" = "$search_dir" ] && break
|
|
49
|
-
search_dir="$parent_dir"
|
|
50
|
-
done
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
if [ -z "$hooks_dir" ]; then
|
|
54
|
-
echo "[SDD] 未找到 spec 仓的 skywalk-sdd/git-hooks 目录,跳过 commit-msg"
|
|
55
|
-
exit 0
|
|
56
|
-
fi
|
|
57
|
-
|
|
58
|
-
# 从配置文件读取插件列表
|
|
59
|
-
config_file="$hooks_dir/hooks.config"
|
|
60
|
-
if [ ! -f "$config_file" ]; then
|
|
61
|
-
echo "[SDD] ⚠️ 配置文件不存在:hooks.config,使用默认插件"
|
|
62
|
-
SCRIPTS="commit-msg-sdd-trailer.cjs"
|
|
63
|
-
else
|
|
64
|
-
# 读取 [commit-msg] 段落的配置
|
|
65
|
-
in_section=0
|
|
66
|
-
SCRIPTS=""
|
|
67
|
-
CR=$(printf '\r')
|
|
68
|
-
while IFS= read -r line; do
|
|
69
|
-
line=${line%"$CR"}
|
|
70
|
-
case "$line" in
|
|
71
|
-
"[commit-msg]") in_section=1 ;;
|
|
72
|
-
"["*) in_section=0 ;;
|
|
73
|
-
"#"*|"") ;;
|
|
74
|
-
*)
|
|
75
|
-
if [ "$in_section" -eq 1 ]; then
|
|
76
|
-
SCRIPTS="$SCRIPTS $line"
|
|
77
|
-
fi
|
|
78
|
-
;;
|
|
79
|
-
esac
|
|
80
|
-
done < "$config_file"
|
|
81
|
-
fi
|
|
82
|
-
|
|
83
|
-
executed=0
|
|
84
|
-
failed=0
|
|
85
|
-
for script_name in $SCRIPTS; do
|
|
86
|
-
script="$hooks_dir/$script_name"
|
|
87
|
-
if [ ! -f "$script" ]; then
|
|
88
|
-
echo "[SDD] ❌ 已配置的插件不存在:$script_name"
|
|
89
|
-
failed=$((failed + 1))
|
|
90
|
-
continue
|
|
91
|
-
fi
|
|
92
|
-
executed=$((executed + 1))
|
|
93
|
-
echo "[SDD] 执行 commit-msg 插件:$script_name"
|
|
94
|
-
node "$script" "$1" --project="$git_root" || failed=$((failed + 1))
|
|
95
|
-
done
|
|
96
|
-
|
|
97
|
-
if [ "$failed" -gt 0 ]; then
|
|
98
|
-
echo "[SDD] ❌ 共执行 $executed 个 commit-msg 插件,$failed 个失败"
|
|
99
|
-
exit 1
|
|
100
|
-
elif [ "$executed" -eq 0 ]; then
|
|
101
|
-
echo "[SDD] 配置已禁用 commit-msg 插件"
|
|
10
|
+
if [ -z "$spec_path" ]; then spec_path="$git_root"; fi
|
|
11
|
+
spec_path=$(printf '%s' "$spec_path" | tr '\\' '/' | tr -d '\r')
|
|
12
|
+
case "$spec_path" in
|
|
13
|
+
/*|[A-Za-z]:*) ;;
|
|
14
|
+
*) spec_path="$git_root/$spec_path" ;;
|
|
15
|
+
esac
|
|
16
|
+
runner="$spec_path/skywalk-sdd/git-hooks/commit-msg-runner.cjs"
|
|
17
|
+
if [ -f "$runner" ]; then
|
|
18
|
+
node "$runner" "$1" --project="$git_root" || echo '[SDD] 关联待补充,本次提交继续' >&2
|
|
102
19
|
else
|
|
103
|
-
echo
|
|
20
|
+
echo '[SDD] Spec 运行时未就绪,关联待补充;可重装或修正 sdd.specPath,本次提交继续' >&2
|
|
104
21
|
fi
|
|
22
|
+
exit 0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
// Only the managed provenance worker is advisory. User hooks are never run here.
|
|
9
|
+
const original = process.argv[2];
|
|
10
|
+
let tempDir;
|
|
11
|
+
try {
|
|
12
|
+
const config = path.join(__dirname, 'hooks.config');
|
|
13
|
+
if (fs.existsSync(config)) {
|
|
14
|
+
const text = fs.readFileSync(config, 'utf8');
|
|
15
|
+
const section = text.split(/\[commit-msg\]\s*\r?\n/)[1]?.split(/^\[/m)[0] || '';
|
|
16
|
+
if (!section.split(/\r?\n/).some(line => line.trim() === 'commit-msg-sdd-trailer.cjs')) process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
if (!String(process.env.KLD_SDD_CHANGE || '').trim()
|
|
19
|
+
&& !fs.existsSync(path.join(__dirname, '../state/delivery-associations'))) {
|
|
20
|
+
// No explicit or recorded association exists. Avoid starting a second Node
|
|
21
|
+
// process and loading ontology just to discover that there is nothing to add.
|
|
22
|
+
const message = fs.readFileSync(original, 'utf8').replace(/^Spec-(?:Change|Revision):[^\n]*\n?/gim, '');
|
|
23
|
+
fs.writeFileSync(original, message);
|
|
24
|
+
console.error('[SDD] 本次提交的 Change 待关联;保存不受影响');
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'sdd-msg-'));
|
|
28
|
+
const copy = path.join(tempDir, 'message');
|
|
29
|
+
fs.copyFileSync(original, copy);
|
|
30
|
+
const result = spawnSync(process.execPath, [path.join(__dirname, 'commit-msg-sdd-trailer.cjs'), copy, ...process.argv.slice(3)], {
|
|
31
|
+
encoding: 'utf8', timeout: 1500, maxBuffer: 256 * 1024,
|
|
32
|
+
});
|
|
33
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
34
|
+
if (result.status === 0 && !result.error) fs.copyFileSync(copy, original);
|
|
35
|
+
else {
|
|
36
|
+
const message = fs.readFileSync(original, 'utf8').replace(/^Spec-(?:Change|Revision):[^\n]*\n?/gim, '');
|
|
37
|
+
fs.writeFileSync(original, message);
|
|
38
|
+
console.error('[SDD] 关联待补充(超时或运行失败),本次提交继续');
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(`[SDD] 关联待补充:${error.message};本次提交继续`);
|
|
42
|
+
} finally {
|
|
43
|
+
if (tempDir) fs.rmSync(tempDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
@@ -1,19 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - 多仓(代码仓有 .sdd.yaml,spec 是另一个 clone):
|
|
8
|
-
* 写 Spec-Revision(spec 干净 HEAD 的完整 SHA)+ Spec-Change(活动 change,可多条)。
|
|
9
|
-
* - 单仓(openspec 与代码同仓):
|
|
10
|
-
* spec 与代码在同一次 commit 里,指针自指无意义 → 只写 Spec-Change,不写 Spec-Revision,
|
|
11
|
-
* 且不做「spec 工作区干净」检查(提交进行中必然是脏的)。
|
|
12
|
-
*
|
|
13
|
-
* Spec-Change 来源:KLD_SDD_CHANGE(显式覆盖)> sdd.config.yaml active_changes。
|
|
14
|
-
* Not from branch name, ever.
|
|
15
|
-
*
|
|
16
|
-
* Authority: 10-最小化多仓库关联与变更命名方案.md
|
|
4
|
+
/** Local provenance only. Explicit Change or exact reviewed index match; failures are advisory.
|
|
5
|
+
* Spec-Revision is written only for a clean, matching, separate Spec repository.
|
|
17
6
|
*/
|
|
18
7
|
|
|
19
8
|
const fs = require('fs');
|
|
@@ -30,7 +19,7 @@ function resolveOntologyDir() {
|
|
|
30
19
|
const ontologyDir = resolveOntologyDir();
|
|
31
20
|
const changeKey = require(path.join(ontologyDir, 'change-key.cjs'));
|
|
32
21
|
const sddConfig = require(path.join(ontologyDir, 'sdd-config.cjs'));
|
|
33
|
-
const
|
|
22
|
+
const snap = require('./delivery-snapshot.cjs');
|
|
34
23
|
const { parseFrontmatter } = require(path.join(ontologyDir, 'artifact-parser.cjs'));
|
|
35
24
|
|
|
36
25
|
function projectRootFromHook() {
|
|
@@ -40,8 +29,7 @@ function projectRootFromHook() {
|
|
|
40
29
|
}
|
|
41
30
|
|
|
42
31
|
function fail(message) {
|
|
43
|
-
|
|
44
|
-
process.exit(1);
|
|
32
|
+
throw new Error(message);
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
function isSddLinkedCodeRepo(codeRepoRoot) {
|
|
@@ -67,9 +55,6 @@ function resolveLayout(repoRoot, env = process.env) {
|
|
|
67
55
|
if (!context.specPath) {
|
|
68
56
|
fail(context.message || '无法解析 spec clone 路径。请执行 kld-sdd link-spec --path=<spec>');
|
|
69
57
|
}
|
|
70
|
-
if (context.code === sddConfig.CODES.SPEC_PATH_INVALID) {
|
|
71
|
-
fail(context.message);
|
|
72
|
-
}
|
|
73
58
|
// specPath 指回本仓根:等同单仓(兼容旧平铺);若指向仓内包裹包仍按 mono
|
|
74
59
|
const resolvedSpec = path.resolve(context.specPath);
|
|
75
60
|
const resolvedRoot = path.resolve(repoRoot);
|
|
@@ -79,6 +64,7 @@ function resolveLayout(repoRoot, env = process.env) {
|
|
|
79
64
|
if (resolvedSpec.startsWith(`${resolvedRoot}${path.sep}`)) {
|
|
80
65
|
return { mode: 'mono', specPath: resolvedSpec, context };
|
|
81
66
|
}
|
|
67
|
+
if (context.code === sddConfig.CODES.SPEC_PATH_INVALID) fail(context.message);
|
|
82
68
|
return { mode: 'multi', specPath: context.specPath, context };
|
|
83
69
|
}
|
|
84
70
|
|
|
@@ -101,24 +87,42 @@ function parseEnvChangeKeys(env = process.env) {
|
|
|
101
87
|
}
|
|
102
88
|
|
|
103
89
|
/**
|
|
104
|
-
* Spec-Change values: explicit env wins, otherwise
|
|
105
|
-
* registered in the spec repo's sdd.config.yaml.
|
|
90
|
+
* Spec-Change values: explicit env wins, otherwise an exact reviewed index match.
|
|
106
91
|
*/
|
|
107
|
-
function resolveChangeKeys(specRoot, env = process.env) {
|
|
92
|
+
function resolveChangeKeys(specRoot, env = process.env, repoRoot = projectRootFromHook()) {
|
|
108
93
|
const fromEnv = parseEnvChangeKeys(env);
|
|
109
94
|
if (fromEnv) {
|
|
110
|
-
for (const key of fromEnv) {
|
|
111
|
-
requireChangeDir(specRoot, key, { strict: true });
|
|
112
|
-
}
|
|
95
|
+
for (const key of fromEnv) requireChangeDir(specRoot, key, { strict: true });
|
|
113
96
|
return { keys: fromEnv, source: 'env' };
|
|
114
97
|
}
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
98
|
+
// The active registry describes ongoing work, not the ownership of this commit.
|
|
99
|
+
const identity = snap.repoIdentity(repoRoot);
|
|
100
|
+
const file = path.join(specRoot, 'skywalk-sdd/state/delivery-associations', `${snap.digest(identity)}.json`);
|
|
101
|
+
if (!fs.existsSync(file)) return { keys: [], source: 'pending' };
|
|
102
|
+
const saved = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
103
|
+
const staged = snap.git(repoRoot, ['diff', '--cached', '--name-only', '--no-renames', '-z']).split('\0').filter(Boolean);
|
|
104
|
+
if (!staged.length || staged.length > 200 || saved.repository !== identity) return { keys: [], source: 'pending' };
|
|
105
|
+
const knownFiles = new Set(Object.values(saved.changes || {}).flatMap(item => (item.coveredFiles || []).map(f => f.path)));
|
|
106
|
+
if (staged.some(file => !knownFiles.has(file))) return { keys: [], source: 'pending' };
|
|
107
|
+
const candidates = [];
|
|
108
|
+
const entries = snap.entriesAt(repoRoot, 'index').filter(e => staged.includes(e.path));
|
|
109
|
+
const blobs = snap.readBlobs(repoRoot, entries);
|
|
110
|
+
for (const [key, association] of Object.entries(saved.changes || {})) {
|
|
111
|
+
const covered = new Map((association.coveredFiles || []).map(f => [f.path, f]));
|
|
112
|
+
if (staged.every(file => {
|
|
113
|
+
const meta = covered.get(file);
|
|
114
|
+
if (!meta) return false;
|
|
115
|
+
if (meta.deleted) return !entries.some(e => e.path === file);
|
|
116
|
+
const content = blobs.get(file);
|
|
117
|
+
return content && snap.fileMeta(file, content, meta.mode).sha256 === meta.sha256 && entries.find(e => e.path === file)?.mode === meta.mode;
|
|
118
|
+
}) && requireChangeDir(specRoot, key, { strict: false })) candidates.push(key);
|
|
119
|
+
}
|
|
120
|
+
// Multiple matching reviews are ambiguous too; explicit selection supports multi-change commits.
|
|
121
|
+
return { keys: candidates.length === 1 ? candidates : [], source: candidates.length === 1 ? 'review' : 'pending' };
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
|
-
* @param {{ strict: boolean }} options strict=true
|
|
125
|
+
* @param {{ strict: boolean }} options strict=true 抛出关联错误;false 仅警告并跳过该条(外层均允许保存)
|
|
122
126
|
*/
|
|
123
127
|
function requireChangeDir(specRoot, key, options = { strict: true }) {
|
|
124
128
|
const proposalPath = path.join(specRoot, 'openspec', 'changes', key, 'proposal.md');
|
|
@@ -181,52 +185,39 @@ function resolveMsgFile(argv = process.argv) {
|
|
|
181
185
|
|
|
182
186
|
function main(env = process.env) {
|
|
183
187
|
const msgFile = resolveMsgFile();
|
|
184
|
-
if (!msgFile)
|
|
185
|
-
fail('缺少 commit message 文件参数');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
+
if (!msgFile) fail('缺少 commit message 文件参数');
|
|
188
189
|
const repoRoot = projectRootFromHook();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
fail('spec 工作区不干净。请先提交或暂存 spec 变更后再提交代码。');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const revision = sddConfig.gitHeadSha(layout.specPath);
|
|
213
|
-
if (!revision || revision.length < 40) {
|
|
214
|
-
fail(`无法读取完整 Spec-Revision(需要完整 SHA): ${revision}`);
|
|
190
|
+
// Keep only the index context for the code repo (git commit -a uses a temporary index).
|
|
191
|
+
for (const key of ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES']) delete process.env[key];
|
|
192
|
+
const original = fs.readFileSync(msgFile, 'utf8');
|
|
193
|
+
let trailers = { 'Spec-Change': [], 'Spec-Revision': [] };
|
|
194
|
+
try {
|
|
195
|
+
const layout = resolveLayout(repoRoot, env);
|
|
196
|
+
if (layout.mode === 'unlinked') return;
|
|
197
|
+
const association = resolveChangeKeys(layout.specPath, env, repoRoot);
|
|
198
|
+
trailers['Spec-Change'] = association.keys;
|
|
199
|
+
if (!association.keys.length) console.error('[SDD] 本次提交的 Change 待关联;未从活动列表猜测归属');
|
|
200
|
+
if (layout.mode === 'multi' && association.keys.length) {
|
|
201
|
+
if (layout.context.code === sddConfig.CODES.REMOTE_MISMATCH) fail(layout.context.message);
|
|
202
|
+
if (!layout.context.sddYaml?.ok || !layout.context.specRemote || !sddConfig.remotesMatch(layout.context.sddYaml.spec_repository, layout.context.specRemote)) fail('Spec 来源仓库尚未确认,不能写入精确 Revision');
|
|
203
|
+
const dirty = snap.git(layout.specPath, ['status', '--porcelain', '--untracked-files=normal']).trim();
|
|
204
|
+
if (dirty) console.error('[SDD] Spec 尚有未提交内容,Spec-Revision 待绑定;本次提交继续');
|
|
205
|
+
else {
|
|
206
|
+
const revision = snap.git(layout.specPath, ['rev-parse', '--verify', 'HEAD']).trim();
|
|
207
|
+
if (!/^[a-f0-9]{40}$|^[a-f0-9]{64}$/.test(revision)) fail('无法读取完整 Spec-Revision');
|
|
208
|
+
trailers['Spec-Revision'] = revision;
|
|
209
|
+
}
|
|
215
210
|
}
|
|
216
|
-
|
|
211
|
+
} catch (error) {
|
|
212
|
+
trailers = { 'Spec-Change': [], 'Spec-Revision': [] };
|
|
213
|
+
console.error(`[SDD] 关联待补充:${error.message};本次提交继续`);
|
|
217
214
|
}
|
|
218
|
-
|
|
219
|
-
const original = fs.readFileSync(msgFile, 'utf8');
|
|
220
|
-
const updated = upsertTrailers(original, trailers);
|
|
221
|
-
fs.writeFileSync(msgFile, updated, 'utf8');
|
|
215
|
+
fs.writeFileSync(msgFile, upsertTrailers(original, trailers), 'utf8');
|
|
222
216
|
}
|
|
223
217
|
|
|
224
218
|
if (require.main === module) {
|
|
225
|
-
try {
|
|
226
|
-
|
|
227
|
-
} catch (error) {
|
|
228
|
-
fail(error.message || String(error));
|
|
229
|
-
}
|
|
219
|
+
try { main(); }
|
|
220
|
+
catch (error) { console.error(`[SDD] 关联待补充:${error.message};本次提交继续`); }
|
|
230
221
|
}
|
|
231
222
|
|
|
232
223
|
module.exports = {
|