Skip to content

Commit bf9bc10

Browse files
[intent:O1.7] feat: 添加 GitHub Actions CI + GitHub Pages 自动发布
- 新增 .github/workflows/publish.yml:push 到 main 时自动构建并部署到 gh-pages - 新增 scripts/manifest.ts:生成 sync-manifest.json 到 dist/ - ScriptConfigBase 新增 greasyforkId 字段 - build.ts 自动注入 downloadURL/updateURL(指向 GitHub Pages) - bilibili-block 添加 greasyforkId: 467384 - package.json 新增 manifest 命令
1 parent aeff511 commit bf9bc10

7 files changed

Lines changed: 272 additions & 5 deletions

File tree

.github/workflows/publish.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Build & Publish UserScripts
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- 'src/**'
8+
- 'scripts/**'
9+
- 'package.json'
10+
- 'tsconfig.json'
11+
12+
# 允许手动触发
13+
workflow_dispatch:
14+
15+
permissions:
16+
contents: write
17+
18+
jobs:
19+
build-and-deploy:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
24+
- uses: actions/setup-node@v4
25+
with:
26+
node-version: 20
27+
28+
- run: npm ci
29+
30+
- run: npm run typecheck
31+
32+
- run: npm run build
33+
34+
# 生成同步清单(供调试和 Webhook 使用)
35+
- name: Generate sync manifest
36+
run: npm run manifest
37+
38+
# 部署 dist/ 到 gh-pages 分支
39+
- name: Deploy to GitHub Pages
40+
uses: peaceiris/actions-gh-pages@v4
41+
with:
42+
github_token: ${{ secrets.GITHUB_TOKEN }}
43+
publish_dir: ./dist
44+
# 不保留历史,每次全量替换
45+
keep_files: false

