@aiyiran/myclaw 1.1.34 → 1.1.35
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/assets/myclaw-artifacts.js +150 -100
- package/package.json +1 -1
- package/patches/patch-manifest.json +2 -1
- package/patches/patch-skill.js +18 -8
- package/server/sync_workspace.py +92 -0
- package/skills/yiran-course-template-pipeline/SKILL.md +1 -2
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2651-demo/347/224/237/346/210/220.md +0 -7
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2652-student/347/224/237/346/210/220.md +35 -0
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2653-teacher/347/224/237/346/210/220.md +6 -5
- package/skills/yiran-course-template-pipeline/prompts//351/230/266/346/256/2654-/346/211/223/345/214/205/350/220/275/347/233/230.md +26 -21
- package/skills/yiran-course-template-pipeline/references/teacher-fields.md +5 -5
- package/skills/yiran-course-template-pipeline/references/teacher-scaffold.json +1 -1
- package/skills/yiran-course-template-pipeline/scripts/build_template_index.py +14 -2
- package/skills/yiran-course-template-pipeline/scripts/move_template_task.py +43 -7
- package/skills/yiran-course-template-pipeline/scripts/publish_template.py +44 -120
- package/skills/yiran-course-template-pipeline/template-index.json +269 -0
- package/skills/yiran-course-template-pipeline/template-index.md +52 -0
- package/skills/yiran-course-template-pipeline//345/272/237/345/274/203/rewrite_html_refs.py +102 -0
- package/skills/yiran-playground-template-use/SKILL.md +2 -12
- package/skills/yiran-course-template-pipeline/cdn-convention.md +0 -77
- package/skills/yiran-playground-template-use/scripts/build_template_index.py +0 -103
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
指导 agent 在已有 `__demo__.html`、`__student__.json`、`__teacher__.json` 的前提下,先生成页面,再移动文件夹。
|
|
5
5
|
|
|
6
6
|
注意:
|
|
7
|
-
第4
|
|
7
|
+
第4阶段拆成三个独立脚本,不再混在一个脚本里。
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
- `index.html`
|
|
30
30
|
|
|
31
31
|
其中 `index.html` 使用固定脚本规则:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
32
|
+
- 左边展示"评价目标"
|
|
33
|
+
- 右边展示"步骤"
|
|
34
34
|
- 不显示任务标题
|
|
35
35
|
- 不显示一句话说明
|
|
36
36
|
- 这部分不再靠临时 prompt 自由发挥,而是固定走脚本渲染
|
|
@@ -50,10 +50,7 @@ python3 scripts/build_template_views.py 任务文件夹
|
|
|
50
50
|
## 脚本2:move_template_task.py
|
|
51
51
|
|
|
52
52
|
目标:
|
|
53
|
-
|
|
54
|
-
skill 目录下的 `templates/`
|
|
55
|
-
|
|
56
|
-
同时按规则改名,例如:
|
|
53
|
+
在页面已经生成好的前提下,把整个文件夹直接移动到 skill 目录下的 `templates/`,同时按规则改名,例如:
|
|
57
54
|
`a100_给依然老师设计一个AI画像`
|
|
58
55
|
|
|
59
56
|
调用方式:
|
|
@@ -68,32 +65,40 @@ python3 scripts/move_template_task.py 任务文件夹
|
|
|
68
65
|
|
|
69
66
|
---
|
|
70
67
|
|
|
71
|
-
---
|
|
72
|
-
|
|
73
68
|
## 脚本3:publish_template.py
|
|
74
69
|
|
|
75
70
|
目标:
|
|
76
|
-
|
|
71
|
+
把模板文件夹内所有文件上传到七牛 CDN,完成后重建并上传 `template-index.json`。
|
|
77
72
|
|
|
78
73
|
处理逻辑:
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
- 本地能找到的文件:上传到七牛 → 重写引用为新 CDN URL
|
|
82
|
-
- 本地找不到的引用:保持原样不动
|
|
74
|
+
- 遍历文件夹内所有文件,逐一上传(强制覆盖)
|
|
75
|
+
- 相对路径在 CDN 上天然可用,无需重写引用
|
|
83
76
|
|
|
84
|
-
CDN
|
|
77
|
+
**CDN 配置:**
|
|
78
|
+
|
|
79
|
+
| 项 | 值 |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Bucket | `yiran1` |
|
|
82
|
+
| CDN 域名 | `https://cdn.yiranlaoshi.com` |
|
|
83
|
+
|
|
84
|
+
**CDN 路径规则:**
|
|
85
85
|
```
|
|
86
86
|
https://cdn.yiranlaoshi.com/myclaw/live/yiran/skills/yiran-playground-template-use/templates/{文件夹名}/{文件名}
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
七牛 key:
|
|
90
|
+
```
|
|
91
|
+
myclaw/live/yiran/skills/yiran-playground-template-use/templates/{文件夹名}/{文件名}
|
|
92
|
+
```
|
|
93
|
+
|
|
89
94
|
调用方式:
|
|
90
95
|
```bash
|
|
91
|
-
|
|
92
|
-
|
|
96
|
+
# 发布单个模板
|
|
97
|
+
python3 scripts/publish_template.py templates/a103_做一个介绍页面
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
# 全量发布所有模板
|
|
100
|
+
python3 scripts/publish_template.py --all
|
|
101
|
+
```
|
|
97
102
|
|
|
98
103
|
---
|
|
99
104
|
|
|
@@ -103,5 +108,5 @@ python3 scripts/publish_template.py templates下的目标文件夹路径
|
|
|
103
108
|
2. 先 build,再 move,再 publish
|
|
104
109
|
3. build 只在原目录生成页面,不移动
|
|
105
110
|
4. move 只负责移动和改名,并自动触发索引构建
|
|
106
|
-
5. publish
|
|
111
|
+
5. publish 负责全量上传到 CDN,是最终对外发布的步骤
|
|
107
112
|
6. 文件命名固定,不临时改名
|
|
@@ -111,7 +111,7 @@ __teacher__.json 中有两组固定标签:
|
|
|
111
111
|
|
|
112
112
|
主能力标签只能从下面 4 个里选 1 个:
|
|
113
113
|
|
|
114
|
-
###
|
|
114
|
+
### 第一能力:构思能力
|
|
115
115
|
适合:
|
|
116
116
|
- 重点在想主题
|
|
117
117
|
- 重点在想设定
|
|
@@ -122,7 +122,7 @@ __teacher__.json 中有两组固定标签:
|
|
|
122
122
|
- 先想一个故事
|
|
123
123
|
- 先想一个主题
|
|
124
124
|
|
|
125
|
-
###
|
|
125
|
+
### 第二能力:表达构建
|
|
126
126
|
适合:
|
|
127
127
|
- 重点在把内容组织出来
|
|
128
128
|
- 重点在把想法做成页面或作品
|
|
@@ -133,7 +133,7 @@ __teacher__.json 中有两组固定标签:
|
|
|
133
133
|
- 做展示页
|
|
134
134
|
- 做图文页面
|
|
135
135
|
|
|
136
|
-
###
|
|
136
|
+
### 第三能力:问题判断
|
|
137
137
|
适合:
|
|
138
138
|
- 重点在发现问题
|
|
139
139
|
- 重点在判断哪里不合适
|
|
@@ -144,7 +144,7 @@ __teacher__.json 中有两组固定标签:
|
|
|
144
144
|
- 找出页面问题
|
|
145
145
|
- 判断哪里需要调整
|
|
146
146
|
|
|
147
|
-
###
|
|
147
|
+
### 第四能力:迭代推进
|
|
148
148
|
适合:
|
|
149
149
|
- 重点在继续修改
|
|
150
150
|
- 重点在一版一版优化
|
|
@@ -243,7 +243,7 @@ __teacher__.json 中有两组固定标签:
|
|
|
243
243
|
|
|
244
244
|
### 4)主能力标签
|
|
245
245
|
- 只能选 1 个
|
|
246
|
-
-
|
|
246
|
+
- 固定集合:第一能力:构思能力 / 第二能力:表达构建 / 第三能力:问题判断 / 第四能力:迭代推进
|
|
247
247
|
|
|
248
248
|
### 5)任务类型标签
|
|
249
249
|
- 只能选 1 个
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"任务类别": "请填写任务类别,只能使用 A、B、C。其中 A=启动类,B=结构类,C=迭代类",
|
|
3
3
|
"任务编号": "请填写三位编号字符串,编号范围为 100 到 999。每个类别单独从 100 开始,后续按顺序递增,例如 100、101、102",
|
|
4
4
|
"任务名称": "请填写任务名称,应和 __student__.json 中的任务标题一致或高度一致",
|
|
5
|
-
"主能力标签": "
|
|
5
|
+
"主能力标签": "请从固定标签中只选一个。固定集合:第一能力:构思能力 / 第二能力:表达构建 / 第三能力:问题判断 / 第四能力:迭代推进。它回答的是这个任务主要训练什么能力",
|
|
6
6
|
"任务类型标签": "请从固定标签中只选一个。固定集合:游戏机制 / 角色交互 / 视听表达 / 改编优化。它回答的是这个任务最终属于什么作品类型",
|
|
7
7
|
"适合学生类型": [
|
|
8
8
|
"请填写一个适合学生类型标签,例如:初次接触"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import json
|
|
3
3
|
import re
|
|
4
|
+
from datetime import datetime, timezone
|
|
4
5
|
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
5
7
|
|
|
6
8
|
ROOT = Path(__file__).resolve().parent.parent
|
|
7
9
|
TEMPLATES_DIR = ROOT / 'templates'
|
|
@@ -23,7 +25,7 @@ def parse_folder_name(name: str):
|
|
|
23
25
|
return None, None, None
|
|
24
26
|
|
|
25
27
|
|
|
26
|
-
def get_entry_file(folder: Path) -> Path
|
|
28
|
+
def get_entry_file(folder: Path) -> Optional[Path]:
|
|
27
29
|
for name in ['index.html', '__demo__.html', '__student-view__.html']:
|
|
28
30
|
if (folder / name).exists():
|
|
29
31
|
return folder / name
|
|
@@ -61,6 +63,12 @@ def main():
|
|
|
61
63
|
])))
|
|
62
64
|
|
|
63
65
|
entry = get_entry_file(folder)
|
|
66
|
+
|
|
67
|
+
# 文件夹内所有文件列表 + 最新 mtime
|
|
68
|
+
files = sorted(f.name for f in folder.iterdir() if f.is_file())
|
|
69
|
+
folder_mtime = max((f.stat().st_mtime for f in folder.iterdir() if f.is_file()), default=0)
|
|
70
|
+
updated_at = datetime.fromtimestamp(folder_mtime, tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
71
|
+
|
|
64
72
|
record = {
|
|
65
73
|
'系列': series,
|
|
66
74
|
'编号': number,
|
|
@@ -74,6 +82,8 @@ def main():
|
|
|
74
82
|
'主能力标签': 能力标签,
|
|
75
83
|
'任务类型标签': 类型标签,
|
|
76
84
|
'关键词': keywords,
|
|
85
|
+
'files': files,
|
|
86
|
+
'updated_at': updated_at,
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
if series not in index:
|
|
@@ -81,7 +91,9 @@ def main():
|
|
|
81
91
|
index[series][number] = record
|
|
82
92
|
items_for_md.append(record)
|
|
83
93
|
|
|
84
|
-
|
|
94
|
+
index_updated_at = datetime.now(tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
95
|
+
output = {'index_updated_at': index_updated_at, 'templates': index}
|
|
96
|
+
OUTPUT_JSON.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding='utf-8')
|
|
85
97
|
|
|
86
98
|
lines = ['# 模板索引', '', '每次 templates 内模板有变化后,都应重新运行本脚本。', '']
|
|
87
99
|
for item in items_for_md:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import json
|
|
3
|
+
import re
|
|
3
4
|
import shutil
|
|
4
5
|
import subprocess
|
|
5
6
|
import sys
|
|
@@ -16,16 +17,46 @@ def load_json(path):
|
|
|
16
17
|
return json.load(f)
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
def
|
|
20
|
+
def save_json(path, data):
|
|
21
|
+
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def normalize_category(teacher):
|
|
20
25
|
category = str(teacher['任务类别']).upper()
|
|
21
26
|
if category not in {'A', 'B', 'C'}:
|
|
22
27
|
raise ValueError('任务类别只能是 A、B、C')
|
|
28
|
+
return category
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def sanitize_name(name):
|
|
32
|
+
return str(name).replace('/', '_').replace(' ', '_')
|
|
33
|
+
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
def find_next_available_number(category, target_root):
|
|
36
|
+
used = set()
|
|
37
|
+
pattern = re.compile(r'^([a-zA-Z])(\d{3})_')
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
for folder in target_root.iterdir() if target_root.exists() else []:
|
|
40
|
+
if not folder.is_dir():
|
|
41
|
+
continue
|
|
42
|
+
m = pattern.match(folder.name)
|
|
43
|
+
if not m:
|
|
44
|
+
continue
|
|
45
|
+
if m.group(1).upper() != category:
|
|
46
|
+
continue
|
|
47
|
+
used.add(int(m.group(2)))
|
|
48
|
+
|
|
49
|
+
for num in range(100, 1000):
|
|
50
|
+
if num not in used:
|
|
51
|
+
return f'{num:03d}'
|
|
52
|
+
raise RuntimeError(f'{category} 类编号已用尽')
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def task_folder_name(teacher, target_root):
|
|
56
|
+
category = normalize_category(teacher)
|
|
57
|
+
number = find_next_available_number(category, target_root)
|
|
58
|
+
teacher['任务编号'] = number
|
|
59
|
+
name = sanitize_name(teacher['任务名称'])
|
|
29
60
|
return f'{category.lower()}{number}_{name}'
|
|
30
61
|
|
|
31
62
|
|
|
@@ -41,14 +72,19 @@ def main():
|
|
|
41
72
|
if not (source_dir / filename).exists():
|
|
42
73
|
raise FileNotFoundError(f'缺少必需文件: {filename}')
|
|
43
74
|
|
|
44
|
-
|
|
75
|
+
teacher_path = source_dir / '__teacher__.json'
|
|
76
|
+
teacher = load_json(teacher_path)
|
|
45
77
|
target_root.mkdir(parents=True, exist_ok=True)
|
|
46
|
-
|
|
78
|
+
folder_name = task_folder_name(teacher, target_root)
|
|
79
|
+
save_json(teacher_path, teacher)
|
|
80
|
+
|
|
81
|
+
target_dir = target_root / folder_name
|
|
47
82
|
if target_dir.exists():
|
|
48
83
|
raise FileExistsError(f'目标目录已存在: {target_dir}')
|
|
49
84
|
|
|
50
85
|
shutil.move(str(source_dir), str(target_dir))
|
|
51
86
|
print(f'已移动并改名到: {target_dir}')
|
|
87
|
+
print(f'已自动分配编号: {teacher["任务编号"]}')
|
|
52
88
|
|
|
53
89
|
subprocess.run(['python3', str(INDEX_SCRIPT)], check=True)
|
|
54
90
|
print('已自动重建 templates 索引')
|
|
@@ -5,25 +5,22 @@ publish_template.py
|
|
|
5
5
|
发布模板文件夹到七牛 CDN,完成后重建并上传 template-index.json。
|
|
6
6
|
|
|
7
7
|
流程:
|
|
8
|
-
1.
|
|
9
|
-
2.
|
|
10
|
-
3. 上传本地文件到七牛,key = myclaw/live/yiran/skills/yiran-playground-template-use/templates/{文件夹名}/{文件名}
|
|
11
|
-
4. 重写 HTML 中的原引用为新 CDN URL
|
|
12
|
-
5. 上传修改后的 HTML
|
|
13
|
-
6. 重建 template-index.json 并上传到 CDN
|
|
8
|
+
1. 把文件夹内所有文件无脑上传到七牛(强制覆盖)
|
|
9
|
+
2. 重建 template-index.json 并上传到 CDN
|
|
14
10
|
|
|
15
11
|
用法:
|
|
16
12
|
python3 scripts/publish_template.py <模板文件夹路径>
|
|
13
|
+
python3 scripts/publish_template.py --all
|
|
17
14
|
|
|
18
15
|
例:
|
|
19
16
|
python3 scripts/publish_template.py templates/a103_做一个介绍页面
|
|
17
|
+
python3 scripts/publish_template.py --all
|
|
20
18
|
"""
|
|
21
19
|
|
|
22
|
-
import
|
|
20
|
+
import argparse
|
|
23
21
|
import subprocess
|
|
24
22
|
import sys
|
|
25
23
|
from pathlib import Path
|
|
26
|
-
from urllib.parse import urlparse, unquote
|
|
27
24
|
|
|
28
25
|
from qiniu import Auth, put_file_v2, CdnManager
|
|
29
26
|
|
|
@@ -37,119 +34,34 @@ CDN_PREFIX = "myclaw/live/yiran/skills/yiran-playground-template-use/templates
|
|
|
37
34
|
q = Auth(QINIU_KEY, QINIU_SECRET)
|
|
38
35
|
cdn_manager = CdnManager(q)
|
|
39
36
|
|
|
40
|
-
# ── 资源引用正则 ────────────────────────────────────────────────────────────
|
|
41
|
-
# 匹配 src="..." href="..." url(...) 三种形式
|
|
42
|
-
ATTR_RE = re.compile(r'(src|href)=["\']([^"\']+)["\']', re.IGNORECASE)
|
|
43
|
-
URL_RE = re.compile(r'url\(["\']?([^)"\']+)["\']?\)', re.IGNORECASE)
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def is_external(url: str) -> bool:
|
|
50
|
-
return url.startswith('http://') or url.startswith('https://') or url.startswith('//')
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def find_local_file(ref: str, base_dir: Path) -> Path | None:
|
|
54
|
-
"""
|
|
55
|
-
给定一个资源引用,尝试在 base_dir 中找到对应的本地文件。
|
|
56
|
-
- 相对路径 → 直接拼接
|
|
57
|
-
- 外部 URL → 取 path 最后一段文件名,在 base_dir 中查找
|
|
58
|
-
"""
|
|
59
|
-
if any(ref.startswith(p) for p in SKIP_PREFIXES):
|
|
60
|
-
return None
|
|
61
|
-
|
|
62
|
-
if is_external(ref):
|
|
63
|
-
filename = Path(unquote(urlparse(ref).path)).name
|
|
64
|
-
candidate = base_dir / filename
|
|
65
|
-
return candidate if candidate.is_file() else None
|
|
66
|
-
else:
|
|
67
|
-
candidate = (base_dir / ref).resolve()
|
|
68
|
-
# 必须在 base_dir 内,防止路径穿越
|
|
69
|
-
if candidate.is_file() and str(candidate).startswith(str(base_dir.resolve())):
|
|
70
|
-
return candidate
|
|
71
|
-
return None
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def upload_file(local_path: Path, folder_name: str) -> str:
|
|
75
|
-
"""上传单个文件到七牛,返回 CDN URL。"""
|
|
76
|
-
key = f"{CDN_PREFIX}/{folder_name}/{local_path.name}"
|
|
77
|
-
token = q.upload_token(QINIU_BUCKET, key, 3600)
|
|
38
|
+
def upload_file(local_path, folder_name):
|
|
39
|
+
"""上传单个文件到七牛(强制覆盖),返回 CDN URL。"""
|
|
40
|
+
key = "{}/{}/{}".format(CDN_PREFIX, folder_name, local_path.name)
|
|
41
|
+
token = q.upload_token(QINIU_BUCKET, key, 3600, policy={'insertOnly': 0})
|
|
78
42
|
ret, info = put_file_v2(token, key, str(local_path))
|
|
79
|
-
if info.status_code
|
|
80
|
-
raise RuntimeError(
|
|
81
|
-
cdn_url =
|
|
43
|
+
if info.status_code != 200:
|
|
44
|
+
raise RuntimeError("上传失败 {}: {}".format(local_path.name, info))
|
|
45
|
+
cdn_url = "{}/{}".format(CDN_DOMAIN, key)
|
|
82
46
|
cdn_manager.refresh_urls([cdn_url])
|
|
83
47
|
return cdn_url
|
|
84
48
|
|
|
85
49
|
|
|
86
|
-
def
|
|
87
|
-
"""将 HTML 中的旧引用替换为新 CDN URL。"""
|
|
88
|
-
def replace_attr(m):
|
|
89
|
-
attr, val = m.group(1), m.group(2)
|
|
90
|
-
new_val = ref_map.get(val, val)
|
|
91
|
-
return f'{attr}="{new_val}"'
|
|
92
|
-
|
|
93
|
-
def replace_url(m):
|
|
94
|
-
val = m.group(1)
|
|
95
|
-
new_val = ref_map.get(val, val)
|
|
96
|
-
return f'url("{new_val}")'
|
|
97
|
-
|
|
98
|
-
html = ATTR_RE.sub(replace_attr, html)
|
|
99
|
-
html = URL_RE.sub(replace_url, html)
|
|
100
|
-
return html
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def collect_refs(html: str) -> set[str]:
|
|
104
|
-
refs = set()
|
|
105
|
-
for m in ATTR_RE.finditer(html):
|
|
106
|
-
refs.add(m.group(2))
|
|
107
|
-
for m in URL_RE.finditer(html):
|
|
108
|
-
refs.add(m.group(1))
|
|
109
|
-
return refs
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def publish(folder_path: Path):
|
|
50
|
+
def publish(folder_path):
|
|
113
51
|
folder_name = folder_path.name
|
|
114
|
-
|
|
52
|
+
files = [f for f in folder_path.iterdir() if f.is_file()]
|
|
115
53
|
|
|
116
|
-
if not
|
|
117
|
-
print(
|
|
54
|
+
if not files:
|
|
55
|
+
print("⚠️ 文件夹为空:{}".format(folder_path))
|
|
118
56
|
return
|
|
119
57
|
|
|
120
|
-
print(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
for html_file in html_files:
|
|
126
|
-
html = html_file.read_text(encoding='utf-8')
|
|
127
|
-
for ref in collect_refs(html):
|
|
128
|
-
if ref in all_refs:
|
|
129
|
-
continue
|
|
130
|
-
local = find_local_file(ref, folder_path)
|
|
131
|
-
if local:
|
|
132
|
-
print(f" ⬆️ 上传资源: {local.name} ← {ref[:60]}")
|
|
133
|
-
cdn_url = upload_file(local, folder_name)
|
|
134
|
-
all_refs[ref] = cdn_url
|
|
135
|
-
print(f" → {cdn_url}")
|
|
136
|
-
else:
|
|
137
|
-
if not any(ref.startswith(p) for p in SKIP_PREFIXES):
|
|
138
|
-
print(f" ⏭️ 跳过(本地未找到): {ref[:80]}")
|
|
58
|
+
print("📂 发布文件夹:{}".format(folder_name))
|
|
59
|
+
for f in sorted(files):
|
|
60
|
+
cdn_url = upload_file(f, folder_name)
|
|
61
|
+
print(" ⬆️ {} → {}".format(f.name, cdn_url))
|
|
139
62
|
|
|
140
|
-
|
|
141
|
-
for html_file in html_files:
|
|
142
|
-
html = html_file.read_text(encoding='utf-8')
|
|
143
|
-
new_html = rewrite_html(html, all_refs)
|
|
63
|
+
print("\n✅ 发布完成:{}".format(folder_name))
|
|
144
64
|
|
|
145
|
-
# 写回本地(覆盖)
|
|
146
|
-
html_file.write_text(new_html, encoding='utf-8')
|
|
147
|
-
|
|
148
|
-
print(f" ⬆️ 上传 HTML: {html_file.name}")
|
|
149
|
-
cdn_url = upload_file(html_file, folder_name)
|
|
150
|
-
print(f" → {cdn_url}")
|
|
151
|
-
|
|
152
|
-
print(f"\n✅ 发布完成:{folder_name}")
|
|
153
65
|
|
|
154
66
|
|
|
155
67
|
def publish_index(pipeline_root: Path):
|
|
@@ -164,9 +76,9 @@ def publish_index(pipeline_root: Path):
|
|
|
164
76
|
return
|
|
165
77
|
|
|
166
78
|
key = f"{CDN_PREFIX.rsplit('/templates', 1)[0]}/template-index.json"
|
|
167
|
-
token = q.upload_token(QINIU_BUCKET, key, 3600)
|
|
79
|
+
token = q.upload_token(QINIU_BUCKET, key, 3600, policy={'insertOnly': 0})
|
|
168
80
|
ret, info = put_file_v2(token, key, str(index_file))
|
|
169
|
-
if info.status_code
|
|
81
|
+
if info.status_code != 200:
|
|
170
82
|
raise RuntimeError(f"index 上传失败: {info}")
|
|
171
83
|
cdn_url = f"{CDN_DOMAIN}/{key}"
|
|
172
84
|
cdn_manager.refresh_urls([cdn_url])
|
|
@@ -174,18 +86,30 @@ def publish_index(pipeline_root: Path):
|
|
|
174
86
|
|
|
175
87
|
|
|
176
88
|
def main():
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
89
|
+
parser = argparse.ArgumentParser(description='发布模板到七牛 CDN')
|
|
90
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
91
|
+
group.add_argument('folder', nargs='?', help='单个模板文件夹路径')
|
|
92
|
+
group.add_argument('--all', action='store_true', help='发布 templates/ 下所有模板')
|
|
93
|
+
args = parser.parse_args()
|
|
180
94
|
|
|
181
|
-
|
|
182
|
-
if not folder.is_dir():
|
|
183
|
-
print(f"❌ 路径不存在或不是文件夹: {folder}")
|
|
184
|
-
sys.exit(1)
|
|
95
|
+
pipeline_root = Path(__file__).resolve().parent.parent
|
|
185
96
|
|
|
186
|
-
|
|
97
|
+
if args.all:
|
|
98
|
+
templates_dir = pipeline_root / 'templates'
|
|
99
|
+
folders = sorted(d for d in templates_dir.iterdir() if d.is_dir())
|
|
100
|
+
if not folders:
|
|
101
|
+
print("⚠️ templates/ 下没有找到任何模板文件夹")
|
|
102
|
+
sys.exit(0)
|
|
103
|
+
print(f"📦 共 {len(folders)} 个模板,开始全量发布...\n")
|
|
104
|
+
for folder in folders:
|
|
105
|
+
publish(folder)
|
|
106
|
+
else:
|
|
107
|
+
folder = Path(args.folder).expanduser().resolve()
|
|
108
|
+
if not folder.is_dir():
|
|
109
|
+
print(f"❌ 路径不存在或不是文件夹: {folder}")
|
|
110
|
+
sys.exit(1)
|
|
111
|
+
publish(folder)
|
|
187
112
|
|
|
188
|
-
pipeline_root = Path(__file__).resolve().parent.parent
|
|
189
113
|
publish_index(pipeline_root)
|
|
190
114
|
|
|
191
115
|
|