@fudrouter/fsrouter 0.6.93 → 0.6.95
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/routes/db/route.js +43 -5
- package/package.json +1 -1
- package/scripts/postinstall.cjs +37 -35
package/dist/routes/db/route.js
CHANGED
|
@@ -72,13 +72,51 @@ export async function POST_handler(req, res) {
|
|
|
72
72
|
if (db && typeof db.close === "function")
|
|
73
73
|
db.close();
|
|
74
74
|
}
|
|
75
|
-
// 2. Remove active -wal and -shm files to prevent corruption/mismatch
|
|
75
|
+
// 2. Remove active -wal and -shm files to prevent corruption/mismatch.
|
|
76
|
+
// On Windows these files can be locked by the OS/AV/SQLite even after
|
|
77
|
+
// db.close() (EBUSY). Use a retry + rename fallback so restore never
|
|
78
|
+
// hard-fails on a transient lock.
|
|
76
79
|
const walFile = path.join(DATA_DIR, "db", "data.sqlite-wal");
|
|
77
80
|
const shmFile = path.join(DATA_DIR, "db", "data.sqlite-shm");
|
|
78
|
-
|
|
79
|
-
fs.
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
const safeUnlink = (file) => {
|
|
82
|
+
if (!fs.existsSync(file))
|
|
83
|
+
return;
|
|
84
|
+
// Give the DB a moment to fully release the file handle.
|
|
85
|
+
const attempts = process.platform === "win32" ? 12 : 3;
|
|
86
|
+
for (let i = 0; i < attempts; i++) {
|
|
87
|
+
try {
|
|
88
|
+
fs.unlinkSync(file);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
if (e.code === "EBUSY" || e.code === "EPERM" || e.code === "ETXTBSY") {
|
|
93
|
+
// Fallback: rename out of the way (atomic on NTFS) then retry unlink.
|
|
94
|
+
try {
|
|
95
|
+
const tmp = `${file}.old-${Date.now()}`;
|
|
96
|
+
fs.renameSync(file, tmp);
|
|
97
|
+
try {
|
|
98
|
+
fs.unlinkSync(tmp);
|
|
99
|
+
}
|
|
100
|
+
catch { /* ignore */ }
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Wait and retry — the lock may be released shortly.
|
|
105
|
+
try {
|
|
106
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
|
|
107
|
+
}
|
|
108
|
+
catch { /* noop */ }
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Last resort: leave the file; SQLite rebuilds -wal/-shm on next open.
|
|
116
|
+
console.warn(`[Restore] Could not remove ${file} (locked) — continuing.`);
|
|
117
|
+
};
|
|
118
|
+
safeUnlink(walFile);
|
|
119
|
+
safeUnlink(shmFile);
|
|
82
120
|
// 3. Restore secrets/raw DB only for legacy backups. Do not overwrite the
|
|
83
121
|
// logical import with a stale/incompatible SQLite file.
|
|
84
122
|
const filesToRestore = [
|
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Post-install: wire automation symlinks so
|
|
4
|
-
* Python venv, source tree and browser profiles
|
|
3
|
+
* Post-install: wire automation symlinks so the signup scripts can find the
|
|
4
|
+
* Python venv, source tree and browser profiles regardless of the process cwd.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The automation routes resolve paths relative to process.cwd()
|
|
7
|
+
* (e.g. .venv/bin/python, src/automation/*.py, profiles/<provider>). When the
|
|
8
|
+
* server runs under pm2 the cwd is the installed package directory
|
|
9
|
+
* (/usr/local/lib/node_modules/@fudrouter/fsrouter), NOT /root — so we create
|
|
10
|
+
* the symlinks in BOTH locations to be cwd-independent.
|
|
10
11
|
*
|
|
11
|
-
* Idempotent and non-fatal: never throws (npm install must succeed even if
|
|
12
|
-
*
|
|
12
|
+
* Idempotent and non-fatal: never throws (npm install must succeed even if a
|
|
13
|
+
* symlink can't be created).
|
|
13
14
|
*/
|
|
14
15
|
const fs = require("fs");
|
|
15
16
|
const path = require("path");
|
|
@@ -19,26 +20,30 @@ function log(msg) {
|
|
|
19
20
|
console.log(`[fsrouter:postinstall] ${msg}`);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function isSymlink(p) {
|
|
24
|
+
try {
|
|
25
|
+
return fs.lstatSync(p).isSymbolicLink();
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
function symlinkIfMissing(linkPath, targetPath) {
|
|
23
32
|
try {
|
|
24
33
|
if (!fs.existsSync(targetPath)) {
|
|
25
|
-
// target missing —
|
|
26
|
-
if (
|
|
27
|
-
// leave existing link alone; just warn if it points nowhere
|
|
28
|
-
if (!fs.existsSync(linkPath)) fs.unlinkSync(linkPath);
|
|
29
|
-
}
|
|
34
|
+
// target missing — remove a dangling link if present, then skip
|
|
35
|
+
if (isSymlink(linkPath)) fs.unlinkSync(linkPath);
|
|
30
36
|
log(`skip ${linkPath} (target ${targetPath} not found)`);
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
39
|
if (isSymlink(linkPath)) {
|
|
34
|
-
|
|
35
|
-
if (cur === targetPath) {
|
|
40
|
+
if (fs.readlinkSync(linkPath) === targetPath) {
|
|
36
41
|
log(`ok ${linkPath} -> ${targetPath}`);
|
|
37
42
|
return;
|
|
38
43
|
}
|
|
39
44
|
fs.unlinkSync(linkPath);
|
|
40
45
|
} else if (fs.existsSync(linkPath)) {
|
|
41
|
-
log(`skip ${linkPath} (exists and is not a symlink)`);
|
|
46
|
+
log(`skip ${linkPath} (exists and is not a symlink — leaving untouched)`);
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
49
|
fs.symlinkSync(targetPath, linkPath, "dir");
|
|
@@ -48,20 +53,9 @@ function symlinkIfMissing(linkPath, targetPath) {
|
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
function isSymlink(p) {
|
|
52
|
-
try {
|
|
53
|
-
return fs.lstatSync(p).isSymbolicLink();
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
56
|
function main() {
|
|
60
|
-
//
|
|
61
|
-
const candidates = [
|
|
62
|
-
"/root/AMRouter",
|
|
63
|
-
path.resolve(__dirname, ".."), // package root when installed
|
|
64
|
-
];
|
|
57
|
+
// Real AMRouter checkout (where the venv + browser profiles live)
|
|
58
|
+
const candidates = ["/root/AMRouter", path.resolve(__dirname, "..", "..")];
|
|
65
59
|
let base = null;
|
|
66
60
|
for (const c of candidates) {
|
|
67
61
|
if (fs.existsSync(c)) {
|
|
@@ -75,12 +69,20 @@ function main() {
|
|
|
75
69
|
}
|
|
76
70
|
log(`base=${base}`);
|
|
77
71
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
72
|
+
const venvTarget = path.join(base, ".venv");
|
|
73
|
+
const srcTarget = path.join(base, "backend", "src");
|
|
74
|
+
const profilesTarget = path.join(base, "backend", "profiles");
|
|
75
|
+
|
|
76
|
+
// Locations to symlink from (cwd-independent coverage)
|
|
77
|
+
const linkBases = ["/root", process.cwd()]; // /root + the dir npm ran install in (global package dir under pm2)
|
|
78
|
+
|
|
79
|
+
for (const lb of linkBases) {
|
|
80
|
+
symlinkIfMissing(path.join(lb, ".venv"), venvTarget);
|
|
81
|
+
// src only if it doesn't already exist as a real dir (package ships src/)
|
|
82
|
+
const srcLink = path.join(lb, "src");
|
|
83
|
+
if (!fs.existsSync(srcLink)) symlinkIfMissing(srcLink, srcTarget);
|
|
84
|
+
symlinkIfMissing(path.join(lb, "profiles"), profilesTarget);
|
|
85
|
+
}
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
try {
|