vite-plugin-siyuan 0.3.0 → 0.3.1
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 +11 -3
- package/dist/artifact/assets.js +5 -3
- package/dist/index.js +5 -3
- package/dist/output/log.js +7 -2
- package/dist/session.js +62 -32
- package/dist/siyuan/link.d.ts +0 -2
- package/dist/siyuan/link.js +4 -5
- package/dist/support/log.d.ts +7 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,13 +94,21 @@ pnpm vite build --watch
|
|
|
94
94
|
|
|
95
95
|
## 输出
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
默认只打印用户需要知道的结果与需要处理的问题:链接/启用状态、校验失败、README 复制失败、打包结果与预览入口;端口探测、跳过监听这类内部过程只在 `SIYUAN_DEBUG=1` 时输出。
|
|
98
|
+
|
|
99
|
+
watch 首轮构建不打印 `build #N` 信号(此时「会话就绪 + 预览入口」已经说明可以开始了);之后每次热重载打印一行,供 agent 判断「可以测量了」:
|
|
98
100
|
|
|
99
101
|
```
|
|
100
102
|
[vite-plugin-siyuan] build #12 ready · theme · watch
|
|
101
103
|
```
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
watch 构建失败(含校验未通过)对应的是:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
[vite-plugin-siyuan] build #12 failed · theme · watch
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
生产构建成功时:
|
|
104
112
|
|
|
105
113
|
```
|
|
106
114
|
[vite-plugin-siyuan] build #1 ready · theme · package.zip
|
|
@@ -117,7 +125,7 @@ watch 模式首次就绪后还会打印桌面端 / 移动端两个稳定预览
|
|
|
117
125
|
| `SIYUAN_PLUGIN_DIR` | 插件安装父目录(`{workspace}/data/plugins`),同上 |
|
|
118
126
|
| `SIYUAN_TOKEN` | 开了 API token 时使用 |
|
|
119
127
|
| `SIYUAN_NO_ELEVATION=1` | 不弹 UAC 提权(CI 环境自动不弹) |
|
|
120
|
-
| `SIYUAN_DEBUG=1` |
|
|
128
|
+
| `SIYUAN_DEBUG=1` | 输出内部调试日志(端口探测、跳过监听、README 兜底等) |
|
|
121
129
|
|
|
122
130
|
旧名 `SYD_NO_ELEVATION` / `SYD_DEBUG` 仍然兼容。
|
|
123
131
|
|
package/dist/artifact/assets.js
CHANGED
|
@@ -24,7 +24,8 @@ const README_PATTERN = /^README.*\.md$/;
|
|
|
24
24
|
/** 把 publicDir 下的文件逐个交给构建器登记监听。 */
|
|
25
25
|
export function watchPublicFiles(publicDir, registerWatchFile, log) {
|
|
26
26
|
if (!publicDir || !existsSync(publicDir)) {
|
|
27
|
-
|
|
27
|
+
// 清单缺失会在校验阶段报错;这里只是内部流程信息,不该默认打扰用户。
|
|
28
|
+
log(`publicDir 不存在,跳过监听:${publicDir}`, 'debug');
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
31
|
for (const file of walkFiles(publicDir))
|
|
@@ -47,14 +48,15 @@ export function copyReadmes({ root, publicDir, outDir, manifestName, watch, log,
|
|
|
47
48
|
const manifestPath = publicDir ? join(publicDir, manifestName) : join(root, 'public', manifestName);
|
|
48
49
|
const parsed = readJsonFile(manifestPath);
|
|
49
50
|
const declared = isRecord(parsed) ? declaredAssets(parsed, ['readme']) : null;
|
|
51
|
+
// 清单问题由构建收尾的校验统一报;这里只是 README 搬运的内部兜底过程。
|
|
50
52
|
if (!isRecord(parsed))
|
|
51
|
-
log(`读不到或无法解析清单 ${manifestPath};README 按 README*.md 兜底`, '
|
|
53
|
+
log(`读不到或无法解析清单 ${manifestPath};README 按 README*.md 兜底`, 'debug');
|
|
52
54
|
const names = [];
|
|
53
55
|
if (declared?.length) {
|
|
54
56
|
names.push(...declared);
|
|
55
57
|
}
|
|
56
58
|
else {
|
|
57
|
-
log(`清单 ${manifestPath} 没声明 readme,按 README*.md 兜底搬运`, '
|
|
59
|
+
log(`清单 ${manifestPath} 没声明 readme,按 README*.md 兜底搬运`, 'debug');
|
|
58
60
|
names.push(...readdirSync(root, { withFileTypes: true })
|
|
59
61
|
.filter((entry) => entry.isFile() && README_PATTERN.test(entry.name))
|
|
60
62
|
.map((entry) => entry.name));
|
package/dist/index.js
CHANGED
|
@@ -53,12 +53,14 @@ export default function siyuan(options) {
|
|
|
53
53
|
const publicDir = resolvedConfig.publicDir;
|
|
54
54
|
const watch = resolvedConfig.command === 'build' && Boolean(resolvedConfig.build.watch);
|
|
55
55
|
if (!kind) {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
// 正常情况下 config 阶段已确定类型或抛错;走到这里说明状态异常,
|
|
57
|
+
// 不能只 warn 后跳过校验——静默产出思源加载不到的东西更难排查。
|
|
58
|
+
throw new UserError('识别不到被测类型:请确认 publicDir 下的 theme.json / plugin.json,或用 kind 选项显式指定');
|
|
58
59
|
}
|
|
59
60
|
session = createBuildSession({ root, outDir: resolve(root, outDir), publicDir, watch }, { kind, log, previewLogger: log });
|
|
61
|
+
// 生产构建的最终结果由「已打包 + build #N ready」体现,模式说明只对排查有用。
|
|
60
62
|
if (!watch)
|
|
61
|
-
log('生产构建:不启动热重载会话,只校验产物并打包');
|
|
63
|
+
log('生产构建:不启动热重载会话,只校验产物并打包', 'debug');
|
|
62
64
|
},
|
|
63
65
|
buildStart() {
|
|
64
66
|
session?.onStart((file) => this.addWatchFile(file));
|
package/dist/output/log.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志:consola 带 tag 前缀,保留级别图标与配色。
|
|
3
3
|
*
|
|
4
|
+
* 默认级别只输出用户需要知道的结果与需要处理的问题;内部过程信息走 debug,
|
|
5
|
+
* 由 `SIYUAN_DEBUG=1`(旧名 `SYD_DEBUG`)打开。用精确比较而不是 truthiness:
|
|
6
|
+
* `=0` 在 shell 里很常见,truthiness 会把它读成「开」。
|
|
7
|
+
*
|
|
4
8
|
* 用 `compact: false` 让多行消息保持原样——预览入口那块靠它维持缩进。
|
|
5
|
-
* `SIYUAN_DEBUG=1`(旧名 `SYD_DEBUG`)时降到 debug 级别;用精确比较而不是
|
|
6
|
-
* truthiness:`=0` 在 shell 里很常见,truthiness 会把它读成「开」。
|
|
7
9
|
*/
|
|
8
10
|
import { createConsola } from 'consola';
|
|
9
11
|
import { envFlag } from '../support/env.js';
|
|
@@ -14,6 +16,9 @@ export function createTaggedLogger(tag) {
|
|
|
14
16
|
}).withTag(tag);
|
|
15
17
|
const log = (message, level = 'info') => {
|
|
16
18
|
switch (level) {
|
|
19
|
+
case 'debug':
|
|
20
|
+
logger.debug(message);
|
|
21
|
+
break;
|
|
17
22
|
case 'success':
|
|
18
23
|
logger.success(message);
|
|
19
24
|
break;
|
package/dist/session.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { relative, resolve } from 'node:path';
|
|
7
7
|
import { activate, reloadUI } from './siyuan/activate.js';
|
|
8
8
|
import { copyReadmes, watchPublicFiles } from './artifact/assets.js';
|
|
9
|
-
import {
|
|
9
|
+
import { ensureLink } from './siyuan/link.js';
|
|
10
10
|
import { kindSpec, readName } from './siyuan/kinds.js';
|
|
11
11
|
import { findKernelPort } from './siyuan/port.js';
|
|
12
12
|
import { logPreviewLinks } from './output/preview-links.js';
|
|
@@ -32,6 +32,9 @@ export function createBuildSession(facts, options) {
|
|
|
32
32
|
let starting = null;
|
|
33
33
|
let reported = false;
|
|
34
34
|
let buildCount = 0;
|
|
35
|
+
let failed = false;
|
|
36
|
+
let wrote = false;
|
|
37
|
+
let signaled = false;
|
|
35
38
|
let pendingTimer = null;
|
|
36
39
|
let pendingFiles = [];
|
|
37
40
|
let chain = Promise.resolve();
|
|
@@ -50,25 +53,18 @@ export function createBuildSession(facts, options) {
|
|
|
50
53
|
const link = ensureLink(current.outDir, installPath, { requireSymlink: spec.requireSymlink });
|
|
51
54
|
if (!link.ok)
|
|
52
55
|
throw new UserError(link.reason ?? '建立链接失败');
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const missing = checkLinkedFiles(installPath, spec.expectedFiles);
|
|
56
|
-
if (missing.length)
|
|
57
|
-
log(`链接目录缺少 ${missing.join('、')},构建完成后会补齐`, 'warn');
|
|
56
|
+
// 首次建链是用户关心的安装结果;链接本来就在时路径只对排查有用,放 debug。
|
|
57
|
+
log(`已链接:${installPath}`, link.changed ? 'success' : 'debug');
|
|
58
58
|
const activation = await activate(kind, port, name);
|
|
59
59
|
if (!activation.ok)
|
|
60
60
|
throw new UserError(activation.message);
|
|
61
61
|
// 首次启用插件必须补一次刷新:思源配置刚变,不刷新页面不会加载它。
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
62
|
+
// 成功是预期结果,不单独占一行;失败才需要提醒用户手动刷新。
|
|
63
|
+
const firstReload = activation.changed
|
|
64
|
+
? { ok: await reloadUI(port), message: '刷新广播失败,请手动刷新思源窗口' }
|
|
65
|
+
: null;
|
|
67
66
|
return {
|
|
68
67
|
port,
|
|
69
|
-
name,
|
|
70
|
-
linkedPath: link.ok ? installPath : null,
|
|
71
|
-
missingFiles: missing,
|
|
72
68
|
activation: { ok: activation.ok, message: activation.message },
|
|
73
69
|
firstReload,
|
|
74
70
|
};
|
|
@@ -101,26 +97,39 @@ export function createBuildSession(facts, options) {
|
|
|
101
97
|
if (!ready)
|
|
102
98
|
return;
|
|
103
99
|
const ok = await reloadUI(ready.port);
|
|
104
|
-
|
|
100
|
+
// 每次保存都成功刷新是正常工作过程,不刷屏;失败必须让用户看到。
|
|
101
|
+
log(ok ? `页面已刷新:${matched[0]}` : `刷新未生效:${matched[0]}`, ok ? 'debug' : 'warn');
|
|
105
102
|
});
|
|
106
103
|
}
|
|
107
104
|
return {
|
|
108
105
|
/** 构建开始:把 public/** 逐个交给构建器登记监听。 */
|
|
109
106
|
onStart(registerWatchFile) {
|
|
107
|
+
// 每轮开始重置状态:上一轮失败不能污染下一轮的成功信号。
|
|
108
|
+
buildCount += 1;
|
|
109
|
+
failed = false;
|
|
110
|
+
wrote = false;
|
|
111
|
+
signaled = false;
|
|
110
112
|
watchPublicFiles(facts.publicDir, registerWatchFile, log);
|
|
111
113
|
},
|
|
112
114
|
/** 产物写完。 */
|
|
113
115
|
async onWritten(files) {
|
|
114
116
|
const current = facts;
|
|
117
|
+
wrote = true;
|
|
115
118
|
// 元数据搬运先做:校验时文件必须已经就位。
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
try {
|
|
120
|
+
copyReadmes({
|
|
121
|
+
root: current.root,
|
|
122
|
+
publicDir: current.publicDir,
|
|
123
|
+
outDir: relative(current.root, current.outDir) || '.',
|
|
124
|
+
manifestName: spec.manifestName,
|
|
125
|
+
watch: current.watch,
|
|
126
|
+
log,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (cause) {
|
|
130
|
+
failed = true;
|
|
131
|
+
throw cause;
|
|
132
|
+
}
|
|
124
133
|
if (!current.watch)
|
|
125
134
|
return;
|
|
126
135
|
// 会话还没起来:启动它,本轮不通知重载。
|
|
@@ -130,6 +139,11 @@ export function createBuildSession(facts, options) {
|
|
|
130
139
|
await ensure();
|
|
131
140
|
}
|
|
132
141
|
catch (cause) {
|
|
142
|
+
failed = true;
|
|
143
|
+
// UserError 已经是可读文案(链接权限、安装目录不存在、多个工作区…),
|
|
144
|
+
// 再套一层「思源在运行」会把权限问题误指成思源没启动。
|
|
145
|
+
if (cause instanceof UserError)
|
|
146
|
+
throw cause;
|
|
133
147
|
throw new UserError(`watch 模式需要思源在运行:${describeError(cause)}`);
|
|
134
148
|
}
|
|
135
149
|
return;
|
|
@@ -145,7 +159,12 @@ export function createBuildSession(facts, options) {
|
|
|
145
159
|
/** 一轮结束:校验 + 打包。 */
|
|
146
160
|
onFinished() {
|
|
147
161
|
const current = facts;
|
|
148
|
-
|
|
162
|
+
// 构建在 writeBundle 之前就失败,或 writeBundle 已经失败:跳过校验/打包,
|
|
163
|
+
// 否则「产物目录不存在」会盖住真正的构建错误。
|
|
164
|
+
if (!wrote || failed) {
|
|
165
|
+
failed = true;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
149
168
|
const result = verifyTarget(kind, {
|
|
150
169
|
root: current.root,
|
|
151
170
|
publicDir: current.publicDir,
|
|
@@ -155,6 +174,7 @@ export function createBuildSession(facts, options) {
|
|
|
155
174
|
const text = formatIssues(result.issues);
|
|
156
175
|
if (current.watch) {
|
|
157
176
|
// 开发中中断会让 watch 退出,得手动重开,代价太大。
|
|
177
|
+
failed = true;
|
|
158
178
|
log(`产物校验未通过,开发模式仅提示:\n${text}`, 'warn');
|
|
159
179
|
}
|
|
160
180
|
else {
|
|
@@ -170,20 +190,30 @@ export function createBuildSession(facts, options) {
|
|
|
170
190
|
/** 本轮最后:打印会话汇报与预览入口(只一次),再打构建完成日志。 */
|
|
171
191
|
onEnd() {
|
|
172
192
|
const current = facts;
|
|
173
|
-
|
|
193
|
+
// Vite 在构建失败时可能多次触发 closeBundle;每轮只发一次完成信号。
|
|
194
|
+
if (signaled)
|
|
195
|
+
return;
|
|
196
|
+
signaled = true;
|
|
197
|
+
if (current.watch && ready && !reported && !failed) {
|
|
174
198
|
reported = true;
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (ready.
|
|
179
|
-
log(ready.
|
|
180
|
-
if (ready.firstReload)
|
|
181
|
-
log(ready.firstReload.message, ready.firstReload.ok ? 'success' : 'warn');
|
|
199
|
+
// 只汇报「现在处于什么状态」:链接/启用的成功路径已在动作发生时打过,
|
|
200
|
+
// 这里不再重复;刷新失败才是需要用户处理的问题。
|
|
201
|
+
log(ready.activation.message, 'success');
|
|
202
|
+
if (ready.firstReload && !ready.firstReload.ok)
|
|
203
|
+
log(ready.firstReload.message, 'warn');
|
|
182
204
|
log('会话就绪,改源码会自动刷新页面', 'success');
|
|
183
205
|
logPreviewLinks(previewLogger, ready.port);
|
|
184
206
|
}
|
|
185
207
|
// agent 的构建完成信号:纯文本、序号递增,便于从 stdout 里认。
|
|
208
|
+
// watch 首轮不打印:此时「会话就绪 + 预览入口」已经说明可以开始了,
|
|
209
|
+
// 只有后续热重载才需要这个信号。失败仍然要打印。
|
|
186
210
|
const files = current.watch ? 'watch' : PACKAGE_NAME;
|
|
211
|
+
if (!wrote || failed) {
|
|
212
|
+
console.log(`[vite-plugin-siyuan] build #${buildCount} failed · ${kind} · ${files}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (current.watch && buildCount === 1)
|
|
216
|
+
return;
|
|
187
217
|
console.log(`[vite-plugin-siyuan] build #${buildCount} ready · ${kind} · ${files}`);
|
|
188
218
|
},
|
|
189
219
|
/** 释放定时器(幂等)。 */
|
package/dist/siyuan/link.d.ts
CHANGED
|
@@ -14,6 +14,4 @@ export interface LinkResult {
|
|
|
14
14
|
export declare function ensureLink(srcPath: string, targetPath: string, { requireSymlink }?: {
|
|
15
15
|
requireSymlink?: boolean;
|
|
16
16
|
}): LinkResult;
|
|
17
|
-
/** 链接建好后,确认思源该读的文件真的在(防止链到空目录还以为成功)。 */
|
|
18
|
-
export declare function checkLinkedFiles(targetPath: string, expectedFiles: string[]): string[];
|
|
19
17
|
export {};
|
package/dist/siyuan/link.js
CHANGED
|
@@ -110,7 +110,10 @@ function elevateSymlink(srcPath, targetPath) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
function describeFailure(cause, requireSymlink, elevationFailed) {
|
|
113
|
-
|
|
113
|
+
// cause 为 null 只可能出现在「循环没走到 catch」的理论分支;仍给出可读文案,
|
|
114
|
+
// 绝不能把 `null` 打给用户。
|
|
115
|
+
const detail = cause instanceof Error ? cause.message : cause === null ? '系统拒绝创建链接(权限不足)' : String(cause);
|
|
116
|
+
const lines = [`无法建立目录链接:${detail}`];
|
|
114
117
|
if (elevationFailed) {
|
|
115
118
|
lines.push(' UAC 提权被拒绝或超时:请重新运行并在弹窗点「是」,或预先开启 Windows 开发者模式。');
|
|
116
119
|
lines.push(' 插件目录必须是真 symlink,不能用 junction 兜底。');
|
|
@@ -197,7 +200,3 @@ export function ensureLink(srcPath, targetPath, { requireSymlink = false } = {})
|
|
|
197
200
|
}
|
|
198
201
|
return { ok: false, changed: false, linkType: null, reason: describeFailure(null, requireSymlink, elevationFailed) };
|
|
199
202
|
}
|
|
200
|
-
/** 链接建好后,确认思源该读的文件真的在(防止链到空目录还以为成功)。 */
|
|
201
|
-
export function checkLinkedFiles(targetPath, expectedFiles) {
|
|
202
|
-
return expectedFiles.filter((file) => !existsSync(resolve(targetPath, file)));
|
|
203
|
-
}
|
package/dist/support/log.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** 日志级别与回调契约(不含输出实现,实现在 output/log.ts)。 */
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* 日志级别。
|
|
4
|
+
*
|
|
5
|
+
* 默认只应使用 info / success / warn / error 输出用户关心的结果与问题;
|
|
6
|
+
* 探测端口、跳过监听、兜底读取这类过程信息走 debug,仅 `SIYUAN_DEBUG=1` 时可见。
|
|
7
|
+
*/
|
|
8
|
+
export type LogLevel = 'debug' | 'info' | 'success' | 'warn' | 'error';
|
|
4
9
|
/** 日志回调。level 可选,缺省按 info 处理。 */
|
|
5
10
|
export type Logger = (message: string, level?: LogLevel) => void;
|
package/package.json
CHANGED