.roo/intent-tree.json

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"58d75c26-af08-4171-bce4-f2a2da01765c",
1515
"44570a55-355b-45dd-90e8-2e4b92ad9246",
1616
"94fde7ec-d937-44f9-9176-3327b82f874f",
17-
"a401200a-d3d3-4609-97fe-e7ce0af87404"
17+
"a401200a-d3d3-4609-97fe-e7ce0af87404",
18+
"8a02e705-67eb-4539-8790-8a35d03bd5e7"
1819
],
1920
"codeBindings": [],
2021
"createdBy": {
@@ -24,7 +25,7 @@
2425
},
2526
"modifiedBy": [],
2627
"assumption": "用户明确要求统一仓库模式:每个脚本一个文件夹,TS 源码,构建输出单一 JS 文件。这是一个全新的目标。",
27-
"maxChildIndex": 6
28+
"maxChildIndex": 7
2829
},
2930
"d4dc577a-4604-4dbb-b024-27342f8c09e2": {
3031
"id": "d4dc577a-4604-4dbb-b024-27342f8c09e2",
@@ -268,7 +269,9 @@
268269
"content": "清理旧版根目录 JS 文件,扩展构建系统支持 plain/node 模式,迁移 Obsidian 和 hexo 项目",
269270
"status": "in_progress",
270271
"parentId": "9fcfb892-37ae-477e-a133-530b729caefd",
271-
"childrenIds": [],
272+
"childrenIds": [
273+
"4a8fae30-29df-4b2f-b5d0-d76954b9ecce"
274+
],
272275
"codeBindings": [],
273276
"createdBy": {
274277
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
@@ -282,7 +285,88 @@
282285
"action": "updated: content, status→in_progress"
283286
}
284287
],
285-
"assumption": "用户要求清理旧脚本和扩展构建系统支持非 Greasyfork 模式,属于 G1 的延续。"
288+
"assumption": "用户要求清理旧脚本和扩展构建系统支持非 Greasyfork 模式,属于 G1 的延续。",
289+
"maxChildIndex": 1
290+
},
291+
"4a8fae30-29df-4b2f-b5d0-d76954b9ecce": {
292+
"id": "4a8fae30-29df-4b2f-b5d0-d76954b9ecce",
293+
"shortId": "I1.6.1",
294+
"type": "impl",
295+
"content": "feat: 清理旧文件,扩展构建系统支持 plain/node 模式,迁移 Obsidian 和 hexo 项目\n\n- 删除已迁移的旧版根目录 JS 文件和 betterIwara/\n- 删除旧版 Obsidian/ 和 hexo-image-redirect/ 目录\n- ScriptConfig 扩展为 discriminated union:userscript | plain | node\n- build.ts 按 mode 分发到不同构建函数\n- 新增 src/obsidian-cardview/(mode: plain,IIFE 无 header)\n- 新增 src/hexo-image-redirect/(mode: node,CJS + external cheerio)\n- 现有 7 个油猴脚本无需任何修改(向后兼容)",
296+
"status": "done",
297+
"parentId": "a401200a-d3d3-4609-97fe-e7ce0af87404",
298+
"childrenIds": [],
299+
"codeBindings": [
300+
{
301+
"commitHash": "aeff5111a436314c4c9b0edcc556be65e5b10649",
302+
"commitMessage": "[intent:O1.6] feat: 清理旧文件,扩展构建系统支持 plain/node 模式,迁移 Obsidian 和 hexo 项目\n\n- 删除已迁移的旧版根目录 JS 文件和 betterIwara/\n- 删除旧版 Obsidian/ 和 hexo-image-redirect/ 目录\n- ScriptConfig 扩展为 discriminated union:userscript | plain | node\n- build.ts 按 mode 分发到不同构建函数\n- 新增 src/obsidian-cardview/(mode: plain,IIFE 无 header)\n- 新增 src/hexo-image-redirect/(mode: node,CJS + external cheerio)\n- 现有 7 个油猴脚本无需任何修改(向后兼容)",
303+
"files": [
304+
".gitignore",
305+
".roo/intent-tree.json",
306+
"AutoTool_DownloadASMR.user.js",
307+
"\"HUST\\345\\215\\216\\344\\270\\255\\347\\247\\221\\346\\212\\200\\345\\244\\247\\345\\255\\246\\345\\206\\233\\347\\220\\206\\347\\272\\277\\344\\270\\212\\344\\275\\234\\344\\270\\232\\347\\256\\200\\346\\230\\223\\350\\207\\252\\345\\212\\250\\345\\241\\253\\345\\205\\205\\345\\212\\251\\346\\211\\213.user.js\"",
308+
"Obsidian/old-cardview.js",
309+
"autoclick.js",
310+
"betterIwara/betterIwara.js",
311+
"hexo-image-redirect/LICENSE",
312+
"hexo-image-redirect/README.md",
313+
"hexo-image-redirect/index.js",
314+
"hexo-image-redirect/package.json",
315+
"scripts/build.ts",
316+
"src/hexo-image-redirect/index.ts",
317+
"src/hexo-image-redirect/meta.ts",
318+
"src/hexo-image-redirect/package.json",
319+
"Obsidian/cardview.js => src/obsidian-cardview/index.ts",
320+
"src/obsidian-cardview/meta.ts",
321+
"src/shared/types.ts",
322+
"\"\\345\\212\\250\\347\\224\\273\\347\\226\\257\\350\\207\\252\\345\\212\\250\\350\\204\\232\\346\\234\\254.js\"",
323+
"\"\\345\\261\\217\\350\\224\\275B\\347\\253\\231\\350\\220\\245\\351\\224\\200\\350\\247\\206\\351\\242\\221\\345\\222\\214\\346\\216\\250\\345\\271\\277\\350\\247\\206\\351\\242\\221.js\"",
324+
"\"\\350\\257\\204\\344\\273\\267\\350\\241\\250\\350\\207\\252\\345\\212\\250\\351\\200\\211\\346\\213\\251.user.js\""
325+
],
326+
"diffSummary": "21 file(s), +553 -1913",
327+
"timestamp": "2026-02-17T13:58:44.119Z"
328+
}
329+
],
330+
"createdBy": {
331+
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
332+
"timestamp": "2026-02-17T13:58:44.119Z",
333+
"action": "created"
334+
},
335+
"modifiedBy": [
336+
{
337+
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
338+
"timestamp": "2026-02-17T13:58:44.119Z",
339+
"action": "bound commit aeff511"
340+
},
341+
{
342+
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
343+
"timestamp": "2026-02-17T13:58:44.119Z",
344+
"action": "updated: status→done"
345+
}
346+
]
347+
},
348+
"8a02e705-67eb-4539-8790-8a35d03bd5e7": {
349+
"id": "8a02e705-67eb-4539-8790-8a35d03bd5e7",
350+
"shortId": "O1.7",
351+
"type": "objective",
352+
"content": "配置 GitHub Actions + GitHub Pages 实现油猴脚本自动发布到 Greasyfork",
353+
"status": "in_progress",
354+
"parentId": "9fcfb892-37ae-477e-a133-530b729caefd",
355+
"childrenIds": [],
356+
"codeBindings": [],
357+
"createdBy": {
358+
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
359+
"timestamp": "2026-02-17T14:11:32.703Z",
360+
"action": "created"
361+
},
362+
"modifiedBy": [
363+
{
364+
"taskId": "019c6bba-5c99-727e-937e-1531e1cf673e",
365+
"timestamp": "2026-02-17T14:11:40.082Z",
366+
"action": "updated: content, status→in_progress"
367+
}
368+
],
369+
"assumption": "用户希望自动发布到 Greasyfork,这是 G1 的延伸目标。"
286370
}
287371
},
288372
"rootIds": [
@@ -296,7 +380,9 @@
296380
"I1.3": "58d75c26-af08-4171-bce4-f2a2da01765c",
297381
"I1.4": "44570a55-355b-45dd-90e8-2e4b92ad9246",
298382
"I1.5": "94fde7ec-d937-44f9-9176-3327b82f874f",
299-
"O1.6": "a401200a-d3d3-4609-97fe-e7ce0af87404"
383+
"O1.6": "a401200a-d3d3-4609-97fe-e7ce0af87404",
384+
"I1.6.1": "4a8fae30-29df-4b2f-b5d0-d76954b9ecce",
385+
"O1.7": "8a02e705-67eb-4539-8790-8a35d03bd5e7"
300386
},
301387
"rootMaxChildIndex": 1
302388
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"scripts": {
88
"build": "tsx scripts/build.ts",
99
"build:watch": "tsx scripts/build.ts --watch",
10+
"manifest": "tsx scripts/manifest.ts",
1011
"typecheck": "tsc --noEmit",
1112
"clean": "rm -rf dist"
1213
},

