@code2rich/jpage 1.5.0 → 1.5.2
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/.github/workflows/ci.yml +3 -1
- package/.github/workflows/release.yml +87 -0
- package/CLAUDE.md +3 -1
- package/README.md +26 -2
- package/bin/commands/ls.js +6 -3
- package/bin/commands/update.js +74 -0
- package/bin/jpage.js +7 -2
- package/docs/RELEASING.md +209 -0
- package/docs/skill-integration-design.md +384 -0
- package/eslint.config.mjs +2 -0
- package/lib/csp.js +8 -2
- package/lib/render.js +9 -2
- package/lib/templates.js +1 -1
- package/mcp/tools-files.js +5 -1
- package/package.json +4 -4
- package/public/css/style.css +128 -1
- package/public/index.html +51 -3
- package/public/js/app.js +8 -6
- package/public/js/pages/content-templates.js +1 -1
- package/public/js/pages/home.js +218 -9
- package/public/js/pages/landing.js +1 -1
- package/public/js/pages/preview.js +1 -1
- package/public/js/utils.js +15 -7
- package/routes/skills.js +77 -3
- package/server.js +10 -3
- package/skills/jpage-presentation/INSTALL.md +50 -0
- package/skills/jpage-presentation/README.md +71 -0
- package/skills/jpage-presentation/SKILL.md +226 -0
- package/skills/jpage-presentation/assets/plugin/highlight/monokai.css +71 -0
- package/skills/jpage-presentation/assets/plugin/highlight/plugin.js +439 -0
- package/skills/jpage-presentation/assets/plugin/notes/notes.js +1 -0
- package/skills/jpage-presentation/assets/reveal-base.css +9 -0
- package/skills/jpage-presentation/assets/reveal.js +9 -0
- package/skills/jpage-presentation/assets/themes/academic.css +68 -0
- package/skills/jpage-presentation/assets/themes/business.css +64 -0
- package/skills/jpage-presentation/assets/themes/creative.css +81 -0
- package/skills/jpage-presentation/assets/themes/minimal.css +117 -0
- package/skills-registry.js +0 -6
- package/test/dispatch-bench.js +0 -3
- package/test/integration/cli.test.js +93 -0
- package/test/integration/skills.test.js +27 -5
- package/test/perf-harness.js +0 -9
- package/test/unit/fts.test.js +0 -1
- package/.claude/settings.local.json +0 -68
package/.github/workflows/ci.yml
CHANGED
|
@@ -28,7 +28,9 @@ jobs:
|
|
|
28
28
|
uses: actions/setup-node@v4
|
|
29
29
|
with:
|
|
30
30
|
node-version: ${{ matrix.node-version }}
|
|
31
|
-
cache: 'npm'
|
|
31
|
+
# 不启用 cache: 'npm':setup-node 恢复缓存时 _cacache/tmp 残留与
|
|
32
|
+
# npm ci 冲突(EEXIST errno -17),Node 20 矩阵稳定复现。
|
|
33
|
+
# npm ci 本身够快(~10s),缓存收益不值得这个 bug。
|
|
32
34
|
|
|
33
35
|
- name: Install dependencies
|
|
34
36
|
run: npm ci
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# npm 自动发版:打 v* tag 触发,跑测试 + 校验版本号一致 + npm publish。
|
|
2
|
+
#
|
|
3
|
+
# 用法(本地):
|
|
4
|
+
# 1. 改 package.json 的 version(如 1.5.1)
|
|
5
|
+
# 2. commit + push 到 main
|
|
6
|
+
# 3. git tag v1.5.1 && git push origin v1.5.1
|
|
7
|
+
# 4. 本 workflow 自动跑:test → 校验 tag==version → npm publish
|
|
8
|
+
#
|
|
9
|
+
# 前置(仓库设置,一次性):
|
|
10
|
+
# GitHub 仓库 Settings → Secrets and variables → Actions → New repository secret
|
|
11
|
+
# Name: NPM_TOKEN
|
|
12
|
+
# Value: 在 npm 生成的 granular access token(@code2rich scope, Read and write)
|
|
13
|
+
# 生成地址:https://www.npmjs.com/settings/code2richnpm/tokens/granular-access-tokens/new
|
|
14
|
+
#
|
|
15
|
+
# 版本一致性:tag 名(去 v 前缀)必须等于 package.json 的 version,否则失败。
|
|
16
|
+
# 这样能防止「tag 标 v1.6.0 但 package.json 还写着 1.5.0」这类手滑。
|
|
17
|
+
|
|
18
|
+
name: Release
|
|
19
|
+
|
|
20
|
+
on:
|
|
21
|
+
push:
|
|
22
|
+
tags: ['v*']
|
|
23
|
+
|
|
24
|
+
# 同一 tag 不要并发跑(理论上 git push tag 是原子的,但保险起见)
|
|
25
|
+
concurrency:
|
|
26
|
+
group: release-${{ github.ref }}
|
|
27
|
+
cancel-in-progress: false
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
release:
|
|
31
|
+
name: Publish to npm
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
# 同时跑测试矩阵确认绿(轻量:只 Node 20,CI workflow 已覆盖 20+22)
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
|
|
37
|
+
- name: Setup Node.js
|
|
38
|
+
uses: actions/setup-node@v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: '20'
|
|
41
|
+
# 不启用 cache: 'npm':见 ci.yml 同款注释(EEXIST bug)
|
|
42
|
+
registry-url: 'https://registry.npmjs.org'
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: npm ci
|
|
46
|
+
|
|
47
|
+
- name: Lint
|
|
48
|
+
run: npm run lint
|
|
49
|
+
|
|
50
|
+
- name: Run tests (unit + integration)
|
|
51
|
+
run: npm test
|
|
52
|
+
|
|
53
|
+
- name: Build frontend bundle
|
|
54
|
+
run: npm run build
|
|
55
|
+
|
|
56
|
+
- name: Verify tag matches package version
|
|
57
|
+
run: |
|
|
58
|
+
TAG="${GITHUB_REF_NAME#v}"
|
|
59
|
+
PKG=$(node -p "require('./package.json').version")
|
|
60
|
+
echo "tag (去 v 前缀): $TAG"
|
|
61
|
+
echo "package.json version: $PKG"
|
|
62
|
+
if [ "$TAG" != "$PKG" ]; then
|
|
63
|
+
echo "::error::tag ($TAG) 与 package.json version ($PKG) 不一致"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
- name: Publish to npm
|
|
68
|
+
run: npm publish
|
|
69
|
+
env:
|
|
70
|
+
# setup-node 配置的 registry 会读这个环境变量做鉴权
|
|
71
|
+
# publishConfig.access=public 已设,scoped 包公开发布无需 --access
|
|
72
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
73
|
+
|
|
74
|
+
- name: Summary
|
|
75
|
+
if: always()
|
|
76
|
+
run: |
|
|
77
|
+
echo "## Release ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
|
78
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
79
|
+
if [ "${{ job.status }}" = "success" ]; then
|
|
80
|
+
echo "✅ 已发布 \`$(node -p "require('./package.json').name")@$(node -p "require('./package.json').version")\`" >> $GITHUB_STEP_SUMMARY
|
|
81
|
+
echo "" >> $GITHUB_STEP_SUMMARY
|
|
82
|
+
echo "安装:\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
|
83
|
+
echo "npm install -g $(node -p "require('./package.json').name")" >> $GITHUB_STEP_SUMMARY
|
|
84
|
+
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
|
85
|
+
else
|
|
86
|
+
echo "❌ 发布失败,查看上面日志" >> $GITHUB_STEP_SUMMARY
|
|
87
|
+
fi
|
package/CLAUDE.md
CHANGED
|
@@ -133,7 +133,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
133
133
|
- `GET /api/skills` — 列出已安装的 skill 包(需登录)
|
|
134
134
|
- `GET /api/skills/:name` — skill 详情(含 SKILL.md 内容、文件列表、INSTALL.md 渲染)
|
|
135
135
|
- `GET /api/skills/:name/download` — ZIP 下载整个 skill 目录
|
|
136
|
-
- `GET /api/mcp/config` — 返回 MCP 连接配置(URL、Token
|
|
136
|
+
- `GET /api/mcp/config` — 返回 MCP 连接配置(URL、Token 列表、多客户端 mcpServers JSON 片段;仅 MCP 客户端)
|
|
137
|
+
- `GET /api/cli/guide` — 返回 `jpage` CLI 用法指南(baseUrl、渲染后的 guideHtml、纯文本 guideText);CLI 与 MCP 是并列的两个客户端入口,各自独立端点
|
|
137
138
|
|
|
138
139
|
**Skills registry** — `skills-registry.js` 自动发现 `skills/*/SKILL.md`,解析 YAML frontmatter(`name`, `description`, `version`, `author`)。Web UI 首页展示 Skills 区块,管理员可查看详情(弹窗)和下载 ZIP。ZIP 包与磁盘目录结构一致,可直接解压到 `~/.claude/skills/`。
|
|
139
140
|
|
|
@@ -168,6 +169,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
168
169
|
- **大 body 端点专用解析** — 全局 `express.json` 限 1MB;`upload-json` / `upload-zip-base64` / `overwrite-json` 用 `largeJson`(50MB)。新增大 body 端点时挂 `largeJson`。
|
|
169
170
|
- **静态资源长缓存** — `express.static` 统一带 `STATIC_OPTS`(30d + immutable)。前端 CSS/JS 引用带 `?v=x.y.z`,改资源后务必 bump 版本号以失效缓存。
|
|
170
171
|
- **MCP 进程内分发** — MCP tool 不再走 `fetch('http://127.0.0.1:port/...')` 自调用,改用 `lib/dispatch.js` 的 `createDispatcher(app, {token})` 直接调 `app.handle()`。新增 MCP tool 时用传入的 `api` 对象(`api.get/post/put/del`),接口与 fetch 版一致;鉴权靠 `Authorization: Bearer <token>` 头走 `requireAuth`,行为与 HTTP 完全相同(权限、限流、审计都生效)。
|
|
172
|
+
- **CLI 与 MCP 双客户端入口必须同步** — `bin/commands/*.js`(CLI 命令)与 `mcp/tools-*.js`(MCP tool)是两个对等的客户端入口,都构建在同一套 REST API(`routes/`)之上。**新增或修改任一端的功能时,另一端必须同步实现对应能力**:CLI 加命令就同时给 MCP 加 tool,反之亦然,避免两边能力漂移。功能域与文件对应:文件管理 → `commands/{upload,ls,cat,url,mv,rm,star,tags}.js` ↔ `mcp/tools-files.js`;标签 → `commands/tags.js` ↔ `mcp/tools-tags.js`;版本 → `mcp/tools-versions.js`(CLI 待补);分类 → `mcp/tools-categories.js`(CLI 待补)。
|
|
171
173
|
- **模板预编译** — `loadTemplates()` 把每个模板经 `compileTemplate()` 编为函数存入 `templateCache`(静态 vendor URL 占位符在加载时一次替换,运行时仅 title/content/hljs_theme 三个 `split/join`)。`templateCache[name]` 是**函数**而非字符串,`applyTemplate(tplFn, ...)` 直接调用。
|
|
172
174
|
- **marked.parse 同步** — 所有 `marked.parse(...)` 显式带 `async: false`(v12 默认同步,显式声明防止未来升级开启 async 返回 Promise 被当字符串拼接)。
|
|
173
175
|
- **搜索 UNION 合并** — `/api/files/search` 用 `UNION` 合并 FTS 全文命中(带 `snippet`)与文件名 LIKE 命中(snippet 为 NULL),一次往返替代旧的两查询+内存去重,分页准确。注意 FTS5 的 `MATCH` 不能与普通列在 `LEFT JOIN + OR` 中混用(SQLite 报 "unable to use function MATCH")。
|
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
[English](README_EN.md) | 中文
|
|
8
8
|
|
|
9
|
-
**[>>> 查看即页产品介绍 <<<](https://jpage.cn/)**
|
|
10
|
-
|
|
11
9
|
**即页**是一个零配置的 HTML / Markdown 即时预览与分享工具。把写好的文档拖进来,立刻获得一个干净的在线页面——无需部署流程,无需服务器知识。特别适合 AI 生成内容的一键分享。
|
|
12
10
|
|
|
13
11
|
---
|
|
@@ -105,6 +103,32 @@ node test/mcp-harness.js 8858 # MCP 端点
|
|
|
105
103
|
node test/perf-bench.js 8858 # 渲染/列表/缓存延迟基准
|
|
106
104
|
```
|
|
107
105
|
|
|
106
|
+
### CLI 工具(npm 包已发布)
|
|
107
|
+
|
|
108
|
+
即页随包提供 `jpage` 命令行工具,可通过 REST API 上传 / 列出 / 管理文件,对大文件和 ZIP 走 multipart 二进制流式上传(比 MCP 的 base64 进 token 流更快更省):
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install -g @code2rich/jpage
|
|
112
|
+
jpage upload ./report.html --public --token <你的 token>
|
|
113
|
+
jpage ls --kw 季度
|
|
114
|
+
jpage cat 8
|
|
115
|
+
jpage --help
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`jpage` 与 MCP 是对称的两个客户端入口,都架在同一套 REST API 之上。详见 `jpage --help`。
|
|
119
|
+
|
|
120
|
+
更新到最新版(不需 token):
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
jpage update # 自更新到最新版
|
|
124
|
+
jpage update --check # 只查有没有新版本
|
|
125
|
+
jpage update --registry https://registry.npmmirror.com # 国内源
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 发版
|
|
129
|
+
|
|
130
|
+
维护者发版指南(含 GitHub Actions 自动发版配置、token 轮换、故障排查)见 [`docs/RELEASING.md`](docs/RELEASING.md)。
|
|
131
|
+
|
|
108
132
|
## 鉴权与安全
|
|
109
133
|
|
|
110
134
|
即页支持多用户体系。admin 可管理全部用户和文件,普通用户只能操作自己的文件和公开文件。
|
package/bin/commands/ls.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// ls 命令:列出文件。
|
|
2
2
|
// 后端:GET /api/files(支持 page/limit/sort/order/keyword/category/tag)。
|
|
3
3
|
|
|
4
|
-
const { formatSize, formatTime, out } = require('./_shared');
|
|
4
|
+
const { formatSize, formatTime, shareUrl, out } = require('./_shared');
|
|
5
5
|
|
|
6
|
-
async function run(client, args) {
|
|
6
|
+
async function run(client, args, { base }) {
|
|
7
7
|
const o = args.opts;
|
|
8
8
|
const params = new URLSearchParams();
|
|
9
9
|
if (o.page) params.set('page', o.page);
|
|
@@ -24,14 +24,17 @@ async function run(client, args) {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// 对齐表格:id / 类型 / 公开 / 大小 / 更新时间 / 文件名 / 标签
|
|
27
|
+
// 对齐表格:id / 类型 / 公开 / 大小 / 更新时间 / 文件名 / 短链 / 标签
|
|
28
28
|
for (const f of files) {
|
|
29
29
|
const pub = f.is_public ? 'pub' : 'pri';
|
|
30
30
|
const tags = (f.tags || []).map((t) => t.name).join(',');
|
|
31
31
|
const bundle = f.is_bundle ? ' 📦' : '';
|
|
32
|
+
const url = shareUrl(base, f);
|
|
33
|
+
const short = url ? ` ${url}` : '';
|
|
32
34
|
out(
|
|
33
35
|
`#${f.id} [${f.file_type || '?'} ${pub}] ${formatSize(f.size).padEnd(7)} ` +
|
|
34
36
|
`${formatTime(f.updated_at)} ${f.original_name}${bundle}` +
|
|
37
|
+
short +
|
|
35
38
|
(tags ? ` {${tags}}` : '') +
|
|
36
39
|
'\n'
|
|
37
40
|
);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// update 命令:把 jpage 自更新到最新版(npm 全局包)。
|
|
2
|
+
//
|
|
3
|
+
// 纯客户端操作:不调后端 API,不需 token(自更新与 jpage 服务端无关)。
|
|
4
|
+
// 流程:npm view 查最新版本 → 与本地对比 → 有新版则 npm install -g 重装。
|
|
5
|
+
//
|
|
6
|
+
// 可注入 npmExec(形如 (args) => string):测试时注入假执行器,避免真的跑 npm。
|
|
7
|
+
// 默认走 child_process.execFileSync,与 build.js 既有风格一致。
|
|
8
|
+
|
|
9
|
+
const { execFileSync } = require('child_process');
|
|
10
|
+
const { out, err } = require('./_shared');
|
|
11
|
+
|
|
12
|
+
const PKG_NAME = '@code2rich/jpage';
|
|
13
|
+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
14
|
+
|
|
15
|
+
// 默认 npm 执行器:同步拿 stdout(trim 尾部换行)。
|
|
16
|
+
function defaultNpmExec(args) {
|
|
17
|
+
return execFileSync(npmBin, args, {
|
|
18
|
+
encoding: 'utf-8',
|
|
19
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
20
|
+
}).trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function run(_client, args, ctx) {
|
|
24
|
+
const o = args.opts;
|
|
25
|
+
const exit = ctx.exit || ((c) => { process.exitCode = c; });
|
|
26
|
+
const npmExec = ctx.npmExec || defaultNpmExec;
|
|
27
|
+
|
|
28
|
+
// --registry 后面没给值时,args.js 会把它解析成 true。
|
|
29
|
+
if (o.registry === true) {
|
|
30
|
+
const e = new Error('用法:jpage update [--registry <url>] [--check]');
|
|
31
|
+
e.name = 'UsageError';
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
34
|
+
const registryArgs = o.registry ? ['--registry', o.registry] : [];
|
|
35
|
+
|
|
36
|
+
const current = require('../../package.json').version;
|
|
37
|
+
|
|
38
|
+
// 查最新版本。
|
|
39
|
+
let latest;
|
|
40
|
+
try {
|
|
41
|
+
latest = (npmExec(['view', PKG_NAME, 'version', ...registryArgs]) || '').trim();
|
|
42
|
+
} catch (e) {
|
|
43
|
+
err(`✗ 查询最新版本失败:${e.message || e}\n`);
|
|
44
|
+
err(' 检查网络连接,或用 --registry 指定可达的 npm 源。\n');
|
|
45
|
+
exit(1);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (latest === current) {
|
|
50
|
+
out(`已是最新版 ${current}\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
out(`发现新版本 ${latest}(当前 ${current}),正在更新…\n`);
|
|
55
|
+
|
|
56
|
+
if (o.check) {
|
|
57
|
+
return; // 只查不更新
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
npmExec(['install', '-g', `${PKG_NAME}@latest`, ...registryArgs]);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
const detail = (e.stderr || e.message || e).toString().split('\n').slice(0, 3).join('\n');
|
|
64
|
+
err(`✗ 更新失败:\n${detail}\n`);
|
|
65
|
+
err(' 常见原因:权限不足(试 sudo)、网络中断、registry 不可达。\n');
|
|
66
|
+
err(' 也可手动执行:npm install -g ' + PKG_NAME + '@latest\n');
|
|
67
|
+
exit(1);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
out(`✓ 已更新到 ${latest},重新运行 jpage 生效\n`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { run };
|
package/bin/jpage.js
CHANGED
|
@@ -31,8 +31,12 @@ const COMMANDS = {
|
|
|
31
31
|
tags: () => require('./commands/tags'),
|
|
32
32
|
skills: () => require('./commands/skills'),
|
|
33
33
|
whoami: () => require('./commands/whoami'),
|
|
34
|
+
update: () => require('./commands/update'),
|
|
34
35
|
};
|
|
35
36
|
|
|
37
|
+
// 这些命令纯本地执行(不调后端 API),不强制要求 token。
|
|
38
|
+
const NO_TOKEN = new Set(['update']);
|
|
39
|
+
|
|
36
40
|
const HELP = `jpage —— 即页命令行
|
|
37
41
|
|
|
38
42
|
用法:
|
|
@@ -51,6 +55,7 @@ const HELP = `jpage —— 即页命令行
|
|
|
51
55
|
skills ls | get <名> | download <名> [--out 文件]
|
|
52
56
|
列出 / 查看 / 下载 Skill
|
|
53
57
|
whoami 校验 token 是否有效
|
|
58
|
+
update [--registry <url>] [--check] 自更新到最新版(不需 token)
|
|
54
59
|
|
|
55
60
|
通用选项:
|
|
56
61
|
--token <TOKEN> 鉴权 token(jp_ 用户 token 或 MCP_TOKEN)
|
|
@@ -108,7 +113,7 @@ async function run(argv, inject = {}) {
|
|
|
108
113
|
}
|
|
109
114
|
|
|
110
115
|
const { token, base } = resolveConfig(opts, inject.env, inject.cwd);
|
|
111
|
-
if (!token) {
|
|
116
|
+
if (!token && !NO_TOKEN.has(cmd)) {
|
|
112
117
|
stderr.write(
|
|
113
118
|
'未提供 token。用 --token <TOKEN>、JPAGE_TOKEN 环境变量、或 .env 的 MCP_TOKEN 设置。\n'
|
|
114
119
|
);
|
|
@@ -117,7 +122,7 @@ async function run(argv, inject = {}) {
|
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
const client = createClient({ base, token, fetchImpl: inject.fetchImpl });
|
|
120
|
-
const ctx = { base, token, exit };
|
|
125
|
+
const ctx = { base, token, exit, npmExec: inject.npmExec };
|
|
121
126
|
const mod = COMMANDS[cmd]();
|
|
122
127
|
|
|
123
128
|
try {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# 发版指南
|
|
2
|
+
|
|
3
|
+
> 维护者文档。记录如何把 `@code2rich/jpage` 发布到 npm,以及 GitHub Actions 自动发版的配置与一次性设置。
|
|
4
|
+
|
|
5
|
+
## TL;DR
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm version patch # 1.5.0 → 1.5.1(自动改 package.json + commit + 打 tag)
|
|
9
|
+
git push origin main # 推 commit
|
|
10
|
+
git push origin v1.5.1 # 推 tag → 触发 GitHub Actions 自动发布
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
打完 tag 等 Actions 跑完(2-3 分钟),`npm view @code2rich/jpage version` 确认。
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 前置(一次性配置)
|
|
18
|
+
|
|
19
|
+
### 1. 生成 npm granular access token
|
|
20
|
+
|
|
21
|
+
专为 CI 用的 token,不要复用你本地的或之前的。
|
|
22
|
+
|
|
23
|
+
1. 访问 `https://www.npmjs.com/settings/code2richnpm/tokens/granular-access-tokens/new`
|
|
24
|
+
2. 按以下填:
|
|
25
|
+
- **Name**: `jpage-ci-publish`(或任意易识别的名字)
|
|
26
|
+
- **Expiration**: 建议 90 天(到期前轮换)
|
|
27
|
+
- **Packages and scopes**: `Only select packages and scopes` → 勾 `@code2rich`
|
|
28
|
+
- 包已存在时可选精确到 `@code2rich/jpage`,权限更小更安全
|
|
29
|
+
- **Permissions**: `Read and write`
|
|
30
|
+
3. 生成后复制 `npm_xxxx...`(**只显示一次**)
|
|
31
|
+
|
|
32
|
+
> granular token 在生成时已勾选 bypass 2FA,CI 发布无需 OTP。
|
|
33
|
+
|
|
34
|
+
### 2. 把 token 存进 GitHub Secrets
|
|
35
|
+
|
|
36
|
+
1. 访问 `https://github.com/code2rich/jpage/settings/secrets/actions`
|
|
37
|
+
2. **New repository secret**
|
|
38
|
+
- Name: `NPM_TOKEN`
|
|
39
|
+
- Value: 上一步复制的 token
|
|
40
|
+
3. Add secret
|
|
41
|
+
|
|
42
|
+
token 从此只存在 GitHub Secrets,不出现在本地终端、代码、`.npmrc`。
|
|
43
|
+
|
|
44
|
+
### 3.(强烈建议)给 npm 账号开 2FA
|
|
45
|
+
|
|
46
|
+
访问 `https://www.npmjs.com/settings/code2richnpm/account/security` → 选 `auth-and-writes`。
|
|
47
|
+
|
|
48
|
+
- 2FA 保护**账号登录态**(防账号被盗后乱改设置)
|
|
49
|
+
- granular token 的 bypass 2FA 只针对**发布动作**,两者互补不冲突
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 发版流程
|
|
54
|
+
|
|
55
|
+
### 常规发版(推荐:自动 CI)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 1. 确认在 main 分支且工作区干净
|
|
59
|
+
git checkout main
|
|
60
|
+
git status
|
|
61
|
+
|
|
62
|
+
# 2. 升版本号(npm version 会自动改 package.json + commit + 打 tag)
|
|
63
|
+
npm version patch # 1.5.0 → 1.5.1 修 bug
|
|
64
|
+
# npm version minor # 1.5.0 → 1.6.0 新功能
|
|
65
|
+
# npm version major # 1.5.0 → 2.0.0 破坏性变更
|
|
66
|
+
|
|
67
|
+
# 3. 推送 commit 和 tag
|
|
68
|
+
git push origin main
|
|
69
|
+
git push origin v1.5.1
|
|
70
|
+
|
|
71
|
+
# 4. 等 GitHub Actions 跑完
|
|
72
|
+
# 进度:https://github.com/code2rich/jpage/actions
|
|
73
|
+
# 成功后验证:
|
|
74
|
+
npm view @code2rich/jpage version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**CI 会做什么**(见 `.github/workflows/release.yml`):
|
|
78
|
+
|
|
79
|
+
1. checkout 代码
|
|
80
|
+
2. `npm ci`(用 lockfile 锁定的依赖)
|
|
81
|
+
3. lint + test(全绿才继续)
|
|
82
|
+
4. build 前端产物
|
|
83
|
+
5. **校验 tag 名 == package.json version**(防手滑:tag 标 v1.6.0 但 package.json 还写 1.5.0 会失败)
|
|
84
|
+
6. `npm publish`(用 `NPM_TOKEN` 鉴权)
|
|
85
|
+
7. 在 Actions Summary 写发布结果
|
|
86
|
+
|
|
87
|
+
### 手动发版(应急,CI 挂了时用)
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 1. 确认版本号已改、commit 已推
|
|
91
|
+
npm version patch
|
|
92
|
+
git push origin main
|
|
93
|
+
git push origin v1.5.1
|
|
94
|
+
|
|
95
|
+
# 2. 本地登录(首次需要)
|
|
96
|
+
npm login
|
|
97
|
+
|
|
98
|
+
# 3. 发布(账号开了 2FA 时需要 --otp)
|
|
99
|
+
npm publish --otp=<authenticator 当前的 6 位>
|
|
100
|
+
|
|
101
|
+
# 4. 验证
|
|
102
|
+
npm view @code2rich/jpage version
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
> 手动发版后建议补打 tag(如果 `npm version` 已打就不用),保持「每个 npm 版本对应一个 git tag」。
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 版本号约定(语义化版本)
|
|
110
|
+
|
|
111
|
+
| 改动类型 | 命令 | 例子 | 含义 |
|
|
112
|
+
|---|---|---|---|
|
|
113
|
+
| 修 bug、小优化 | `npm version patch` | 1.5.0 → 1.5.1 | 向后兼容的修复 |
|
|
114
|
+
| 新功能 | `npm version minor` | 1.5.0 → 1.6.0 | 向后兼容的新能力 |
|
|
115
|
+
| 破坏性变更 | `npm version major` | 1.5.0 → 2.0.0 | 不兼容旧版的改动 |
|
|
116
|
+
|
|
117
|
+
> 重大变更应同步更新 `CHANGELOG`(如果有)或在 GitHub Release 写说明。
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 预发版(可选,beta/rc)
|
|
122
|
+
|
|
123
|
+
npm 支持预发布版本号,如 `1.6.0-beta.1`:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm version prerelease --preid=beta # 1.5.0 → 1.6.0-beta.0
|
|
127
|
+
git push origin main
|
|
128
|
+
git push origin v1.6.0-beta.0
|
|
129
|
+
|
|
130
|
+
# 用户安装 beta:npm install -g @code2rich/jpage@beta
|
|
131
|
+
# 正式版:npm install -g @code2rich/jpage@latest(默认)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
预发版默认不进 `latest` tag,用户不主动指定 `@beta` 不会装到。
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 回滚 / 撤回
|
|
139
|
+
|
|
140
|
+
**npm 版本一旦发布,版本号永久占用,无法删除**(只能 deprecate)。
|
|
141
|
+
|
|
142
|
+
### 标记弃用(deprecate)
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# 弃用某个版本(用户安装时会看到警告)
|
|
146
|
+
npm deprecate @code2rich/jpage@1.5.1 "有严重 bug,请用 1.5.2"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 发布修复版本
|
|
150
|
+
|
|
151
|
+
发一个新版本(如 `1.5.2`)修正问题,然后 deprecate 坏版本。**不要试图覆盖已发布的版本号**——npm 不允许重复发布同一版本。
|
|
152
|
+
|
|
153
|
+
### 完全撤下包(72 小时内)
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# 包发布 72 小时内可 unpublish 整个包(慎用,会破坏所有依赖者)
|
|
157
|
+
# npm unpublish @code2rich/jpage --force
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
> 超过 72 小时无法 unpublish。一般用 deprecate + 发新版本代替。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 故障排查
|
|
165
|
+
|
|
166
|
+
### CI 发版失败
|
|
167
|
+
|
|
168
|
+
| 错误 | 原因 | 处理 |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `403 Forbidden - Two-factor authentication required` | `NPM_TOKEN` 是 session token 不是 granular token | 重新生成 granular token(bypass 2FA),更新 GitHub Secret |
|
|
171
|
+
| `403 Forbidden - You do not have permission` | token 权限不含 `@code2rich` scope | 重新生成 token,Packages 勾 `@code2rich` |
|
|
172
|
+
| `EPUBLISHCONFLICT - You cannot publish over` | 该版本号已发布过 | 升版本号再发,npm 不允许覆盖 |
|
|
173
|
+
| `tag (x) 与 package.json version (y) 不一致` | tag 名和 package.json 对不上 | 确保 `npm version` 自动打 tag,或手动对齐 |
|
|
174
|
+
| lint/test/build 失败 | 代码有问题 | 看 Actions 日志修,修完不用重打 tag(直接 push commit 到 main,tag 已存在不会重跑——需删 tag 重打) |
|
|
175
|
+
|
|
176
|
+
**删 tag 重跑**(修完代码后):
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
git tag -d v1.5.1 # 删本地
|
|
180
|
+
git push origin :refs/tags/v1.5.1 # 删远程
|
|
181
|
+
# 重新打 tag(指向最新 commit)
|
|
182
|
+
git tag v1.5.1
|
|
183
|
+
git push origin v1.5.1
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### `NPM_TOKEN` 过期 / 轮换
|
|
187
|
+
|
|
188
|
+
1. npm 网站生成新 granular token
|
|
189
|
+
2. 更新 GitHub Secret:`https://github.com/code2rich/jpage/settings/secrets/actions` → `NPM_TOKEN` → Update
|
|
190
|
+
3. 下次发版自动用新 token
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 安全清单(每次发版前后自查)
|
|
195
|
+
|
|
196
|
+
- [ ] 没有把 npm token / `.npmrc` 含明文 token 的文件 commit 到仓库
|
|
197
|
+
- [ ] `.gitignore` 含 `.npmrc`(防误提交)
|
|
198
|
+
- [ ] CI 的 `NPM_TOKEN` 是 granular token(最小权限),不是账号 session token
|
|
199
|
+
- [ ] npm 账号开了 2FA(`auth-and-writes`)
|
|
200
|
+
- [ ] 过期的 token 已在 npm 网站撤销
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 相关文件
|
|
205
|
+
|
|
206
|
+
- `.github/workflows/release.yml` — 自动发版 workflow(tag 触发)
|
|
207
|
+
- `.github/workflows/ci.yml` — CI 测试 workflow(push/PR 触发)
|
|
208
|
+
- `package.json` 的 `publishConfig.access: "public"` — scoped 包公开发布(必须)
|
|
209
|
+
- `package.json` 的 `bin`、`engines`、`version` — 发布元信息
|