mirror of
https://git.openapi.site/https://github.com/desirecore/market.git
synced 2026-06-06 05:30:39 +08:00
feat: add guizang-ppt market skill (vendored from op7418, AGPL-3.0) (#20)
## 概述 / Summary 新增市场技能 **`guizang-ppt`**(归藏网页 PPT),vendored 自上游开源项目 [op7418/guizang-ppt-skill](https://github.com/op7418/guizang-ppt-skill)。 Add a new **market skill** `guizang-ppt` — generates single-file HTML horizontal-swipe slide decks (web PPT) in two visual systems (editorial "magazine × e-ink" / "Swiss International"). Vendored from the upstream open-source project. ## 变更内容 / Changes - `skills/guizang-ppt/`:SKILL.md(DesireCore frontmatter 覆盖层 + 上游正文)、`references/`(10)、`assets/`(2 模板 + motion.min.js + 9 张 webp)、`scripts/validate-swiss-deck.mjs`、`LICENSE`(AGPL-3.0)、`NOTICE.md`(署名与合规) - `skills/guizang-ppt/_desirecore/`:DesireCore 维护态(不随上游覆盖) - `frontmatter.yaml`:市场 frontmatter 覆盖层(i18n: zh-CN 源 + en-US 显示串,body 暂回退中文,留给 CI 翻译) - `upstream.json`:上游溯源(commit `014c572`、AGPL-3.0、作者 歸藏/op7418) - `scripts/vendor/guizang-ppt.mjs`:可复用的 vendor 更新脚本(`--src <本地路径>` 或 `--ref <tag>`) - `manifest.json`:`version` → `1.2.3`,`stats.totalSkills` → 25 ## 定位 / Positioning - **仅市场可选安装**:未加入 `builtin-skills.json`,不随客户端开机自动安装;用户在市场按需安装。 - 分类 `creative`;与已有 `pptx`(生成 .pptx 文件)区分:本技能生成 **HTML deck**。 ## 许可与署名 / License & Attribution 上游为 **AGPL-3.0**。本技能保留原 `LICENSE` 与作者署名(`NOTICE.md` + `metadata.author` + `market.maintainer`),作为聚合内容分发。 ## 校验 / Validation - `python3 scripts/i18n/validate-i18n.py skills/guizang-ppt` → `OK: no i18n issues found.` ## 手动更新流程 / Manual update 上游发版时:`node scripts/vendor/guizang-ppt.mjs --ref <tag>` → 核对 diff → 必要时 bump `_desirecore/frontmatter.yaml#version` 与 `manifest.json` → 提交。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
146
scripts/vendor/guizang-ppt.mjs
vendored
Normal file
146
scripts/vendor/guizang-ppt.mjs
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Vendor 更新器:把上游 op7418/guizang-ppt-skill 同步进官方市场的 skills/guizang-ppt/。
|
||||
*
|
||||
* 设计目标(“手动可更新”):
|
||||
* - 上游内容(references/ assets/ scripts/ LICENSE + SKILL.md 正文)是“被 vendored”的,可重复覆盖;
|
||||
* - DesireCore 自己维护的元数据放在 skills/guizang-ppt/_desirecore/(frontmatter.yaml 覆盖层 + NOTICE.md),
|
||||
* vendor 时不被上游覆盖;
|
||||
* - 每次同步把上游 commit/版本写进 _desirecore/upstream.json,保证可追溯。
|
||||
*
|
||||
* 用法:
|
||||
* node scripts/vendor/guizang-ppt.mjs --src /path/to/local/guizang-ppt-skill # 复用本地 clone
|
||||
* node scripts/vendor/guizang-ppt.mjs --ref v1.2.0 # 联网 clone 指定 ref
|
||||
* node scripts/vendor/guizang-ppt.mjs # 联网 clone 默认分支
|
||||
*
|
||||
* 同步后仍需人工:核对 diff、必要时 bump _desirecore/frontmatter.yaml#version 与 metadata.updated_at、
|
||||
* 更新根 manifest.json,再 commit / push。
|
||||
*/
|
||||
|
||||
import { execSync } from 'node:child_process'
|
||||
import {
|
||||
existsSync,
|
||||
readFileSync,
|
||||
writeFileSync,
|
||||
rmSync,
|
||||
cpSync,
|
||||
mkdtempSync,
|
||||
} from 'node:fs'
|
||||
import { join, dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { tmpdir } from 'node:os'
|
||||
|
||||
const SKILL_ID = 'guizang-ppt'
|
||||
const UPSTREAM_URL = 'https://github.com/op7418/guizang-ppt-skill.git'
|
||||
const DEFAULT_BRANCH = 'main'
|
||||
// 从上游仓库 vendor 进来的顶层条目(会被每次同步覆盖刷新)
|
||||
const VENDORED_ENTRIES = ['references', 'assets', 'scripts', 'LICENSE']
|
||||
|
||||
const __dir = dirname(fileURLToPath(import.meta.url))
|
||||
const REPO_ROOT = resolve(__dir, '..', '..') // scripts/vendor -> 仓库根
|
||||
const SKILL_DIR = join(REPO_ROOT, 'skills', SKILL_ID)
|
||||
const DESIRE_DIR = join(SKILL_DIR, '_desirecore')
|
||||
const FRONTMATTER_FILE = join(DESIRE_DIR, 'frontmatter.yaml')
|
||||
|
||||
function parseArgs(argv) {
|
||||
const out = { src: null, ref: DEFAULT_BRANCH }
|
||||
for (let i = 0; i < argv.length; i++) {
|
||||
if (argv[i] === '--src') out.src = argv[++i]
|
||||
else if (argv[i] === '--ref') out.ref = argv[++i]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function git(cmd, cwd) {
|
||||
return execSync(`git ${cmd}`, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
|
||||
}
|
||||
|
||||
/** 取得上游源目录与 commit/ref;本地 --src 优先,否则浅克隆。返回 { srcDir, commit, ref, cleanup } */
|
||||
function resolveSource(args) {
|
||||
if (args.src) {
|
||||
const srcDir = resolve(args.src)
|
||||
if (!existsSync(join(srcDir, 'SKILL.md'))) {
|
||||
throw new Error(`--src 指向的目录缺少 SKILL.md:${srcDir}`)
|
||||
}
|
||||
let commit = 'unknown'
|
||||
try {
|
||||
commit = git('rev-parse HEAD', srcDir)
|
||||
} catch {
|
||||
/* 非 git 目录则忽略 */
|
||||
}
|
||||
return { srcDir, commit, ref: args.ref, cleanup: () => {} }
|
||||
}
|
||||
const tmp = mkdtempSync(join(tmpdir(), 'guizang-vendor-'))
|
||||
console.log(`→ 克隆上游 ${UPSTREAM_URL} (ref=${args.ref}) ...`)
|
||||
execSync(`git clone --depth 1 --branch ${args.ref} ${UPSTREAM_URL} ${tmp}`, {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
})
|
||||
const commit = git('rev-parse HEAD', tmp)
|
||||
return { srcDir: tmp, commit, ref: args.ref, cleanup: () => rmSync(tmp, { recursive: true, force: true }) }
|
||||
}
|
||||
|
||||
/** 去掉上游 SKILL.md 的 frontmatter,返回正文 */
|
||||
function stripFrontmatter(text) {
|
||||
const lines = text.split('\n')
|
||||
if (lines[0].trim() !== '---') return text.trim() + '\n'
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (lines[i].trim() === '---') {
|
||||
return lines.slice(i + 1).join('\n').replace(/^\n+/, '')
|
||||
}
|
||||
}
|
||||
return text.trim() + '\n'
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = parseArgs(process.argv.slice(2))
|
||||
|
||||
if (!existsSync(FRONTMATTER_FILE)) {
|
||||
throw new Error(`缺少覆盖层 frontmatter:${FRONTMATTER_FILE}`)
|
||||
}
|
||||
const frontmatter = readFileSync(FRONTMATTER_FILE, 'utf-8').trimEnd()
|
||||
|
||||
const { srcDir, commit, ref, cleanup } = resolveSource(args)
|
||||
try {
|
||||
// 1. 清理旧的 vendored 内容(保留 _desirecore/ 与 NOTICE.md 等维护态)
|
||||
for (const entry of [...VENDORED_ENTRIES, 'SKILL.md']) {
|
||||
rmSync(join(SKILL_DIR, entry), { recursive: true, force: true })
|
||||
}
|
||||
|
||||
// 2. 复制上游 vendored 条目
|
||||
for (const entry of VENDORED_ENTRIES) {
|
||||
const from = join(srcDir, entry)
|
||||
if (existsSync(from)) {
|
||||
cpSync(from, join(SKILL_DIR, entry), { recursive: true })
|
||||
} else {
|
||||
console.warn(` ⚠ 上游缺少 ${entry},跳过`)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 生成 SKILL.md = 覆盖层 frontmatter + 上游正文
|
||||
const upstreamSkill = readFileSync(join(srcDir, 'SKILL.md'), 'utf-8')
|
||||
const body = stripFrontmatter(upstreamSkill)
|
||||
writeFileSync(join(SKILL_DIR, 'SKILL.md'), `${frontmatter}\n\n${body}`)
|
||||
|
||||
// 4. 写溯源信息
|
||||
const upstream = {
|
||||
skillId: SKILL_ID,
|
||||
repoUrl: UPSTREAM_URL,
|
||||
branch: DEFAULT_BRANCH,
|
||||
ref,
|
||||
commit,
|
||||
license: 'AGPL-3.0',
|
||||
author: '歸藏 (op7418)',
|
||||
vendoredAt: new Date().toISOString(),
|
||||
}
|
||||
writeFileSync(join(DESIRE_DIR, 'upstream.json'), `${JSON.stringify(upstream, null, 2)}\n`)
|
||||
|
||||
console.log('✓ vendor 完成')
|
||||
console.log(` upstream commit: ${commit}`)
|
||||
console.log(` skill dir: skills/${SKILL_ID}/`)
|
||||
console.log(' 后续:核对 diff → 必要时 bump _desirecore/frontmatter.yaml#version 与根 manifest.json → commit')
|
||||
} finally {
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user