forge-openclaw-plugin 0.2.50 → 0.2.52
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/dist/assets/{index-C9_gJvi6.js → index-DX8RiahO.js} +46 -46
- package/dist/assets/{index-C9_gJvi6.js.map → index-DX8RiahO.js.map} +1 -1
- package/dist/assets/index-gthTrgvO.css +1 -0
- package/dist/index.html +2 -2
- package/dist/server/server/src/db.js +11 -0
- package/dist/server/server/src/openapi.js +1 -1
- package/dist/server/server/src/repositories/wiki-memory.js +1 -1
- package/dist/server/server/src/services/legacy-wiki-markdown-import.js +364 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/assets/index-2_tuemtU.css +0 -1
package/dist/index.html
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
/>
|
|
14
14
|
<link rel="icon" type="image/png" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
15
15
|
<link rel="alternate icon" href="/forge/assets/favicon-BCHm9dUV.ico" />
|
|
16
|
-
<script type="module" crossorigin src="/forge/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/forge/assets/index-DX8RiahO.js"></script>
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/forge/assets/vendor-D_NZFJze.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/forge/assets/board-CAszQU7Y.js">
|
|
19
19
|
<link rel="modulepreload" crossorigin href="/forge/assets/ui-B5MjRjKe.js">
|
|
20
20
|
<link rel="modulepreload" crossorigin href="/forge/assets/motion-CU5aNClV.js">
|
|
21
21
|
<link rel="modulepreload" crossorigin href="/forge/assets/table-CK0KcPYW.js">
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/forge/assets/vendor-DT3pnAKJ.css">
|
|
23
|
-
<link rel="stylesheet" crossorigin href="/forge/assets/index-
|
|
23
|
+
<link rel="stylesheet" crossorigin href="/forge/assets/index-gthTrgvO.css">
|
|
24
24
|
</head>
|
|
25
25
|
<body class="bg-canvas text-ink antialiased">
|
|
26
26
|
<div id="root"></div>
|
|
@@ -79,6 +79,7 @@ export function resolveDefaultDataRoot(currentWorkingDir = process.cwd()) {
|
|
|
79
79
|
}
|
|
80
80
|
let dataRoot = resolveDefaultDataRoot();
|
|
81
81
|
let seedDemoDataEnabled = false;
|
|
82
|
+
let legacyWikiAutoImportEnabled = true;
|
|
82
83
|
let db = null;
|
|
83
84
|
let transactionDepth = 0;
|
|
84
85
|
let savepointCounter = 0;
|
|
@@ -161,6 +162,9 @@ export function configureDatabase(options = {}) {
|
|
|
161
162
|
seedDemoDataEnabled = options.seedDemoData;
|
|
162
163
|
}
|
|
163
164
|
}
|
|
165
|
+
export function configureLegacyWikiAutoImport(enabled) {
|
|
166
|
+
legacyWikiAutoImportEnabled = enabled;
|
|
167
|
+
}
|
|
164
168
|
async function listMigrationFiles() {
|
|
165
169
|
const files = await readdir(migrationsDir);
|
|
166
170
|
return files.filter((file) => file.endsWith(".sql")).sort();
|
|
@@ -389,6 +393,13 @@ export async function initializeDatabase() {
|
|
|
389
393
|
seedData();
|
|
390
394
|
}
|
|
391
395
|
ensureQuestionnaireSeeds();
|
|
396
|
+
if (legacyWikiAutoImportEnabled) {
|
|
397
|
+
const { importLegacyWikiMarkdownOnStartup } = await import("./services/legacy-wiki-markdown-import.js");
|
|
398
|
+
const legacyWikiImport = await importLegacyWikiMarkdownOnStartup(getDataDir());
|
|
399
|
+
if (legacyWikiImport.scanned > 0) {
|
|
400
|
+
logForgeDebug(`[forge-db] imported legacy wiki markdown scanned=${legacyWikiImport.scanned} inserted=${legacyWikiImport.inserted} updated=${legacyWikiImport.updated} backed_up=${legacyWikiImport.backedUp} backup_path=${legacyWikiImport.backupPath}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
392
403
|
}
|
|
393
404
|
export function configureDatabaseSeeding(enabled) {
|
|
394
405
|
seedDemoDataEnabled = enabled;
|
|
@@ -115,7 +115,7 @@ const API_TAGS = [
|
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
name: "Wiki",
|
|
118
|
-
description: "
|
|
118
|
+
description: "SQLite-backed wiki settings, pages, ingest, sync, health, and search."
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
name: "Preferences",
|
|
@@ -1481,7 +1481,7 @@ export function listWikiSpaces() {
|
|
|
1481
1481
|
const rows = getDatabase()
|
|
1482
1482
|
.prepare(`SELECT id, slug, label, description, owner_user_id, visibility, created_at, updated_at
|
|
1483
1483
|
FROM wiki_spaces
|
|
1484
|
-
ORDER BY visibility
|
|
1484
|
+
ORDER BY CASE WHEN visibility = 'shared' THEN 0 ELSE 1 END, updated_at DESC`)
|
|
1485
1485
|
.all();
|
|
1486
1486
|
const spaces = rows.map(mapWikiSpace);
|
|
1487
1487
|
for (const space of spaces) {
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { cp, mkdir, readdir, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getDatabase, getEffectiveDataRoot } from "../db.js";
|
|
6
|
+
import { getNoteById } from "../repositories/notes.js";
|
|
7
|
+
import { syncNoteWikiArtifacts } from "../repositories/wiki-memory.js";
|
|
8
|
+
const startupImportMarkerId = "runtime:legacy-wiki-markdown-import:v1";
|
|
9
|
+
function parseFrontmatter(markdown) {
|
|
10
|
+
const normalized = markdown.replace(/\r\n/g, "\n");
|
|
11
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
12
|
+
if (!match) {
|
|
13
|
+
return { frontmatter: {}, body: normalized };
|
|
14
|
+
}
|
|
15
|
+
const frontmatter = {};
|
|
16
|
+
for (const line of match[1].split("\n")) {
|
|
17
|
+
const separatorIndex = line.indexOf(":");
|
|
18
|
+
if (separatorIndex <= 0) {
|
|
19
|
+
throw new Error(`Malformed frontmatter line: ${line}`);
|
|
20
|
+
}
|
|
21
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
22
|
+
const rawValue = line.slice(separatorIndex + 1).trim();
|
|
23
|
+
if (!key) {
|
|
24
|
+
throw new Error(`Malformed empty frontmatter key: ${line}`);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
frontmatter[key] = JSON.parse(rawValue);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
frontmatter[key] = rawValue.replace(/^"(.*)"$/, "$1");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { frontmatter, body: match[2] };
|
|
34
|
+
}
|
|
35
|
+
function slugify(value) {
|
|
36
|
+
const normalized = value
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.normalize("NFKD")
|
|
39
|
+
.replace(/[^\w\s-]/g, "")
|
|
40
|
+
.trim()
|
|
41
|
+
.replace(/[\s_]+/g, "-")
|
|
42
|
+
.replace(/-+/g, "-");
|
|
43
|
+
return normalized || "imported-page";
|
|
44
|
+
}
|
|
45
|
+
function stripMarkdown(markdown) {
|
|
46
|
+
return markdown
|
|
47
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
48
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
49
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
|
|
50
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
|
|
51
|
+
.replace(/[#>*_\-~]/g, " ")
|
|
52
|
+
.replace(/\s+/g, " ")
|
|
53
|
+
.trim();
|
|
54
|
+
}
|
|
55
|
+
function inferTitle(markdown, fallback) {
|
|
56
|
+
const heading = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
57
|
+
return heading || fallback.trim() || "Imported wiki page";
|
|
58
|
+
}
|
|
59
|
+
function inferSummary(markdown) {
|
|
60
|
+
return stripMarkdown(markdown).slice(0, 240);
|
|
61
|
+
}
|
|
62
|
+
function normalizeStringArray(value) {
|
|
63
|
+
return Array.isArray(value)
|
|
64
|
+
? value.filter((entry) => typeof entry === "string")
|
|
65
|
+
: [];
|
|
66
|
+
}
|
|
67
|
+
function normalizeLinkedEntities(value) {
|
|
68
|
+
if (!Array.isArray(value)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return value.flatMap((entry) => {
|
|
72
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const record = entry;
|
|
76
|
+
if (typeof record.entityType !== "string" ||
|
|
77
|
+
typeof record.entityId !== "string") {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
entityType: record.entityType,
|
|
83
|
+
entityId: record.entityId,
|
|
84
|
+
anchorKey: typeof record.anchorKey === "string" ? record.anchorKey : ""
|
|
85
|
+
}
|
|
86
|
+
];
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function walkMarkdownFiles(root) {
|
|
90
|
+
const results = [];
|
|
91
|
+
async function visit(directory) {
|
|
92
|
+
if (!existsSync(directory)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
96
|
+
const entryPath = path.join(directory, entry.name);
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
await visit(entryPath);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
102
|
+
results.push(entryPath);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
await visit(root);
|
|
107
|
+
return results.sort();
|
|
108
|
+
}
|
|
109
|
+
function findSpaceForFile(dataRoot, filePath, parsed) {
|
|
110
|
+
const explicitSpaceId = typeof parsed.frontmatter.spaceId === "string"
|
|
111
|
+
? parsed.frontmatter.spaceId
|
|
112
|
+
: "";
|
|
113
|
+
if (explicitSpaceId) {
|
|
114
|
+
const row = getDatabase()
|
|
115
|
+
.prepare("SELECT id FROM wiki_spaces WHERE id = ?")
|
|
116
|
+
.get(explicitSpaceId);
|
|
117
|
+
if (row) {
|
|
118
|
+
return row.id;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const relative = path.relative(path.join(dataRoot, "wiki"), filePath);
|
|
122
|
+
const parts = relative.split(path.sep);
|
|
123
|
+
if (parts[0] === "shared" && parts[1]) {
|
|
124
|
+
const row = getDatabase()
|
|
125
|
+
.prepare("SELECT id FROM wiki_spaces WHERE visibility = 'shared' AND slug = ?")
|
|
126
|
+
.get(parts[1]);
|
|
127
|
+
if (row) {
|
|
128
|
+
return row.id;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (parts[0] === "users" && parts[1]) {
|
|
132
|
+
const row = getDatabase()
|
|
133
|
+
.prepare("SELECT id FROM wiki_spaces WHERE owner_user_id = ? OR slug = ?")
|
|
134
|
+
.get(parts[1], parts[1]);
|
|
135
|
+
if (row) {
|
|
136
|
+
return row.id;
|
|
137
|
+
}
|
|
138
|
+
return ensurePersonalWikiSpaceForLegacyUser(parts[1]);
|
|
139
|
+
}
|
|
140
|
+
return "wiki_space_shared";
|
|
141
|
+
}
|
|
142
|
+
function findExistingNote(input) {
|
|
143
|
+
if (input.id) {
|
|
144
|
+
const byId = getDatabase()
|
|
145
|
+
.prepare("SELECT id FROM notes WHERE id = ?")
|
|
146
|
+
.get(input.id);
|
|
147
|
+
if (byId) {
|
|
148
|
+
return byId.id;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const bySlug = getDatabase()
|
|
152
|
+
.prepare("SELECT id FROM notes WHERE space_id = ? AND slug = ?")
|
|
153
|
+
.get(input.spaceId, input.slug);
|
|
154
|
+
return bySlug?.id ?? null;
|
|
155
|
+
}
|
|
156
|
+
function ensurePersonalWikiSpaceForLegacyUser(userId) {
|
|
157
|
+
const row = getDatabase()
|
|
158
|
+
.prepare(`SELECT id FROM wiki_spaces
|
|
159
|
+
WHERE owner_user_id = ? OR slug = ? OR slug = ? OR id = ?
|
|
160
|
+
ORDER BY created_at ASC
|
|
161
|
+
LIMIT 1`)
|
|
162
|
+
.get(userId, userId, `user-${slugify(userId)}`, `wiki_space_user_${slugify(userId)}`);
|
|
163
|
+
if (row) {
|
|
164
|
+
return row.id;
|
|
165
|
+
}
|
|
166
|
+
const now = new Date().toISOString();
|
|
167
|
+
const id = `wiki_space_user_${slugify(userId)}`;
|
|
168
|
+
getDatabase()
|
|
169
|
+
.prepare(`INSERT INTO wiki_spaces (id, slug, label, description, owner_user_id, visibility, created_at, updated_at)
|
|
170
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
171
|
+
ON CONFLICT(id) DO NOTHING`)
|
|
172
|
+
.run(id, `user-${slugify(userId)}`, `${userId} Wiki`, "Personal Forge wiki space recovered from legacy wiki files.", userId, "personal", now, now);
|
|
173
|
+
const inserted = getDatabase()
|
|
174
|
+
.prepare("SELECT id FROM wiki_spaces WHERE id = ?")
|
|
175
|
+
.get(id);
|
|
176
|
+
return inserted?.id ?? "wiki_space_shared";
|
|
177
|
+
}
|
|
178
|
+
function upsertLinks(noteId, links) {
|
|
179
|
+
getDatabase().prepare("DELETE FROM note_links WHERE note_id = ?").run(noteId);
|
|
180
|
+
const createdAt = new Date().toISOString();
|
|
181
|
+
const statement = getDatabase().prepare(`INSERT OR IGNORE INTO note_links (note_id, entity_type, entity_id, anchor_key, created_at)
|
|
182
|
+
VALUES (?, ?, ?, ?, ?)`);
|
|
183
|
+
for (const link of links) {
|
|
184
|
+
statement.run(noteId, link.entityType, link.entityId, link.anchorKey, createdAt);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function legacyBackupPath(dataRoot, backupLabel) {
|
|
188
|
+
return path.join(dataRoot, "backups", backupLabel, "wiki");
|
|
189
|
+
}
|
|
190
|
+
async function backupWikiRootOnce(input) {
|
|
191
|
+
const backupPath = legacyBackupPath(input.dataRoot, input.backupLabel);
|
|
192
|
+
if (existsSync(backupPath)) {
|
|
193
|
+
return { backupPath, backedUp: false };
|
|
194
|
+
}
|
|
195
|
+
await mkdir(path.dirname(backupPath), { recursive: true });
|
|
196
|
+
await cp(input.wikiRoot, backupPath, { recursive: true, force: false });
|
|
197
|
+
return { backupPath, backedUp: true };
|
|
198
|
+
}
|
|
199
|
+
export async function importLegacyWikiMarkdownToSqlite(input = {}) {
|
|
200
|
+
const options = {
|
|
201
|
+
dataRoot: path.resolve(input.dataRoot ?? getEffectiveDataRoot()),
|
|
202
|
+
apply: input.apply ?? false,
|
|
203
|
+
deleteFiles: input.deleteFiles ?? false,
|
|
204
|
+
backupBeforeApply: input.backupBeforeApply ?? false,
|
|
205
|
+
backupLabel: input.backupLabel ?? "legacy-wiki-markdown-pre-sqlite-import",
|
|
206
|
+
preserveExistingNotes: input.preserveExistingNotes ?? false
|
|
207
|
+
};
|
|
208
|
+
const wikiRoot = path.join(options.dataRoot, "wiki");
|
|
209
|
+
const files = await walkMarkdownFiles(wikiRoot);
|
|
210
|
+
let scanned = 0;
|
|
211
|
+
let inserted = 0;
|
|
212
|
+
let updated = 0;
|
|
213
|
+
let deleted = 0;
|
|
214
|
+
let backupPath = legacyBackupPath(options.dataRoot, options.backupLabel);
|
|
215
|
+
let backedUp = false;
|
|
216
|
+
if (options.apply && options.backupBeforeApply && files.length > 0) {
|
|
217
|
+
const backup = await backupWikiRootOnce({
|
|
218
|
+
wikiRoot,
|
|
219
|
+
dataRoot: options.dataRoot,
|
|
220
|
+
backupLabel: options.backupLabel
|
|
221
|
+
});
|
|
222
|
+
backupPath = backup.backupPath;
|
|
223
|
+
backedUp = backup.backedUp;
|
|
224
|
+
}
|
|
225
|
+
for (const filePath of files) {
|
|
226
|
+
scanned += 1;
|
|
227
|
+
const parsed = parseFrontmatter(await readFile(filePath, "utf8"));
|
|
228
|
+
const kind = filePath.includes(`${path.sep}pages${path.sep}`)
|
|
229
|
+
? "wiki"
|
|
230
|
+
: "evidence";
|
|
231
|
+
const spaceId = findSpaceForFile(options.dataRoot, filePath, parsed);
|
|
232
|
+
const markdown = parsed.body.trim();
|
|
233
|
+
const id = typeof parsed.frontmatter.id === "string" && parsed.frontmatter.id.trim()
|
|
234
|
+
? parsed.frontmatter.id.trim()
|
|
235
|
+
: `note_${createHash("sha1").update(filePath).digest("hex").slice(0, 10)}`;
|
|
236
|
+
const title = typeof parsed.frontmatter.title === "string"
|
|
237
|
+
? parsed.frontmatter.title
|
|
238
|
+
: inferTitle(markdown, path.basename(filePath, ".md"));
|
|
239
|
+
const slug = typeof parsed.frontmatter.slug === "string"
|
|
240
|
+
? parsed.frontmatter.slug
|
|
241
|
+
: slugify(path.basename(filePath, ".md"));
|
|
242
|
+
const aliases = normalizeStringArray(parsed.frontmatter.aliases);
|
|
243
|
+
const tags = normalizeStringArray(parsed.frontmatter.tags);
|
|
244
|
+
const summary = typeof parsed.frontmatter.summary === "string"
|
|
245
|
+
? parsed.frontmatter.summary
|
|
246
|
+
: inferSummary(markdown);
|
|
247
|
+
const contentPlain = stripMarkdown(markdown);
|
|
248
|
+
const links = normalizeLinkedEntities(parsed.frontmatter.linkedEntities);
|
|
249
|
+
const noteId = findExistingNote({ id, spaceId, slug });
|
|
250
|
+
const now = new Date().toISOString();
|
|
251
|
+
if (!options.apply) {
|
|
252
|
+
if (noteId) {
|
|
253
|
+
updated += 1;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
inserted += 1;
|
|
257
|
+
}
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (noteId) {
|
|
261
|
+
const existingNote = getNoteById(noteId, { skipCleanup: true });
|
|
262
|
+
if (options.preserveExistingNotes &&
|
|
263
|
+
existingNote &&
|
|
264
|
+
existingNote.contentMarkdown.trim().length > 0) {
|
|
265
|
+
getDatabase()
|
|
266
|
+
.prepare("UPDATE notes SET source_path = '' WHERE id = ? AND source_path <> ''")
|
|
267
|
+
.run(noteId);
|
|
268
|
+
syncNoteWikiArtifacts(existingNote);
|
|
269
|
+
updated += 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
getDatabase()
|
|
273
|
+
.prepare(`UPDATE notes
|
|
274
|
+
SET kind = ?, title = ?, slug = ?, space_id = ?, parent_slug = ?, index_order = ?, show_in_index = ?,
|
|
275
|
+
aliases_json = ?, summary = ?, content_markdown = ?, content_plain = ?, tags_json = ?,
|
|
276
|
+
source_path = '', frontmatter_json = ?, updated_at = ?
|
|
277
|
+
WHERE id = ?`)
|
|
278
|
+
.run(kind, title, slug, spaceId, typeof parsed.frontmatter.parentSlug === "string"
|
|
279
|
+
? parsed.frontmatter.parentSlug
|
|
280
|
+
: null, typeof parsed.frontmatter.indexOrder === "number"
|
|
281
|
+
? Math.trunc(parsed.frontmatter.indexOrder)
|
|
282
|
+
: 0, parsed.frontmatter.showInIndex === false ? 0 : 1, JSON.stringify(aliases), summary, markdown, contentPlain, JSON.stringify(tags), JSON.stringify(parsed.frontmatter), now, noteId);
|
|
283
|
+
upsertLinks(noteId, links);
|
|
284
|
+
const note = getNoteById(noteId, { skipCleanup: true });
|
|
285
|
+
if (note) {
|
|
286
|
+
syncNoteWikiArtifacts(note);
|
|
287
|
+
}
|
|
288
|
+
updated += 1;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
getDatabase()
|
|
292
|
+
.prepare(`INSERT INTO notes (
|
|
293
|
+
id, kind, title, slug, space_id, parent_slug, index_order, show_in_index, aliases_json, summary,
|
|
294
|
+
content_markdown, content_plain, author, source, tags_json, destroy_at, source_path, frontmatter_json,
|
|
295
|
+
revision_hash, last_synced_at, created_at, updated_at
|
|
296
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', ?, '', NULL, ?, ?)`)
|
|
297
|
+
.run(id, kind, title, slug, spaceId, typeof parsed.frontmatter.parentSlug === "string"
|
|
298
|
+
? parsed.frontmatter.parentSlug
|
|
299
|
+
: null, typeof parsed.frontmatter.indexOrder === "number"
|
|
300
|
+
? Math.trunc(parsed.frontmatter.indexOrder)
|
|
301
|
+
: 0, parsed.frontmatter.showInIndex === false ? 0 : 1, JSON.stringify(aliases), summary, markdown, contentPlain, typeof parsed.frontmatter.author === "string"
|
|
302
|
+
? parsed.frontmatter.author
|
|
303
|
+
: null, "system", JSON.stringify(tags), null, JSON.stringify(parsed.frontmatter), now, now);
|
|
304
|
+
upsertLinks(id, links);
|
|
305
|
+
const note = getNoteById(id, { skipCleanup: true });
|
|
306
|
+
if (note) {
|
|
307
|
+
syncNoteWikiArtifacts(note);
|
|
308
|
+
}
|
|
309
|
+
inserted += 1;
|
|
310
|
+
}
|
|
311
|
+
if (options.deleteFiles) {
|
|
312
|
+
await rm(filePath, { force: true });
|
|
313
|
+
deleted += 1;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
dataRoot: options.dataRoot,
|
|
318
|
+
scanned,
|
|
319
|
+
wouldApply: options.apply,
|
|
320
|
+
wouldDelete: options.deleteFiles,
|
|
321
|
+
inserted,
|
|
322
|
+
updated,
|
|
323
|
+
deleted,
|
|
324
|
+
backupPath,
|
|
325
|
+
backedUp,
|
|
326
|
+
skippedAlreadyImported: false
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function hasStartupImportMarker() {
|
|
330
|
+
const row = getDatabase()
|
|
331
|
+
.prepare("SELECT id FROM migrations WHERE id = ?")
|
|
332
|
+
.get(startupImportMarkerId);
|
|
333
|
+
return Boolean(row);
|
|
334
|
+
}
|
|
335
|
+
function markStartupImportComplete() {
|
|
336
|
+
getDatabase()
|
|
337
|
+
.prepare("INSERT OR IGNORE INTO migrations (id, applied_at) VALUES (?, ?)")
|
|
338
|
+
.run(startupImportMarkerId, new Date().toISOString());
|
|
339
|
+
}
|
|
340
|
+
export async function importLegacyWikiMarkdownOnStartup(dataRoot = getEffectiveDataRoot()) {
|
|
341
|
+
if (hasStartupImportMarker()) {
|
|
342
|
+
return {
|
|
343
|
+
dataRoot: path.resolve(dataRoot),
|
|
344
|
+
scanned: 0,
|
|
345
|
+
wouldApply: true,
|
|
346
|
+
wouldDelete: false,
|
|
347
|
+
inserted: 0,
|
|
348
|
+
updated: 0,
|
|
349
|
+
deleted: 0,
|
|
350
|
+
backupPath: legacyBackupPath(path.resolve(dataRoot), "legacy-wiki-markdown-pre-sqlite-import"),
|
|
351
|
+
backedUp: false,
|
|
352
|
+
skippedAlreadyImported: true
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const result = await importLegacyWikiMarkdownToSqlite({
|
|
356
|
+
dataRoot,
|
|
357
|
+
apply: true,
|
|
358
|
+
deleteFiles: false,
|
|
359
|
+
backupBeforeApply: true,
|
|
360
|
+
preserveExistingNotes: true
|
|
361
|
+
});
|
|
362
|
+
markStartupImportComplete();
|
|
363
|
+
return result;
|
|
364
|
+
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED