create-dwy 0.25.2 → 0.26.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 +19 -0
- package/package.json +1 -1
- package/src/sync-plan.js +1 -1
- package/src/sync.js +1 -0
- package/src/tab-multiselect.js +2 -2
- package/templates/ai-tools/CHANGELOG.md +10 -0
- package/templates/ai-tools/VERSION +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-commercial-sdk/SKILL.md +20 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +6 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +2 -2
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +2 -2
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/SKILL.md +0 -244
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/files-whitelist-snippet.json +0 -22
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/js-commercial.npmignore +0 -76
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/python-commercial-MANIFEST.in +0 -37
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/assets/python-commercial.gitignore +0 -62
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/js-commercial-guide.md +0 -133
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/publish-blacklist.md +0 -106
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/python-commercial-guide.md +0 -147
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/references/sensitive-patterns.md +0 -112
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_comments.py +0 -260
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_js_publish.sh +0 -110
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/check_python_publish.sh +0 -171
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/detect_sdk.sh +0 -71
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-sdk-spec/scripts/init_config.sh +0 -91
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +0 -136
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/references/semver-spec.md +0 -70
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/scripts/bump.py +0 -137
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""扫 Python docstring 与 JS/TS JSDoc 中的敏感模式。
|
|
3
|
-
|
|
4
|
-
用法:
|
|
5
|
-
check_comments.py <root> [--sdk-paths <json>] [--format text|json]
|
|
6
|
-
|
|
7
|
-
退出码:
|
|
8
|
-
0 = 无违规
|
|
9
|
-
1 = 有违规
|
|
10
|
-
2 = 用法错误
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import ast
|
|
16
|
-
import json
|
|
17
|
-
import re
|
|
18
|
-
import sys
|
|
19
|
-
from dataclasses import dataclass, asdict
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
from typing import Iterable
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
# 敏感模式表,与 references/sensitive-patterns.md 对齐
|
|
25
|
-
PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|
26
|
-
("internal-domain", re.compile(r"\b[\w-]+\.(internal|corp|local|intranet)\b", re.I)),
|
|
27
|
-
("private-ip", re.compile(
|
|
28
|
-
r"\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}"
|
|
29
|
-
r"|192\.168\.\d{1,3}\.\d{1,3}"
|
|
30
|
-
r"|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3})\b"
|
|
31
|
-
)),
|
|
32
|
-
("sql-statement", re.compile(
|
|
33
|
-
r"\b(SELECT|UPDATE|DELETE|INSERT)\b[^.\n]{0,80}\b(FROM|WHERE|SET|INTO)\b", re.I
|
|
34
|
-
)),
|
|
35
|
-
("redis-key-pattern", re.compile(r"\b(cache|redis|fmt|lock|queue):[a-z_]+:")),
|
|
36
|
-
("jira-ticket", re.compile(r"\b[A-Z]{2,6}-\d{2,6}\b")),
|
|
37
|
-
("internal-service", re.compile(
|
|
38
|
-
r"\b[a-z_]+_(?:service|server|grpc|rpc)_(?:v\d|prod|stg|stage|dev)\b"
|
|
39
|
-
)),
|
|
40
|
-
("aws-arn", re.compile(r"arn:aws:[a-z0-9-]+:[a-z0-9-]*:\d+:[a-zA-Z0-9-_/:]+")),
|
|
41
|
-
("aws-key", re.compile(r"\bAKIA[0-9A-Z]{16}\b")),
|
|
42
|
-
("cred-keyword", re.compile(
|
|
43
|
-
r"\b(API_KEY|TOKEN|SECRET|PASSWORD|PRIVATE_KEY)\s*[=:]\s*['\"][\w\-./+]{6,}['\"]"
|
|
44
|
-
)),
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
# 弱模式(仅在行内有上下文关键字时命中,降误报)
|
|
48
|
-
PORT_PATTERN = re.compile(r":(\d{4,5})\b")
|
|
49
|
-
PORT_CONTEXT = re.compile(r"\b(connect|server|host|endpoint|address|listen|bind)\b", re.I)
|
|
50
|
-
|
|
51
|
-
EMAIL_PATTERN = re.compile(r"[\w.+-]+@[\w.-]+\.[a-z]{2,}")
|
|
52
|
-
PUBLIC_EMAIL_DOMAINS = {
|
|
53
|
-
"example.com", "example.org", "example.net",
|
|
54
|
-
"gmail.com", "outlook.com", "hotmail.com", "qq.com", "163.com",
|
|
55
|
-
"yourdomain.com", "your-domain.com", "domain.com",
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# 行内示例 / 占位关键字 → 整行视为示例不告警
|
|
59
|
-
EXAMPLE_HINTS = re.compile(r"\b(example|示例|e\.g\.|placeholder|占位|your[- ]?domain|<.*>)\b", re.I)
|
|
60
|
-
|
|
61
|
-
# 行内豁免
|
|
62
|
-
IGNORE_MARKER = re.compile(r"#\s*sdk-spec:\s*ignore|//\s*sdk-spec:\s*ignore")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
@dataclass
|
|
66
|
-
class Violation:
|
|
67
|
-
file: str
|
|
68
|
-
line: int
|
|
69
|
-
pattern: str
|
|
70
|
-
match: str
|
|
71
|
-
context: str
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def scan_text(file_rel: str, text: str, start_line_offset: int = 0) -> list[Violation]:
|
|
75
|
-
"""扫一段连续文本(docstring 或注释块),返回违规列表。
|
|
76
|
-
|
|
77
|
-
`text` 内部带换行,`start_line_offset` 是该文本第一行在文件中的实际行号。
|
|
78
|
-
"""
|
|
79
|
-
out: list[Violation] = []
|
|
80
|
-
for i, line in enumerate(text.splitlines(), start=start_line_offset):
|
|
81
|
-
if IGNORE_MARKER.search(line):
|
|
82
|
-
continue
|
|
83
|
-
line_stripped = line.strip()
|
|
84
|
-
if not line_stripped:
|
|
85
|
-
continue
|
|
86
|
-
|
|
87
|
-
is_example = bool(EXAMPLE_HINTS.search(line))
|
|
88
|
-
|
|
89
|
-
# 主模式
|
|
90
|
-
for pat_name, pat in PATTERNS:
|
|
91
|
-
for m in pat.finditer(line):
|
|
92
|
-
if is_example and pat_name in {"sql-statement", "internal-domain", "jira-ticket"}:
|
|
93
|
-
continue
|
|
94
|
-
out.append(Violation(
|
|
95
|
-
file=file_rel, line=i,
|
|
96
|
-
pattern=pat_name, match=m.group(0),
|
|
97
|
-
context=line_stripped[:160],
|
|
98
|
-
))
|
|
99
|
-
|
|
100
|
-
# 端口(弱模式)
|
|
101
|
-
if not is_example and PORT_CONTEXT.search(line):
|
|
102
|
-
for m in PORT_PATTERN.finditer(line):
|
|
103
|
-
port = int(m.group(1))
|
|
104
|
-
if port in {8080, 8000, 3000, 5000, 5173, 5432, 6379, 9000}:
|
|
105
|
-
# 常见示例端口,跳过
|
|
106
|
-
continue
|
|
107
|
-
out.append(Violation(
|
|
108
|
-
file=file_rel, line=i,
|
|
109
|
-
pattern="service-port", match=m.group(0),
|
|
110
|
-
context=line_stripped[:160],
|
|
111
|
-
))
|
|
112
|
-
|
|
113
|
-
# 邮箱(弱模式)
|
|
114
|
-
if not is_example:
|
|
115
|
-
for m in EMAIL_PATTERN.finditer(line):
|
|
116
|
-
email = m.group(0)
|
|
117
|
-
domain = email.split("@", 1)[1].lower()
|
|
118
|
-
if domain in PUBLIC_EMAIL_DOMAINS:
|
|
119
|
-
continue
|
|
120
|
-
out.append(Violation(
|
|
121
|
-
file=file_rel, line=i,
|
|
122
|
-
pattern="internal-email", match=email,
|
|
123
|
-
context=line_stripped[:160],
|
|
124
|
-
))
|
|
125
|
-
return out
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def scan_python_file(path: Path, root: Path) -> list[Violation]:
|
|
129
|
-
rel = str(path.relative_to(root))
|
|
130
|
-
try:
|
|
131
|
-
source = path.read_text(encoding="utf-8")
|
|
132
|
-
except (UnicodeDecodeError, OSError):
|
|
133
|
-
return []
|
|
134
|
-
try:
|
|
135
|
-
tree = ast.parse(source)
|
|
136
|
-
except SyntaxError:
|
|
137
|
-
return []
|
|
138
|
-
|
|
139
|
-
out: list[Violation] = []
|
|
140
|
-
# 模块、函数、类、异步函数的 docstring
|
|
141
|
-
for node in ast.walk(tree):
|
|
142
|
-
if not isinstance(node, (ast.Module, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
|
143
|
-
continue
|
|
144
|
-
doc = ast.get_docstring(node, clean=False)
|
|
145
|
-
if not doc:
|
|
146
|
-
continue
|
|
147
|
-
# docstring 首行在 node.body[0].lineno(第一条 Expr 是字符串)
|
|
148
|
-
first_stmt = node.body[0] if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) else node.body[0]
|
|
149
|
-
start_line = getattr(first_stmt, "lineno", 1)
|
|
150
|
-
out.extend(scan_text(rel, doc, start_line_offset=start_line))
|
|
151
|
-
return out
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
# 匹配 JS/TS 的 /** ... */ JSDoc 块(不含其他 /* */ 注释)
|
|
155
|
-
JSDOC_BLOCK = re.compile(r"/\*\*(.*?)\*/", re.DOTALL)
|
|
156
|
-
# 行注释 //
|
|
157
|
-
LINE_COMMENT = re.compile(r"^\s*//\s?(.*)$")
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def scan_js_file(path: Path, root: Path) -> list[Violation]:
|
|
161
|
-
rel = str(path.relative_to(root))
|
|
162
|
-
try:
|
|
163
|
-
source = path.read_text(encoding="utf-8")
|
|
164
|
-
except (UnicodeDecodeError, OSError):
|
|
165
|
-
return []
|
|
166
|
-
|
|
167
|
-
out: list[Violation] = []
|
|
168
|
-
|
|
169
|
-
# JSDoc 块
|
|
170
|
-
for m in JSDOC_BLOCK.finditer(source):
|
|
171
|
-
block_text = m.group(1)
|
|
172
|
-
# 起始行号
|
|
173
|
-
start_line = source.count("\n", 0, m.start()) + 1
|
|
174
|
-
# 去掉每行的 leading * 和空白
|
|
175
|
-
cleaned_lines = []
|
|
176
|
-
for line in block_text.splitlines():
|
|
177
|
-
cleaned_lines.append(re.sub(r"^\s*\*\s?", "", line))
|
|
178
|
-
out.extend(scan_text(rel, "\n".join(cleaned_lines), start_line_offset=start_line))
|
|
179
|
-
|
|
180
|
-
# 行内注释(只扫描,因为商业 SDK 内部实现的行注释虽然不打包但开发期能看到)
|
|
181
|
-
# 注:源文件其实是否打包取决于配置,行注释泄露主要看是否打包 src/
|
|
182
|
-
# 跨边界谨慎起见也扫
|
|
183
|
-
for i, line in enumerate(source.splitlines(), start=1):
|
|
184
|
-
lm = LINE_COMMENT.match(line)
|
|
185
|
-
if not lm:
|
|
186
|
-
continue
|
|
187
|
-
comment = lm.group(1)
|
|
188
|
-
out.extend(scan_text(rel, comment, start_line_offset=i))
|
|
189
|
-
|
|
190
|
-
return out
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def iter_target_files(root: Path, sdk_paths: list[str] | None) -> Iterable[Path]:
|
|
194
|
-
"""枚举要扫的文件。sdk_paths 为 None 时全扫;否则只扫指定路径。"""
|
|
195
|
-
excludes = {
|
|
196
|
-
"node_modules", ".venv", "venv", "dist", "build",
|
|
197
|
-
".git", "__pycache__", ".mypy_cache", ".pytest_cache",
|
|
198
|
-
".ruff_cache", ".tox", "egg-info", "site-packages",
|
|
199
|
-
}
|
|
200
|
-
bases = [root / p for p in sdk_paths] if sdk_paths else [root]
|
|
201
|
-
for base in bases:
|
|
202
|
-
if not base.exists():
|
|
203
|
-
continue
|
|
204
|
-
for p in base.rglob("*"):
|
|
205
|
-
if not p.is_file():
|
|
206
|
-
continue
|
|
207
|
-
if any(part in excludes or part.endswith(".egg-info") for part in p.parts):
|
|
208
|
-
continue
|
|
209
|
-
suf = p.suffix.lower()
|
|
210
|
-
if suf in {".py", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"}:
|
|
211
|
-
yield p
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def main() -> int:
|
|
215
|
-
parser = argparse.ArgumentParser(description="Scan SDK comments for sensitive patterns.")
|
|
216
|
-
parser.add_argument("root", help="Project root directory (absolute path)")
|
|
217
|
-
parser.add_argument("--sdk-paths", default=None,
|
|
218
|
-
help='JSON {"python":[...], "js":[...]} relative to root. Omit to scan all.')
|
|
219
|
-
parser.add_argument("--format", choices=["text", "json"], default="text")
|
|
220
|
-
args = parser.parse_args()
|
|
221
|
-
|
|
222
|
-
root = Path(args.root).resolve()
|
|
223
|
-
if not root.is_dir():
|
|
224
|
-
print(f"ERROR: not a directory: {root}", file=sys.stderr)
|
|
225
|
-
return 2
|
|
226
|
-
|
|
227
|
-
target_paths: list[str] | None = None
|
|
228
|
-
if args.sdk_paths:
|
|
229
|
-
try:
|
|
230
|
-
sdk_spec = json.loads(args.sdk_paths)
|
|
231
|
-
except json.JSONDecodeError as e:
|
|
232
|
-
print(f"ERROR: invalid --sdk-paths JSON: {e}", file=sys.stderr)
|
|
233
|
-
return 2
|
|
234
|
-
target_paths = []
|
|
235
|
-
for key in ("python", "js"):
|
|
236
|
-
target_paths.extend(sdk_spec.get(key, []))
|
|
237
|
-
|
|
238
|
-
violations: list[Violation] = []
|
|
239
|
-
for fp in iter_target_files(root, target_paths):
|
|
240
|
-
if fp.suffix == ".py":
|
|
241
|
-
violations.extend(scan_python_file(fp, root))
|
|
242
|
-
else:
|
|
243
|
-
violations.extend(scan_js_file(fp, root))
|
|
244
|
-
|
|
245
|
-
if args.format == "json":
|
|
246
|
-
print(json.dumps([asdict(v) for v in violations], ensure_ascii=False, indent=2))
|
|
247
|
-
else:
|
|
248
|
-
if not violations:
|
|
249
|
-
print("OK: no sensitive patterns found")
|
|
250
|
-
else:
|
|
251
|
-
print(f"FAIL: {len(violations)} violation(s) found:\n")
|
|
252
|
-
for v in violations:
|
|
253
|
-
print(f" {v.file}:{v.line} [{v.pattern}] match={v.match!r}")
|
|
254
|
-
print(f" > {v.context}")
|
|
255
|
-
|
|
256
|
-
return 1 if violations else 0
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if __name__ == "__main__":
|
|
260
|
-
sys.exit(main())
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# JS/TS SDK 发布前文件清单审计(npm pack --dry-run)
|
|
3
|
-
# 用法: check_js_publish.sh <js_sdk_root> --edition commercial|opensource
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
if [[ $# -lt 3 ]]; then
|
|
8
|
-
echo "Usage: $0 <js_sdk_root> --edition commercial|opensource" >&2
|
|
9
|
-
exit 2
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
PKG_ROOT="$1"
|
|
13
|
-
shift
|
|
14
|
-
|
|
15
|
-
EDITION=""
|
|
16
|
-
while [[ $# -gt 0 ]]; do
|
|
17
|
-
case "$1" in
|
|
18
|
-
--edition) EDITION="$2"; shift 2 ;;
|
|
19
|
-
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
|
|
20
|
-
esac
|
|
21
|
-
done
|
|
22
|
-
|
|
23
|
-
if [[ "$EDITION" != "commercial" && "$EDITION" != "opensource" ]]; then
|
|
24
|
-
echo "ERROR: --edition must be 'commercial' or 'opensource'" >&2
|
|
25
|
-
exit 2
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
if [[ ! -d "$PKG_ROOT" ]]; then
|
|
29
|
-
echo "ERROR: not a directory: $PKG_ROOT" >&2
|
|
30
|
-
exit 2
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
if ! command -v npm >/dev/null 2>&1; then
|
|
34
|
-
echo "ERROR: npm not found in PATH" >&2
|
|
35
|
-
exit 2
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
echo "[1/2] Running npm pack --dry-run in $PKG_ROOT ..."
|
|
39
|
-
|
|
40
|
-
# npm pack --dry-run --json 输出包内文件列表
|
|
41
|
-
# 注:某些 npm 版本 --json 输出到 stderr,--silent 抑制非 JSON 输出
|
|
42
|
-
PACK_OUTPUT=$(cd "$PKG_ROOT" && npm pack --dry-run --json 2>/dev/null) || {
|
|
43
|
-
echo "ERROR: npm pack --dry-run failed; run 'npm install' first?" >&2
|
|
44
|
-
exit 2
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
# 提取文件路径列表
|
|
48
|
-
FILES=$(echo "$PACK_OUTPUT" | jq -r '.[0].files[].path' 2>/dev/null) || {
|
|
49
|
-
echo "ERROR: failed to parse npm pack output:" >&2
|
|
50
|
-
echo "$PACK_OUTPUT" | head -20 >&2
|
|
51
|
-
exit 2
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
echo "[2/2] Audit result (edition=$EDITION):"
|
|
55
|
-
echo "----------------------------------------"
|
|
56
|
-
|
|
57
|
-
# 致命级(任何版本都禁)
|
|
58
|
-
CRITICAL_REGEX='(^|/)(\.env(\..+)?|\.npmrc|\.pypirc|\.git-credentials|id_rsa|id_ed25519|secrets\.json|credentials\.json)$|\.(pem|key|p12)$|(^|/)\.aws/credentials$'
|
|
59
|
-
|
|
60
|
-
# 开源版黑名单
|
|
61
|
-
OPENSOURCE_DENY_REGEX='(^|/)(src|tests|__tests__|\.git|\.github|\.gitlab-ci\.yml|\.travis\.yml|\.circleci|\.vscode|\.idea|node_modules|\.husky)/|(^|/)(tsconfig.*\.json|vite\.config\.|vitest\.config\.|tsup\.config\.|rollup\.config\.|webpack\.config\.|jest\.config\.|\.eslintrc.*|\.prettierrc.*|eslint\.config\.|prettier\.config\.|commitlint\.config\.|\.gitignore|\.gitattributes|\.DS_Store|Thumbs\.db|pnpm-lock\.yaml|package-lock\.json|yarn\.lock)|\.(map|tsbuildinfo|swp|swo)$|\.(test|spec)\.(ts|tsx|js|jsx)$'
|
|
62
|
-
|
|
63
|
-
# 商业版白名单(其他视为违规)
|
|
64
|
-
COMMERCIAL_ALLOW_REGEX='^(dist/|bin/|package\.json$|README(\..+)?$|LICEN[SC]E(\..+)?$|CHANGELOG(\..+)?$)'
|
|
65
|
-
|
|
66
|
-
violations=()
|
|
67
|
-
critical=()
|
|
68
|
-
|
|
69
|
-
while IFS= read -r f; do
|
|
70
|
-
[[ -z "$f" ]] && continue
|
|
71
|
-
|
|
72
|
-
if [[ "$f" =~ $CRITICAL_REGEX ]]; then
|
|
73
|
-
critical+=("$f")
|
|
74
|
-
continue
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
if [[ "$EDITION" == "opensource" ]]; then
|
|
78
|
-
if [[ "$f" =~ $OPENSOURCE_DENY_REGEX ]]; then
|
|
79
|
-
violations+=("$f [denied by opensource rules]")
|
|
80
|
-
fi
|
|
81
|
-
else
|
|
82
|
-
if [[ ! "$f" =~ $COMMERCIAL_ALLOW_REGEX ]]; then
|
|
83
|
-
violations+=("$f [not in commercial whitelist]")
|
|
84
|
-
fi
|
|
85
|
-
fi
|
|
86
|
-
done <<< "$FILES"
|
|
87
|
-
|
|
88
|
-
if [[ ${#critical[@]} -gt 0 ]]; then
|
|
89
|
-
echo "[CRITICAL] secret-like files in package — DO NOT PUBLISH:"
|
|
90
|
-
for f in "${critical[@]}"; do
|
|
91
|
-
echo " $f"
|
|
92
|
-
done
|
|
93
|
-
echo ""
|
|
94
|
-
fi
|
|
95
|
-
|
|
96
|
-
if [[ ${#violations[@]} -gt 0 ]]; then
|
|
97
|
-
echo "[VIOLATION] ${#violations[@]} file(s) violate $EDITION rules:"
|
|
98
|
-
for v in "${violations[@]}"; do
|
|
99
|
-
echo " $v"
|
|
100
|
-
done
|
|
101
|
-
echo ""
|
|
102
|
-
exit 1
|
|
103
|
-
fi
|
|
104
|
-
|
|
105
|
-
if [[ ${#critical[@]} -gt 0 ]]; then
|
|
106
|
-
exit 1
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
count=$(echo "$FILES" | grep -c .)
|
|
110
|
-
echo "OK: package passes $EDITION audit ($count file(s))"
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Python SDK 发布前文件清单审计(不实际发布)
|
|
3
|
-
# 用法: check_python_publish.sh <python_sdk_root> --edition commercial|opensource
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
if [[ $# -lt 3 ]]; then
|
|
8
|
-
echo "Usage: $0 <python_sdk_root> --edition commercial|opensource" >&2
|
|
9
|
-
exit 2
|
|
10
|
-
fi
|
|
11
|
-
|
|
12
|
-
PKG_ROOT="$1"
|
|
13
|
-
shift
|
|
14
|
-
|
|
15
|
-
EDITION=""
|
|
16
|
-
while [[ $# -gt 0 ]]; do
|
|
17
|
-
case "$1" in
|
|
18
|
-
--edition) EDITION="$2"; shift 2 ;;
|
|
19
|
-
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
|
|
20
|
-
esac
|
|
21
|
-
done
|
|
22
|
-
|
|
23
|
-
if [[ "$EDITION" != "commercial" && "$EDITION" != "opensource" ]]; then
|
|
24
|
-
echo "ERROR: --edition must be 'commercial' or 'opensource'" >&2
|
|
25
|
-
exit 2
|
|
26
|
-
fi
|
|
27
|
-
|
|
28
|
-
if [[ ! -d "$PKG_ROOT" ]]; then
|
|
29
|
-
echo "ERROR: not a directory: $PKG_ROOT" >&2
|
|
30
|
-
exit 2
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
if ! command -v python >/dev/null 2>&1 && ! command -v python3 >/dev/null 2>&1; then
|
|
34
|
-
echo "ERROR: python not found in PATH" >&2
|
|
35
|
-
exit 2
|
|
36
|
-
fi
|
|
37
|
-
PY=$(command -v python || command -v python3)
|
|
38
|
-
|
|
39
|
-
# 临时构建目录
|
|
40
|
-
TMPDIR=$(mktemp -d -t dwy-sdk-spec-py-XXXX)
|
|
41
|
-
trap 'rm -rf "$TMPDIR"' EXIT
|
|
42
|
-
|
|
43
|
-
echo "[1/3] Building wheel to $TMPDIR (--wheel: skip sdist 中转,商业 Cython 项目 MANIFEST 通常排除 _*.py 源码,默认 sdist→wheel 会失败) ..."
|
|
44
|
-
(cd "$PKG_ROOT" && "$PY" -m build --wheel --outdir "$TMPDIR") >&2
|
|
45
|
-
|
|
46
|
-
WHEEL=$(find "$TMPDIR" -name '*.whl' -type f | head -1)
|
|
47
|
-
if [[ -z "$WHEEL" ]]; then
|
|
48
|
-
echo "ERROR: no wheel produced" >&2
|
|
49
|
-
exit 2
|
|
50
|
-
fi
|
|
51
|
-
echo "[2/3] Listing wheel contents: $(basename "$WHEEL")"
|
|
52
|
-
|
|
53
|
-
# 列文件(剥掉 .dist-info 路径前缀,便于审计)
|
|
54
|
-
FILES=$(unzip -Z1 "$WHEEL")
|
|
55
|
-
|
|
56
|
-
# 致命级(任何版本都禁)
|
|
57
|
-
declare -a CRITICAL_PATTERNS=(
|
|
58
|
-
'(^|/)\.env(\..+)?$'
|
|
59
|
-
'(^|/)\.npmrc$'
|
|
60
|
-
'(^|/)\.pypirc$'
|
|
61
|
-
'(^|/)\.git-credentials$'
|
|
62
|
-
'(^|/)id_rsa$'
|
|
63
|
-
'(^|/)id_ed25519$'
|
|
64
|
-
'\.pem$'
|
|
65
|
-
'\.key$'
|
|
66
|
-
'\.p12$'
|
|
67
|
-
'(^|/)secrets\.json$'
|
|
68
|
-
'(^|/)credentials\.json$'
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# 开源版黑名单
|
|
72
|
-
declare -a OPENSOURCE_DENY=(
|
|
73
|
-
'__pycache__/'
|
|
74
|
-
'\.pyc$'
|
|
75
|
-
'\.pyo$'
|
|
76
|
-
'\.egg-info/'
|
|
77
|
-
'\.mypy_cache/'
|
|
78
|
-
'\.pytest_cache/'
|
|
79
|
-
'\.ruff_cache/'
|
|
80
|
-
'\.pdb$'
|
|
81
|
-
'(^|/)\.git/'
|
|
82
|
-
'(^|/)\.gitignore$'
|
|
83
|
-
'(^|/)\.DS_Store$'
|
|
84
|
-
'(^|/)\.vscode/'
|
|
85
|
-
'(^|/)\.idea/'
|
|
86
|
-
'\.bak$'
|
|
87
|
-
'\.swp$'
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# 商业版白名单(其他视为违规)
|
|
91
|
-
declare -a COMMERCIAL_ALLOW=(
|
|
92
|
-
'\.so$'
|
|
93
|
-
'\.pyi$'
|
|
94
|
-
'(^|/)py\.typed$'
|
|
95
|
-
'\.dist-info/'
|
|
96
|
-
'(^|/)LICEN[SC]E(\..+)?$'
|
|
97
|
-
'(^|/)README(\..+)?$'
|
|
98
|
-
'\.json$'
|
|
99
|
-
'\.txt$'
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
violations=()
|
|
103
|
-
critical=()
|
|
104
|
-
|
|
105
|
-
while IFS= read -r f; do
|
|
106
|
-
[[ -z "$f" ]] && continue
|
|
107
|
-
|
|
108
|
-
# 致命级
|
|
109
|
-
for pat in "${CRITICAL_PATTERNS[@]}"; do
|
|
110
|
-
if [[ "$f" =~ $pat ]]; then
|
|
111
|
-
critical+=("$f")
|
|
112
|
-
continue 2
|
|
113
|
-
fi
|
|
114
|
-
done
|
|
115
|
-
|
|
116
|
-
if [[ "$EDITION" == "opensource" ]]; then
|
|
117
|
-
for pat in "${OPENSOURCE_DENY[@]}"; do
|
|
118
|
-
if [[ "$f" =~ $pat ]]; then
|
|
119
|
-
violations+=("$f [denied: $pat]")
|
|
120
|
-
break
|
|
121
|
-
fi
|
|
122
|
-
done
|
|
123
|
-
else
|
|
124
|
-
# commercial: 白名单检查
|
|
125
|
-
# 排除目录条目(以 / 结尾)
|
|
126
|
-
[[ "$f" == */ ]] && continue
|
|
127
|
-
matched=0
|
|
128
|
-
for pat in "${COMMERCIAL_ALLOW[@]}"; do
|
|
129
|
-
if [[ "$f" =~ $pat ]]; then
|
|
130
|
-
matched=1
|
|
131
|
-
break
|
|
132
|
-
fi
|
|
133
|
-
done
|
|
134
|
-
# 单独允许:包根的 __init__.py(公开入口)/ 不以 _ 开头的 .py
|
|
135
|
-
if [[ $matched -eq 0 ]] && [[ "$f" =~ \.py$ ]]; then
|
|
136
|
-
base=$(basename "$f")
|
|
137
|
-
if [[ "$base" == "__init__.py" ]] || [[ ! "$base" =~ ^_ ]]; then
|
|
138
|
-
matched=1
|
|
139
|
-
fi
|
|
140
|
-
fi
|
|
141
|
-
if [[ $matched -eq 0 ]]; then
|
|
142
|
-
violations+=("$f [not in commercial whitelist]")
|
|
143
|
-
fi
|
|
144
|
-
fi
|
|
145
|
-
done <<< "$FILES"
|
|
146
|
-
|
|
147
|
-
echo "[3/3] Audit result (edition=$EDITION):"
|
|
148
|
-
echo "----------------------------------------"
|
|
149
|
-
|
|
150
|
-
if [[ ${#critical[@]} -gt 0 ]]; then
|
|
151
|
-
echo "[CRITICAL] secret-like files in wheel — DO NOT PUBLISH:"
|
|
152
|
-
for f in "${critical[@]}"; do
|
|
153
|
-
echo " $f"
|
|
154
|
-
done
|
|
155
|
-
echo ""
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
if [[ ${#violations[@]} -gt 0 ]]; then
|
|
159
|
-
echo "[VIOLATION] ${#violations[@]} file(s) violate $EDITION rules:"
|
|
160
|
-
for v in "${violations[@]}"; do
|
|
161
|
-
echo " $v"
|
|
162
|
-
done
|
|
163
|
-
echo ""
|
|
164
|
-
exit 1
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
if [[ ${#critical[@]} -gt 0 ]]; then
|
|
168
|
-
exit 1
|
|
169
|
-
fi
|
|
170
|
-
|
|
171
|
-
echo "OK: wheel passes $EDITION audit ($(echo "$FILES" | wc -l | tr -d ' ') file(s))"
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# 探测项目内的 Python / JS SDK 子包
|
|
3
|
-
# 用法: detect_sdk.sh <project_root>
|
|
4
|
-
# 输出: JSON {"python": [...], "js": [...]} 路径相对项目根
|
|
5
|
-
|
|
6
|
-
set -euo pipefail
|
|
7
|
-
|
|
8
|
-
PROJECT_ROOT="${1:-$(pwd)}"
|
|
9
|
-
|
|
10
|
-
if [[ ! -d "$PROJECT_ROOT" ]]; then
|
|
11
|
-
echo "ERROR: not a directory: $PROJECT_ROOT" >&2
|
|
12
|
-
exit 2
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
# 找 Python SDK:pyproject.toml 含 [project] 段,或 setup.py
|
|
16
|
-
find_python_sdks() {
|
|
17
|
-
local results=()
|
|
18
|
-
# 项目根
|
|
19
|
-
if [[ -f "$PROJECT_ROOT/pyproject.toml" ]] && grep -q '^\[project\]' "$PROJECT_ROOT/pyproject.toml" 2>/dev/null; then
|
|
20
|
-
results+=(".")
|
|
21
|
-
elif [[ -f "$PROJECT_ROOT/setup.py" ]]; then
|
|
22
|
-
results+=(".")
|
|
23
|
-
fi
|
|
24
|
-
# 子目录(限 2 层深度,排除 node_modules / .venv / venv / dist / build / .git)
|
|
25
|
-
while IFS= read -r f; do
|
|
26
|
-
local d
|
|
27
|
-
d=$(dirname "$f")
|
|
28
|
-
local rel="${d#"$PROJECT_ROOT"/}"
|
|
29
|
-
# 验证含 [project] 段
|
|
30
|
-
if grep -q '^\[project\]' "$f" 2>/dev/null; then
|
|
31
|
-
results+=("$rel")
|
|
32
|
-
fi
|
|
33
|
-
done < <(find "$PROJECT_ROOT" -mindepth 2 -maxdepth 3 -name pyproject.toml -type f \
|
|
34
|
-
-not -path "*/node_modules/*" \
|
|
35
|
-
-not -path "*/.venv/*" \
|
|
36
|
-
-not -path "*/venv/*" \
|
|
37
|
-
-not -path "*/dist/*" \
|
|
38
|
-
-not -path "*/build/*" \
|
|
39
|
-
-not -path "*/.git/*" 2>/dev/null)
|
|
40
|
-
|
|
41
|
-
# 去重(空数组下 ${arr[@]} 在 set -u 会 unbound,用 ${arr[@]+...} 安全展开)
|
|
42
|
-
printf '%s\n' "${results[@]+"${results[@]}"}" | awk 'NF && !seen[$0]++'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
# 找 JS SDK:package.json 含 main / exports / module 字段
|
|
46
|
-
find_js_sdks() {
|
|
47
|
-
local results=()
|
|
48
|
-
if [[ -f "$PROJECT_ROOT/package.json" ]] && jq -e '.main // .exports // .module' "$PROJECT_ROOT/package.json" >/dev/null 2>&1; then
|
|
49
|
-
results+=(".")
|
|
50
|
-
fi
|
|
51
|
-
while IFS= read -r f; do
|
|
52
|
-
local d
|
|
53
|
-
d=$(dirname "$f")
|
|
54
|
-
local rel="${d#"$PROJECT_ROOT"/}"
|
|
55
|
-
if jq -e '.main // .exports // .module' "$f" >/dev/null 2>&1; then
|
|
56
|
-
results+=("$rel")
|
|
57
|
-
fi
|
|
58
|
-
done < <(find "$PROJECT_ROOT" -mindepth 2 -maxdepth 3 -name package.json -type f \
|
|
59
|
-
-not -path "*/node_modules/*" \
|
|
60
|
-
-not -path "*/.venv/*" \
|
|
61
|
-
-not -path "*/dist/*" \
|
|
62
|
-
-not -path "*/build/*" \
|
|
63
|
-
-not -path "*/.git/*" 2>/dev/null)
|
|
64
|
-
|
|
65
|
-
printf '%s\n' "${results[@]+"${results[@]}"}" | awk 'NF && !seen[$0]++'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
python_list=$(find_python_sdks | jq -R . | jq -s .)
|
|
69
|
-
js_list=$(find_js_sdks | jq -R . | jq -s .)
|
|
70
|
-
|
|
71
|
-
jq -n --argjson py "$python_list" --argjson js "$js_list" '{python: $py, js: $js}'
|