scripts/build.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const SRC_DIR = path.resolve(import.meta.dirname, '..', 'src');
1414
const DIST_DIR = path.resolve(import.meta.dirname, '..', 'dist');
1515
const SKIP_DIRS = new Set(['shared']);
1616

17+
// GitHub Pages base URL,用于自动注入 downloadURL/updateURL
18+
const PAGES_BASE_URL =
19+
'https://xiaolinxiaozhu.github.io/JavaScriptTools';
20+
1721
// ─── UserScript Header 生成 ───────────────────────────────────
1822

1923
function generateHeader(meta: UserScriptMeta): string {
@@ -177,6 +181,11 @@ async function buildUserScript(entry: ScriptEntry): Promise<void> {
177181
return;
178182
}
179183

184+
// 自动注入 downloadURL/updateURL(如果未手动指定)
185+
const fileUrl = `${PAGES_BASE_URL}/${config.category}/${outputName}.user.js`;
186+
config.meta.downloadURL ??= fileUrl;
187+
config.meta.updateURL ??= fileUrl;
188+
180189
const header = generateHeader(config.meta);
181190

182191
await esbuild.build({

scripts/manifest.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
* 生成 sync-manifest.json 到 dist/ 目录
3+
* 包含所有脚本的发布信息,供 CI 和调试使用
4+
*/
5+
import * as esbuild from 'esbuild';
6+
import * as fs from 'node:fs';
7+
import * as path from 'node:path';
8+
import { pathToFileURL } from 'node:url';
9+
import type { ScriptConfig, UserScriptConfig } from '../src/shared/types';
10+
11+
const SRC_DIR = path.resolve(import.meta.dirname, '..', 'src');
12+
const DIST_DIR = path.resolve(import.meta.dirname, '..', 'dist');
13+
const SKIP_DIRS = new Set(['shared']);
14+
15+
// GitHub Pages base URL(需要根据实际仓库名调整)
16+
const PAGES_BASE_URL =
17+
'https://xiaolinxiaozhu.github.io/JavaScriptTools';
18+
19+
interface ManifestEntry {
20+
name: string;
21+
mode: string;
22+
category: string;
23+
distPath: string;
24+
pagesUrl: string;
25+
version?: string;
26+
greasyforkId?: number;
27+
greasyforkUrl?: string;
28+
}
29+
30+
async function loadMetaConfig(metaFile: string): Promise<ScriptConfig> {
31+
const result = await esbuild.build({
32+
entryPoints: [metaFile],
33+
bundle: true,
34+
format: 'esm',
35+
platform: 'node',
36+
write: false,
37+
outdir: 'out',
38+
});
39+
40+
const code = result.outputFiles[0].text;
41+
const tmpFile = metaFile.replace(/\.ts$/, '.meta.tmp.mjs');
42+
fs.writeFileSync(tmpFile, code);
43+
try {
44+
const mod = await import(pathToFileURL(tmpFile).href);
45+
return mod.default as ScriptConfig;
46+
} finally {
47+
fs.unlinkSync(tmpFile);
48+
}
49+
}
50+
51+
async function main(): Promise<void> {
52+
const manifest: ManifestEntry[] = [];
53+
const dirs = fs.readdirSync(SRC_DIR, { withFileTypes: true });
54+
55+
for (const dirent of dirs) {
56+
if (!dirent.isDirectory()) continue;
57+
if (SKIP_DIRS.has(dirent.name)) continue;
58+
59+
const metaFile = path.join(SRC_DIR, dirent.name, 'meta.ts');
60+
if (!fs.existsSync(metaFile)) continue;
61+
62+
const config = await loadMetaConfig(metaFile);
63+
const mode = config.mode ?? 'userscript';
64+
const outputName = config.outputName ?? dirent.name;
65+
66+
let distFile: string;
67+
if (mode === 'userscript') {
68+
distFile = `${config.category}/${outputName}.user.js`;
69+
} else if (mode === 'node') {
70+
const fmt = (config as { format?: string }).format ?? 'cjs';
71+
const ext = fmt === 'esm' ? '.mjs' : '.js';
72+
distFile = `${config.category}/${outputName}${ext}`;
73+
} else {
74+
const fmt = (config as { format?: string }).format ?? 'iife';
75+
const ext = fmt === 'esm' ? '.mjs' : '.js';
76+
distFile = `${config.category}/${outputName}${ext}`;
77+
}
78+
79+
const entry: ManifestEntry = {
80+
name: dirent.name,
81+
mode,
82+
category: config.category,
83+
distPath: distFile,
84+
pagesUrl: `${PAGES_BASE_URL}/${distFile}`,
85+
};
86+
87+
if (mode === 'userscript') {
88+
const usc = config as UserScriptConfig;
89+
const meta = usc.meta;
90+
entry.version =
91+
typeof meta.version === 'string' ? meta.version : undefined;
92+
}
93+
94+
if (config.greasyforkId) {
95+
entry.greasyforkId = config.greasyforkId;
96+
entry.greasyforkUrl = `https://greasyfork.org/scripts/${config.greasyforkId}`;
97+
}
98+
99+
manifest.push(entry);
100+
}
101+
102+
// 确保 dist 目录存在
103+
if (!fs.existsSync(DIST_DIR)) {
104+
fs.mkdirSync(DIST_DIR, { recursive: true });
105+
}
106+
107+
const outputPath = path.join(DIST_DIR, 'sync-manifest.json');
108+
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
109+
console.log(`📋 生成 sync-manifest.json(${manifest.length} 个脚本)`);
110+
111+
// 打印摘要
112+
for (const entry of manifest) {
113+
const gf = entry.greasyforkId
114+
? ` → GF#${entry.greasyforkId}`
115+
: '';
116+
console.log(` ${entry.name} [${entry.mode}] ${entry.pagesUrl}${gf}`);
117+
}
118+
}
119+
120+
main().catch((err) => {
121+
console.error('生成 manifest 失败:', err);
122+
process.exit(1);
123+
});

src/bilibili-block/meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ export default defineConfig({
3232
'https://update.greasyfork.org/scripts/467384/%F0%9F%9B%A0%EF%B8%8F%E5%B1%8F%E8%94%BDB%E7%AB%99%E8%90%A5%E9%94%80%E8%A7%86%E9%A2%91.meta.js',
3333
},
3434
category: 'bilibili',
35+
greasyforkId: 467384,
3536
});

src/shared/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ interface ScriptConfigBase {
3939
outputName?: string;
4040
/** 入口文件路径(相对于脚本文件夹),默认 index.ts */
4141
entry?: string;
42+
/** Greasyfork 脚本 ID,用于自动同步触发(仅 userscript 模式需要) */
43+
greasyforkId?: number;
4244
}
4345

4446
// ─── 油猴脚本模式(默认) ──────────────────────

0 commit comments

Comments
 (0)