onepage-cli 1.0.0 → 1.0.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/README.md +19 -5
- package/bin/op.js +72 -20
- package/lib/config.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,10 +33,22 @@ op push # 部署上线(交互式输入短链名、预览图、公开、SEO
|
|
|
33
33
|
|
|
34
34
|
| 命令 | 说明 |
|
|
35
35
|
|---|---|
|
|
36
|
-
| `op init` | 在当前目录创建代码暂存区 `.onepage
|
|
36
|
+
| `op init` | 在当前目录创建代码暂存区 `.onepage/`,并生成**站点配置模板 `.onepage/preview.yml`** |
|
|
37
37
|
| `op add [路径]` | 把文件/目录加入暂存区,默认当前目录 `.`(自动过滤不支持的类型与 node_modules 等) |
|
|
38
38
|
| `op status` | 查看暂存区文件清单与大小 |
|
|
39
|
-
| `op push` |
|
|
39
|
+
| `op push` | 部署暂存区代码上线。**存在 `.onepage/preview.yml` 时直接按其配置部署(免交互)**;不存在时回退为交互式输入:短链名 → 预览图路径或 no → 是否公开 → 是否 SEO。短链名已存在且属于自己时为**覆盖更新** |
|
|
40
|
+
|
|
41
|
+
### preview.yml 站点配置(op init 自动生成)
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
# OnePage 站点配置 —— 填好后直接执行 op push,无需交互输入
|
|
45
|
+
title: '' # 站点名称(留空则使用短链名)
|
|
46
|
+
description: '' # 站点介绍
|
|
47
|
+
path_name: '' # 短链名:字母/数字/连字符/下划线,<=64 字符(留空则使用目录名)
|
|
48
|
+
preview: no # 预览图路径(本地图片文件,无则填 no)
|
|
49
|
+
is_public: yes # 是否公开(yes/no)
|
|
50
|
+
seo: yes # 是否 SEO 优化(yes/no)
|
|
51
|
+
```
|
|
40
52
|
|
|
41
53
|
### 站点管理
|
|
42
54
|
|
|
@@ -59,13 +71,15 @@ op push # 部署上线(交互式输入短链名、预览图、公开、SEO
|
|
|
59
71
|
|
|
60
72
|
```json
|
|
61
73
|
{
|
|
62
|
-
"server": "https://
|
|
74
|
+
"server": "https://app.collegehub.cn",
|
|
63
75
|
"token": "...",
|
|
64
76
|
"username": "...",
|
|
65
77
|
"email": "..."
|
|
66
78
|
}
|
|
67
79
|
```
|
|
68
80
|
|
|
81
|
+
默认服务器为 `https://app.collegehub.cn`;如需指向其他地址,设置环境变量 `OP_SERVER=https://xxx` 后重新 `op login`。
|
|
82
|
+
|
|
69
83
|
## 典型工作流
|
|
70
84
|
|
|
71
85
|
```bash
|
|
@@ -78,13 +92,13 @@ op init && op add . && op push
|
|
|
78
92
|
# → 预览图路径(no 跳过): no
|
|
79
93
|
# → 是否公开(yes/no): yes
|
|
80
94
|
# → 是否 SEO 优化(yes/no): yes
|
|
81
|
-
# ✓ 部署完成:https://
|
|
95
|
+
# ✓ 部署完成:https://app.collegehub.cn/my-site/
|
|
82
96
|
|
|
83
97
|
# 更新:改完代码再 add + push(短链名不变即覆盖更新)
|
|
84
98
|
op add . && op push
|
|
85
99
|
|
|
86
100
|
# 备份/迁移
|
|
87
|
-
op clone https://
|
|
101
|
+
op clone https://app.collegehub.cn/my-site/
|
|
88
102
|
```
|
|
89
103
|
|
|
90
104
|
## 安全说明
|
package/bin/op.js
CHANGED
|
@@ -14,6 +14,18 @@ const { ok, err, info, warn, table, pad } = require('../lib/util');
|
|
|
14
14
|
const STAGE_DIR = '.onepage';
|
|
15
15
|
const STAGE_FILES = path.join(STAGE_DIR, 'stage');
|
|
16
16
|
const STAGE_MANIFEST = path.join(STAGE_DIR, 'manifest.json');
|
|
17
|
+
const STAGE_YML = path.join(STAGE_DIR, 'preview.yml');
|
|
18
|
+
|
|
19
|
+
const PREVIEW_YML_TEMPLATE = [
|
|
20
|
+
'# OnePage 站点配置 —— 填好后直接执行 op push,无需交互输入',
|
|
21
|
+
'# 值一律写在冒号后面;不需要的字段留空即可',
|
|
22
|
+
"title: '' # 站点名称(留空则使用短链名)",
|
|
23
|
+
"description: '' # 站点介绍",
|
|
24
|
+
"path_name: '' # 短链名:字母/数字/连字符/下划线,<=64 字符(留空则使用目录名)",
|
|
25
|
+
'preview: no # 预览图路径(本地图片文件,无则填 no)',
|
|
26
|
+
'is_public: yes # 是否公开(yes/no)',
|
|
27
|
+
'seo: yes # 是否 SEO 优化(yes/no)',
|
|
28
|
+
].join('\n') + '\n';
|
|
17
29
|
|
|
18
30
|
// 允许部署的文件扩展名(与服务端保持一致)
|
|
19
31
|
const ALLOWED_EXTS = new Set([
|
|
@@ -83,6 +95,23 @@ function cmdLogout() {
|
|
|
83
95
|
ok('已退出登录(本地凭证已清除)');
|
|
84
96
|
}
|
|
85
97
|
|
|
98
|
+
/* ---------- 极简 YAML 解析(仅支持扁平 key: value,满足 preview.yml) ---------- */
|
|
99
|
+
function parseSimpleYaml(text) {
|
|
100
|
+
const out = {};
|
|
101
|
+
for (let line of String(text).split(/\r?\n/)) {
|
|
102
|
+
line = line.trim();
|
|
103
|
+
if (!line || line.startsWith('#')) continue;
|
|
104
|
+
const i = line.indexOf(':');
|
|
105
|
+
if (i < 1) continue;
|
|
106
|
+
const k = line.slice(0, i).trim();
|
|
107
|
+
let v = line.slice(i + 1).trim();
|
|
108
|
+
if ((v.startsWith('"') && v.endsWith('"') && v.length >= 2) ||
|
|
109
|
+
(v.startsWith("'") && v.endsWith("'") && v.length >= 2)) v = v.slice(1, -1);
|
|
110
|
+
out[k] = v;
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
|
|
86
115
|
/* ---------- init ---------- */
|
|
87
116
|
function cmdInit() {
|
|
88
117
|
const cwd = process.cwd();
|
|
@@ -96,10 +125,12 @@ function cmdInit() {
|
|
|
96
125
|
created_at: new Date().toISOString(),
|
|
97
126
|
files: [],
|
|
98
127
|
}, null, 2));
|
|
99
|
-
|
|
128
|
+
fs.writeFileSync(STAGE_YML, PREVIEW_YML_TEMPLATE);
|
|
129
|
+
ok('已创建暂存区 .onepage/stage/ 和站点配置 .onepage/preview.yml');
|
|
100
130
|
console.log('接下来:');
|
|
101
|
-
console.log('
|
|
102
|
-
console.log('
|
|
131
|
+
console.log(' 1. 编辑 .onepage/preview.yml,填好站点名称、介绍、短链名等');
|
|
132
|
+
console.log(' 2. op add . 把当前目录代码加入暂存区');
|
|
133
|
+
console.log(' 3. op push 按 preview.yml 配置直接部署上线');
|
|
103
134
|
}
|
|
104
135
|
|
|
105
136
|
/* ---------- add ---------- */
|
|
@@ -207,29 +238,50 @@ async function cmdPush() {
|
|
|
207
238
|
|
|
208
239
|
// 默认短链名:目录名
|
|
209
240
|
const dirName = path.basename(process.cwd()).replace(/[^A-Za-z0-9_\-]/g, '-').toLowerCase() || 'site';
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
241
|
+
let pathName, title, description, previewPath, isPublic, useSeo;
|
|
242
|
+
|
|
243
|
+
if (fs.existsSync(STAGE_YML)) {
|
|
244
|
+
// preview.yml 模式:免交互直接部署
|
|
245
|
+
const y = parseSimpleYaml(fs.readFileSync(STAGE_YML, 'utf8'));
|
|
246
|
+
pathName = (y.path_name || '').trim() || dirName;
|
|
247
|
+
title = (y.title || '').trim() || pathName;
|
|
248
|
+
description = (y.description || '').trim();
|
|
249
|
+
isPublic = parseBool(y.is_public, true);
|
|
250
|
+
useSeo = parseBool(y.seo, true);
|
|
251
|
+
const pv = (y.preview || 'no').trim();
|
|
252
|
+
previewPath = null;
|
|
253
|
+
if (pv && pv.toLowerCase() !== 'no') {
|
|
254
|
+
previewPath = path.resolve(process.cwd(), pv);
|
|
255
|
+
if (!fs.existsSync(previewPath)) die('preview.yml 中预览图不存在:' + pv);
|
|
256
|
+
const pext = path.basename(previewPath).split('.').pop().toLowerCase();
|
|
257
|
+
if (!['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(pext)) die('预览图仅支持 jpg/png/gif/webp');
|
|
258
|
+
}
|
|
259
|
+
info('使用 .onepage/preview.yml 配置部署(短链名:' + pathName + ')');
|
|
260
|
+
} else {
|
|
261
|
+
// 交互模式(无 preview.yml 时兼容旧行为)
|
|
262
|
+
pathName = await ask('短链名(字母/数字/连字符/下划线)', dirName);
|
|
263
|
+
title = pathName;
|
|
264
|
+
description = '';
|
|
265
|
+
const previewAns = await ask('预览图路径(图片文件,输入 no 跳过)', 'no');
|
|
266
|
+
previewPath = null;
|
|
267
|
+
if (previewAns && previewAns.toLowerCase() !== 'no') {
|
|
268
|
+
const pp = path.resolve(process.cwd(), previewAns);
|
|
269
|
+
if (!fs.existsSync(pp)) die('预览图不存在:' + previewAns);
|
|
270
|
+
const pext = path.basename(pp).split('.').pop().toLowerCase();
|
|
271
|
+
if (!['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(pext)) die('预览图仅支持 jpg/png/gif/webp');
|
|
272
|
+
previewPath = pp;
|
|
273
|
+
}
|
|
274
|
+
isPublic = parseBool(await ask('是否公开(yes/no)', 'yes'), true);
|
|
275
|
+
useSeo = parseBool(await ask('是否 SEO 优化(yes/no)', 'yes'), true);
|
|
221
276
|
}
|
|
222
277
|
|
|
223
|
-
|
|
224
|
-
const isPublic = parseBool(pubAns, true);
|
|
225
|
-
const seoAns = await ask('是否 SEO 优化(yes/no)', 'yes');
|
|
226
|
-
const useSeo = parseBool(seoAns, true);
|
|
278
|
+
if (!/^[A-Za-z0-9_\-]{1,64}$/.test(pathName)) die('短链名只能包含字母、数字、连字符和下划线(≤64 字符)');
|
|
227
279
|
|
|
228
280
|
info('上传 ' + files.length + ' 个文件中…');
|
|
229
281
|
const form = new FormData();
|
|
230
282
|
form.append('path_name', pathName);
|
|
231
|
-
form.append('title',
|
|
232
|
-
form.append('description',
|
|
283
|
+
form.append('title', title);
|
|
284
|
+
form.append('description', description);
|
|
233
285
|
form.append('is_public', isPublic ? 'true' : 'false');
|
|
234
286
|
form.append('seo', useSeo ? 'true' : 'false');
|
|
235
287
|
for (const f of files) {
|
package/lib/config.js
CHANGED
|
@@ -5,7 +5,10 @@ const path = require('path');
|
|
|
5
5
|
const os = require('os');
|
|
6
6
|
|
|
7
7
|
const CONF_PATH = path.join(os.homedir(), '.onepage-cli.json');
|
|
8
|
-
const DEFAULTS = {
|
|
8
|
+
const DEFAULTS = {
|
|
9
|
+
server: process.env.OP_SERVER || 'https://app.collegehub.cn',
|
|
10
|
+
token: '', username: '', email: '',
|
|
11
|
+
};
|
|
9
12
|
|
|
10
13
|
function load() {
|
|
11
14
|
try {
|