@tom2012/cc-web 2026.4.19-s → 2026.4.19-u
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 +1 -1
- package/backend/dist/adapters/claude-adapter.d.ts +1 -1
- package/backend/dist/adapters/claude-adapter.d.ts.map +1 -1
- package/backend/dist/adapters/claude-adapter.js +169 -11
- package/backend/dist/adapters/claude-adapter.js.map +1 -1
- package/backend/dist/adapters/gemini-adapter.js +1 -1
- package/backend/dist/adapters/gemini-adapter.js.map +1 -1
- package/backend/dist/adapters/types.d.ts +4 -1
- package/backend/dist/adapters/types.d.ts.map +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +4 -0
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/claude.d.ts.map +1 -1
- package/backend/dist/routes/claude.js +23 -1
- package/backend/dist/routes/claude.js.map +1 -1
- package/backend/dist/routes/projects.d.ts.map +1 -1
- package/backend/dist/routes/projects.js +26 -0
- package/backend/dist/routes/projects.js.map +1 -1
- package/backend/dist/routes/skillhub.d.ts +27 -0
- package/backend/dist/routes/skillhub.d.ts.map +1 -1
- package/backend/dist/routes/skillhub.js +149 -180
- package/backend/dist/routes/skillhub.js.map +1 -1
- package/backend/dist/routes/sync.d.ts +3 -0
- package/backend/dist/routes/sync.d.ts.map +1 -0
- package/backend/dist/routes/sync.js +183 -0
- package/backend/dist/routes/sync.js.map +1 -0
- package/backend/dist/sync-config.d.ts +62 -0
- package/backend/dist/sync-config.d.ts.map +1 -0
- package/backend/dist/sync-config.js +207 -0
- package/backend/dist/sync-config.js.map +1 -0
- package/backend/dist/sync-scheduler.d.ts +7 -0
- package/backend/dist/sync-scheduler.d.ts.map +1 -0
- package/backend/dist/sync-scheduler.js +157 -0
- package/backend/dist/sync-scheduler.js.map +1 -0
- package/backend/dist/sync-service.d.ts +54 -0
- package/backend/dist/sync-service.d.ts.map +1 -0
- package/backend/dist/sync-service.js +414 -0
- package/backend/dist/sync-service.js.map +1 -0
- package/backend/dist/user-prefs.d.ts +3 -0
- package/backend/dist/user-prefs.d.ts.map +1 -0
- package/backend/dist/user-prefs.js +87 -0
- package/backend/dist/user-prefs.js.map +1 -0
- package/backend/package-lock.json +26 -0
- package/backend/package.json +2 -0
- package/frontend/dist/assets/AssistantMessageContent-DuOuKxgH.js +13 -0
- package/frontend/dist/assets/{GraphPreview-DqnUioLI.js → GraphPreview-UVA4ODDv.js} +1 -1
- package/frontend/dist/assets/MobilePage-8bMFdgD9.js +14 -0
- package/frontend/dist/assets/{OfficePreview-DsfkiPFS.js → OfficePreview-DFa568OD.js} +2 -2
- package/frontend/dist/assets/{ProjectPage-eUYt7NVa.js → ProjectPage-D23Cx4LX.js} +5 -6
- package/frontend/dist/assets/SettingsPage-CTTCafJA.js +13 -0
- package/frontend/dist/assets/SkillHubPage-BBWaNxzY.js +13 -0
- package/frontend/dist/assets/{chevron-down-9-G7S6iK.js → chevron-down-XjVVezAn.js} +1 -1
- package/frontend/dist/assets/{chevron-up-BnktQ0Ve.js → chevron-up-BD4j4icv.js} +1 -1
- package/frontend/dist/assets/index-BL3iZx_u.css +1 -0
- package/frontend/dist/assets/{index-B7AIJuGG.js → index-Cz7UGH1l.js} +1 -1
- package/frontend/dist/assets/index-Qowsi3Ey.js +13 -0
- package/frontend/dist/assets/{index-Dn-YLcfD.js → index-hLxTQaCY.js} +10 -10
- package/frontend/dist/assets/{jszip.min-wfpG-66r.js → jszip.min-BvfmA-CV.js} +1 -1
- package/frontend/dist/assets/{search-Dq8YTmIg.js → search-CdJtkP3F.js} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/AssistantMessageContent-DayURBLZ.js +0 -13
- package/frontend/dist/assets/MobilePage-CIA-DkuB.js +0 -14
- package/frontend/dist/assets/SettingsPage-CCk3EySa.js +0 -13
- package/frontend/dist/assets/SkillHubPage-CRFSy-Cg.js +0 -13
- package/frontend/dist/assets/index-CboghLS5.js +0 -7
- package/frontend/dist/assets/index-pG308FQn.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-scheduler.js","sourceRoot":"","sources":["../src/sync-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type SyncDirection } from './sync-config';
|
|
2
|
+
/**
|
|
3
|
+
* rsync-driven sync service.
|
|
4
|
+
*
|
|
5
|
+
* Design choices worth noting:
|
|
6
|
+
*
|
|
7
|
+
* - **SSH options go through a wrapper script**, not through `rsync -e "ssh -p 22 -i …"`.
|
|
8
|
+
* Rsync parses the `-e` value by splitting on whitespace, so any keyPath with a space
|
|
9
|
+
* or an attacker-crafted `-oProxyCommand=…` would escalate self-config to code execution
|
|
10
|
+
* as the ccweb process. A wrapper `sh -c 'exec ssh <hardcoded opts> "$@"'` file written
|
|
11
|
+
* with user values quoted by `shell-quote` equivalent semantics removes this vector
|
|
12
|
+
* entirely.
|
|
13
|
+
* - **Password auth** uses `sshpass -e` wrapping the same script. `SSHPASS` env stays
|
|
14
|
+
* in the parent and is inherited by child only — never appears in argv or logs.
|
|
15
|
+
* - **Concurrency**: one in-flight sync per (user, project); subsequent calls return
|
|
16
|
+
* `{ skipped: true }`.
|
|
17
|
+
* - **Logs**: written via createWriteStream (ordered); file truncated to keep the last
|
|
18
|
+
* ~20 runs, preventing long-term growth.
|
|
19
|
+
* - **bidirectional**: is NOT a safe two-way sync (rsync can't). The push leg is run
|
|
20
|
+
* without --delete and with -u (update-if-newer), so remote-newer files survive the
|
|
21
|
+
* pull leg that follows. Deletions must be handled manually.
|
|
22
|
+
* - **openrsync (macOS 15+)**: shipped at `/usr/bin/rsync`, protocol 29. Doesn't support
|
|
23
|
+
* `--stats`; `-v` output doesn't include per-file lines. We use `-avzi` (itemize
|
|
24
|
+
* changes) which works on both GNU and openrsync, and parses telemetry from formats
|
|
25
|
+
* both emit (`>f`/`<f`-prefixed lines and the `total size is N` tail). A homebrew
|
|
26
|
+
* GNU rsync at `/opt/homebrew/bin/rsync` is preferred when present for richer output.
|
|
27
|
+
*/
|
|
28
|
+
export interface SyncResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
exitCode: number | null;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
bytes: number;
|
|
33
|
+
filesTransferred: number;
|
|
34
|
+
logTail: string;
|
|
35
|
+
skipped?: true;
|
|
36
|
+
reason?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Sync one project. Bidirectional runs the push leg without --delete (so the
|
|
40
|
+
* following pull can restore remote-newer files), then the pull leg.
|
|
41
|
+
*/
|
|
42
|
+
export declare function syncProject(username: string, projectId: string, projectName: string, localPath: string, overrideDirection?: SyncDirection): Promise<SyncResult>;
|
|
43
|
+
export declare function isSyncing(username: string, projectId: string): boolean;
|
|
44
|
+
export declare function listInFlight(username: string): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Non-destructive connection test: runs the wrapper script directly with
|
|
47
|
+
* `<user@host> true`. Uses the identical exec path as rsync's `-e`, so if
|
|
48
|
+
* this succeeds the sync's SSH setup will also succeed.
|
|
49
|
+
*/
|
|
50
|
+
export declare function testConnection(username: string): Promise<{
|
|
51
|
+
ok: boolean;
|
|
52
|
+
message: string;
|
|
53
|
+
}>;
|
|
54
|
+
//# sourceMappingURL=sync-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-service.d.ts","sourceRoot":"","sources":["../src/sync-service.ts"],"names":[],"mappings":"AAMA,OAAO,EAEY,KAAK,aAAa,EACpC,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAkSD;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,iBAAiB,CAAC,EAAE,aAAa,GAChC,OAAO,CAAC,UAAU,CAAC,CA6DrB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAOvD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAwChG"}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.syncProject = syncProject;
|
|
37
|
+
exports.isSyncing = isSyncing;
|
|
38
|
+
exports.listInFlight = listInFlight;
|
|
39
|
+
exports.testConnection = testConnection;
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const crypto = __importStar(require("crypto"));
|
|
45
|
+
const config_1 = require("./config");
|
|
46
|
+
const sync_config_1 = require("./sync-config");
|
|
47
|
+
const LOGS_DIR = path.join(config_1.DATA_DIR, 'sync-logs');
|
|
48
|
+
const WRAP_DIR = path.join(config_1.DATA_DIR, 'sync-ssh');
|
|
49
|
+
const LOG_MAX_BYTES = 2 * 1024 * 1024; // 2 MB per project
|
|
50
|
+
const inFlight = new Map();
|
|
51
|
+
function inFlightKey(username, projectId) {
|
|
52
|
+
return `${username}::${projectId}`;
|
|
53
|
+
}
|
|
54
|
+
function userSlug(username) {
|
|
55
|
+
return crypto.createHash('sha1').update(`ccweb-sync-user:${username}`).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
function ensureLogDir(username) {
|
|
58
|
+
const dir = path.join(LOGS_DIR, userSlug(username));
|
|
59
|
+
if (!fs.existsSync(dir))
|
|
60
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
61
|
+
return dir;
|
|
62
|
+
}
|
|
63
|
+
function ensureWrapDir() {
|
|
64
|
+
if (!fs.existsSync(WRAP_DIR))
|
|
65
|
+
fs.mkdirSync(WRAP_DIR, { recursive: true, mode: 0o700 });
|
|
66
|
+
return WRAP_DIR;
|
|
67
|
+
}
|
|
68
|
+
function resolveKeyPath(p) {
|
|
69
|
+
if (!p)
|
|
70
|
+
return null;
|
|
71
|
+
if (p.startsWith('~'))
|
|
72
|
+
return path.join(os.homedir(), p.slice(1));
|
|
73
|
+
return p;
|
|
74
|
+
}
|
|
75
|
+
/** Test that `sshpass` is on PATH; needed only for password auth. */
|
|
76
|
+
function hasSshpass() {
|
|
77
|
+
try {
|
|
78
|
+
(0, child_process_1.execSync)('command -v sshpass', { stdio: 'ignore', shell: '/bin/sh' });
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let _rsyncBin = null;
|
|
86
|
+
function detectRsyncBin() {
|
|
87
|
+
if (_rsyncBin)
|
|
88
|
+
return _rsyncBin;
|
|
89
|
+
// Prefer homebrew GNU rsync; it has `--stats`, per-file `-v` output, and
|
|
90
|
+
// itemize changes that exactly matches the regex we use.
|
|
91
|
+
const candidates = ['/opt/homebrew/bin/rsync', '/usr/local/bin/rsync', '/usr/bin/rsync', 'rsync'];
|
|
92
|
+
for (const p of candidates) {
|
|
93
|
+
try {
|
|
94
|
+
const out = (0, child_process_1.execSync)(`${p} --version 2>&1 | head -1`, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'], shell: '/bin/sh' }).trim();
|
|
95
|
+
if (!out)
|
|
96
|
+
continue;
|
|
97
|
+
const isOpenrsync = /openrsync/i.test(out);
|
|
98
|
+
_rsyncBin = { path: p, isOpenrsync, versionLine: out };
|
|
99
|
+
return _rsyncBin;
|
|
100
|
+
}
|
|
101
|
+
catch { /* try next */ }
|
|
102
|
+
}
|
|
103
|
+
// Last resort — will fail at spawn time with a clear error.
|
|
104
|
+
_rsyncBin = { path: 'rsync', isOpenrsync: false, versionLine: 'unknown' };
|
|
105
|
+
return _rsyncBin;
|
|
106
|
+
}
|
|
107
|
+
// ── SSH wrapper script ───────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* POSIX shell single-quoting: wrap in `'…'` and replace any embedded `'`
|
|
110
|
+
* with `'\''`. Safe for all byte strings.
|
|
111
|
+
*/
|
|
112
|
+
function shSingleQuote(s) {
|
|
113
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Write (or refresh) a per-user SSH wrapper script. Rsync's `-e` takes a
|
|
117
|
+
* single token that it splits on whitespace; by passing it a path to this
|
|
118
|
+
* script, the rsync subprocess exec's the script as a *command*, passing the
|
|
119
|
+
* `[user@host, remote-command...]` tail as positional args. The script
|
|
120
|
+
* itself hardcodes all SSH options in shell-quoted form, so no user value is
|
|
121
|
+
* ever re-tokenized by anything between us and ssh.
|
|
122
|
+
*
|
|
123
|
+
* For password auth the same script prepends `sshpass -e`; SSHPASS is read
|
|
124
|
+
* from the inherited env at run time.
|
|
125
|
+
*
|
|
126
|
+
* Returns the absolute path to the script, written 0700.
|
|
127
|
+
*/
|
|
128
|
+
function writeSshWrapper(cfg) {
|
|
129
|
+
ensureWrapDir();
|
|
130
|
+
const knownHostsFile = path.join(os.homedir(), '.ssh', 'known_hosts_ccweb');
|
|
131
|
+
const parts = [];
|
|
132
|
+
if (cfg.authMethod === 'password')
|
|
133
|
+
parts.push('sshpass', '-e');
|
|
134
|
+
parts.push('ssh');
|
|
135
|
+
parts.push('-p', String(cfg.port));
|
|
136
|
+
parts.push('-o', 'StrictHostKeyChecking=accept-new');
|
|
137
|
+
parts.push('-o', `UserKnownHostsFile=${knownHostsFile}`);
|
|
138
|
+
parts.push('-o', 'BatchMode=yes');
|
|
139
|
+
parts.push('-o', 'ConnectTimeout=20');
|
|
140
|
+
if (cfg.authMethod === 'key') {
|
|
141
|
+
const keyPath = resolveKeyPath(cfg.keyPath);
|
|
142
|
+
if (keyPath) {
|
|
143
|
+
parts.push('-i', keyPath);
|
|
144
|
+
parts.push('-o', 'IdentitiesOnly=yes');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Quote every component once, here, and never string-concat user data again
|
|
148
|
+
const quoted = parts.map(shSingleQuote).join(' ');
|
|
149
|
+
const script = `#!/bin/sh\nexec ${quoted} "$@"\n`;
|
|
150
|
+
const file = path.join(WRAP_DIR, `${userSlug(cfg.username)}.sh`);
|
|
151
|
+
fs.writeFileSync(file, script, { mode: 0o700 });
|
|
152
|
+
return file;
|
|
153
|
+
}
|
|
154
|
+
// ── Log rotation ────────────────────────────────────────────────────────────
|
|
155
|
+
function rotateLogIfLarge(logFile) {
|
|
156
|
+
try {
|
|
157
|
+
const stat = fs.statSync(logFile);
|
|
158
|
+
if (stat.size > LOG_MAX_BYTES) {
|
|
159
|
+
// Keep the last half by reading tail and overwriting; avoids
|
|
160
|
+
// renaming (which would leave a .old file on disk forever).
|
|
161
|
+
const fd = fs.openSync(logFile, 'r');
|
|
162
|
+
const halfSize = Math.floor(LOG_MAX_BYTES / 2);
|
|
163
|
+
const buf = Buffer.alloc(halfSize);
|
|
164
|
+
fs.readSync(fd, buf, 0, halfSize, stat.size - halfSize);
|
|
165
|
+
fs.closeSync(fd);
|
|
166
|
+
// Align to next newline so we don't start mid-line
|
|
167
|
+
const nl = buf.indexOf(0x0a);
|
|
168
|
+
const tail = nl >= 0 ? buf.subarray(nl + 1) : buf;
|
|
169
|
+
fs.writeFileSync(logFile, Buffer.concat([Buffer.from('===== (older entries rotated) =====\n'), tail]));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch { /* missing file or read failure — no-op */ }
|
|
173
|
+
}
|
|
174
|
+
function buildRsyncArgs(cfg, localPath, folderName, excludes, direction, bidirectionalLeg) {
|
|
175
|
+
const wrapper = writeSshWrapper(cfg);
|
|
176
|
+
// `-a` archive, `-v` verbose, `-z` compress, `-i` itemize changes.
|
|
177
|
+
// `-i` prints one `<11-char-flags> path` line per changed path on both GNU
|
|
178
|
+
// and openrsync, so telemetry parsing works identically on both. macOS
|
|
179
|
+
// openrsync doesn't support `--stats`, so we avoid it.
|
|
180
|
+
const args = ['-avzi'];
|
|
181
|
+
args.push('-e', wrapper);
|
|
182
|
+
for (const pat of excludes) {
|
|
183
|
+
const trimmed = pat.trim();
|
|
184
|
+
if (trimmed)
|
|
185
|
+
args.push('--exclude', trimmed);
|
|
186
|
+
}
|
|
187
|
+
const localSpec = localPath.endsWith('/') ? localPath : localPath + '/';
|
|
188
|
+
// path.posix.join on sanitized name; folderName is already validated to
|
|
189
|
+
// contain no `..` / separators.
|
|
190
|
+
const remoteSpec = `${cfg.user}@${cfg.host}:${path.posix.join(cfg.remoteRoot, folderName)}/`;
|
|
191
|
+
if (direction === 'push') {
|
|
192
|
+
// Only the pure-push path uses --delete (mirror semantics). The push leg
|
|
193
|
+
// of "bidirectional" must not delete remote-newer files the pull leg is
|
|
194
|
+
// about to bring back.
|
|
195
|
+
if (!bidirectionalLeg)
|
|
196
|
+
args.push('--delete');
|
|
197
|
+
else
|
|
198
|
+
args.push('-u');
|
|
199
|
+
args.push(localSpec, remoteSpec);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
// pull: never --delete (protects local work-in-progress)
|
|
203
|
+
args.push(remoteSpec, localSpec);
|
|
204
|
+
}
|
|
205
|
+
const env = { ...process.env };
|
|
206
|
+
if (cfg.authMethod === 'password' && cfg.passwordEnc) {
|
|
207
|
+
const pw = (0, sync_config_1.decryptPassword)(cfg.passwordEnc, cfg.passwordFp);
|
|
208
|
+
if (pw)
|
|
209
|
+
env.SSHPASS = pw;
|
|
210
|
+
}
|
|
211
|
+
return { args, env };
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Telemetry parse from rsync -avzi output.
|
|
215
|
+
*
|
|
216
|
+
* - **filesTransferred**: lines beginning with `[<>ch*.]f` (itemize-changes
|
|
217
|
+
* format; the first char indicates direction of update, second char is
|
|
218
|
+
* type=file). Matches both GNU rsync and openrsync output.
|
|
219
|
+
* - **bytes**: prefer `total size is N` (user-facing "how much data moved"),
|
|
220
|
+
* fall back to `sent N bytes` / `received N bytes` depending on direction.
|
|
221
|
+
* `total size is` is authoritative on both rsync variants.
|
|
222
|
+
*/
|
|
223
|
+
function parseRsyncOutput(combined, direction) {
|
|
224
|
+
// itemize-changes lines: <11 flag chars> <space> <path>
|
|
225
|
+
// First char: >/< (update local/remote), c (created), h (hardlink), * (message), . (unchanged)
|
|
226
|
+
// Second char: f (file), d (dir), L (symlink), D (device), S (special)
|
|
227
|
+
// We want "file transferred" = first char is >/< and second is f.
|
|
228
|
+
const fileMatches = combined.match(/^[<>ch][fdLDS]\S*\s+\S/gm) ?? [];
|
|
229
|
+
const files = fileMatches.filter((line) => /^[<>ch]f/.test(line)).length;
|
|
230
|
+
// Bytes: prefer total size (user-meaningful), fallback to sent/received.
|
|
231
|
+
const totalMatch = combined.match(/total size is\s+([\d,]+)/i);
|
|
232
|
+
let bytes = 0;
|
|
233
|
+
if (totalMatch) {
|
|
234
|
+
bytes = parseInt(totalMatch[1].replace(/,/g, ''), 10);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// sent/received lines: `sent X bytes received Y bytes ...`
|
|
238
|
+
const sentMatch = combined.match(/sent\s+([\d,]+)\s+bytes/i);
|
|
239
|
+
const recvMatch = combined.match(/received\s+([\d,]+)\s+bytes/i);
|
|
240
|
+
const sent = sentMatch ? parseInt(sentMatch[1].replace(/,/g, ''), 10) : 0;
|
|
241
|
+
const recv = recvMatch ? parseInt(recvMatch[1].replace(/,/g, ''), 10) : 0;
|
|
242
|
+
// Push: local sends file data; Pull: local receives file data. Pick the
|
|
243
|
+
// direction-appropriate number rather than `sent` always.
|
|
244
|
+
bytes = direction === 'pull' ? recv : sent;
|
|
245
|
+
}
|
|
246
|
+
return { bytes, files };
|
|
247
|
+
}
|
|
248
|
+
async function runOne(cfg, projectId, folderName, localPath, direction, bidirectionalLeg) {
|
|
249
|
+
const excludes = [...cfg.defaultExcludes, ...(cfg.projectExcludes[projectId] ?? [])];
|
|
250
|
+
const { args, env } = buildRsyncArgs(cfg, localPath, folderName, excludes, direction, bidirectionalLeg);
|
|
251
|
+
if (cfg.authMethod === 'password' && !env.SSHPASS) {
|
|
252
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', reason: 'password-decrypt-failed' };
|
|
253
|
+
}
|
|
254
|
+
const logDir = ensureLogDir(cfg.username);
|
|
255
|
+
const logFile = path.join(logDir, `${projectId}.log`);
|
|
256
|
+
rotateLogIfLarge(logFile);
|
|
257
|
+
const bin = detectRsyncBin();
|
|
258
|
+
const startStamp = new Date().toISOString();
|
|
259
|
+
const header = `\n===== ${startStamp} ${direction.toUpperCase()}${bidirectionalLeg ? '(bidi)' : ''} ${folderName} (${bin.versionLine}) =====\n`;
|
|
260
|
+
const started = Date.now();
|
|
261
|
+
return await new Promise((resolve) => {
|
|
262
|
+
let combined = '';
|
|
263
|
+
const logStream = fs.createWriteStream(logFile, { flags: 'a', mode: 0o600 });
|
|
264
|
+
logStream.write(header);
|
|
265
|
+
const child = (0, child_process_1.spawn)(bin.path, args, { env, cwd: os.homedir(), stdio: ['ignore', 'pipe', 'pipe'] });
|
|
266
|
+
const onData = (buf) => {
|
|
267
|
+
const s = buf.toString();
|
|
268
|
+
combined += s;
|
|
269
|
+
logStream.write(s);
|
|
270
|
+
};
|
|
271
|
+
child.stdout.on('data', onData);
|
|
272
|
+
child.stderr.on('data', onData);
|
|
273
|
+
child.on('error', (err) => {
|
|
274
|
+
logStream.write(`spawn error: ${err.message}\n`);
|
|
275
|
+
logStream.end();
|
|
276
|
+
resolve({ ok: false, exitCode: null, durationMs: Date.now() - started, bytes: 0, filesTransferred: 0, logTail: err.message, reason: 'spawn-failed' });
|
|
277
|
+
});
|
|
278
|
+
child.on('close', (code) => {
|
|
279
|
+
logStream.end();
|
|
280
|
+
const ok = code === 0;
|
|
281
|
+
const { bytes, files } = parseRsyncOutput(combined, direction);
|
|
282
|
+
const logTail = combined.split('\n').slice(-30).join('\n');
|
|
283
|
+
resolve({ ok, exitCode: code, durationMs: Date.now() - started, bytes, filesTransferred: files, logTail });
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
288
|
+
/**
|
|
289
|
+
* Sync one project. Bidirectional runs the push leg without --delete (so the
|
|
290
|
+
* following pull can restore remote-newer files), then the pull leg.
|
|
291
|
+
*/
|
|
292
|
+
async function syncProject(username, projectId, projectName, localPath, overrideDirection) {
|
|
293
|
+
const key = inFlightKey(username, projectId);
|
|
294
|
+
if (inFlight.has(key)) {
|
|
295
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', skipped: true, reason: 'already-syncing' };
|
|
296
|
+
}
|
|
297
|
+
const cfg = (0, sync_config_1.getSyncConfig)(username);
|
|
298
|
+
// Preflight validation — fail fast with a clear reason instead of spawning
|
|
299
|
+
// rsync and watching it error cryptically.
|
|
300
|
+
if (!cfg.host || !cfg.user || !cfg.remoteRoot) {
|
|
301
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', reason: 'incomplete-config' };
|
|
302
|
+
}
|
|
303
|
+
// keyPath is OPTIONAL for key auth (empty → default agent / ~/.ssh/id_*).
|
|
304
|
+
// BUT if the user specified one, verify it exists — ssh will otherwise
|
|
305
|
+
// silently ignore a missing -i file and fall back to the agent, creating
|
|
306
|
+
// the illusion that their configured key is being used.
|
|
307
|
+
if (cfg.authMethod === 'key' && cfg.keyPath) {
|
|
308
|
+
const resolved = resolveKeyPath(cfg.keyPath);
|
|
309
|
+
if (resolved && !fs.existsSync(resolved)) {
|
|
310
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', reason: 'key-path-not-found' };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (cfg.authMethod === 'password' && !hasSshpass()) {
|
|
314
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', reason: 'sshpass-not-installed' };
|
|
315
|
+
}
|
|
316
|
+
if (!fs.existsSync(localPath)) {
|
|
317
|
+
return { ok: false, exitCode: null, durationMs: 0, bytes: 0, filesTransferred: 0, logTail: '', reason: 'local-path-missing' };
|
|
318
|
+
}
|
|
319
|
+
// Folder name on the remote: prefer the project display name, fall back to
|
|
320
|
+
// basename then projectId. Must be sanitized or we'd let a project name
|
|
321
|
+
// containing `..` escape the remote root.
|
|
322
|
+
const rawName = projectName || path.basename(localPath) || projectId;
|
|
323
|
+
const folderName = (0, sync_config_1.sanitizeFolderName)(rawName) ?? projectId;
|
|
324
|
+
const direction = overrideDirection ?? cfg.direction;
|
|
325
|
+
const job = (async () => {
|
|
326
|
+
if (direction === 'bidirectional') {
|
|
327
|
+
const push = await runOne(cfg, projectId, folderName, localPath, 'push', true);
|
|
328
|
+
if (!push.ok)
|
|
329
|
+
return push;
|
|
330
|
+
const pull = await runOne(cfg, projectId, folderName, localPath, 'pull', true);
|
|
331
|
+
return {
|
|
332
|
+
ok: pull.ok,
|
|
333
|
+
exitCode: pull.exitCode,
|
|
334
|
+
durationMs: push.durationMs + pull.durationMs,
|
|
335
|
+
bytes: push.bytes + pull.bytes,
|
|
336
|
+
filesTransferred: push.filesTransferred + pull.filesTransferred,
|
|
337
|
+
logTail: `[PUSH]\n${push.logTail}\n[PULL]\n${pull.logTail}`,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return runOne(cfg, projectId, folderName, localPath, direction, false);
|
|
341
|
+
})();
|
|
342
|
+
inFlight.set(key, job);
|
|
343
|
+
try {
|
|
344
|
+
return await job;
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
inFlight.delete(key);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function isSyncing(username, projectId) {
|
|
351
|
+
return inFlight.has(inFlightKey(username, projectId));
|
|
352
|
+
}
|
|
353
|
+
function listInFlight(username) {
|
|
354
|
+
const prefix = `${username}::`;
|
|
355
|
+
const out = [];
|
|
356
|
+
for (const k of inFlight.keys()) {
|
|
357
|
+
if (k.startsWith(prefix))
|
|
358
|
+
out.push(k.slice(prefix.length));
|
|
359
|
+
}
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Non-destructive connection test: runs the wrapper script directly with
|
|
364
|
+
* `<user@host> true`. Uses the identical exec path as rsync's `-e`, so if
|
|
365
|
+
* this succeeds the sync's SSH setup will also succeed.
|
|
366
|
+
*/
|
|
367
|
+
async function testConnection(username) {
|
|
368
|
+
const cfg = (0, sync_config_1.getSyncConfig)(username);
|
|
369
|
+
if (!cfg.host || !cfg.user)
|
|
370
|
+
return { ok: false, message: '未配置 host/user' };
|
|
371
|
+
if (cfg.authMethod === 'key' && cfg.keyPath) {
|
|
372
|
+
const resolved = resolveKeyPath(cfg.keyPath);
|
|
373
|
+
if (resolved && !fs.existsSync(resolved)) {
|
|
374
|
+
return { ok: false, message: `SSH 私钥文件不存在: ${resolved}` };
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (cfg.authMethod === 'password' && !hasSshpass()) {
|
|
378
|
+
return { ok: false, message: '未安装 sshpass(密码认证需要)。请用 key 认证或安装 sshpass。' };
|
|
379
|
+
}
|
|
380
|
+
if (cfg.authMethod === 'password' && cfg.passwordEnc) {
|
|
381
|
+
const pw = (0, sync_config_1.decryptPassword)(cfg.passwordEnc, cfg.passwordFp);
|
|
382
|
+
if (!pw)
|
|
383
|
+
return { ok: false, message: '密码解密失败(服务端密钥可能已轮换)。请重新输入密码。' };
|
|
384
|
+
}
|
|
385
|
+
const wrapper = writeSshWrapper(cfg);
|
|
386
|
+
const env = { ...process.env };
|
|
387
|
+
if (cfg.authMethod === 'password' && cfg.passwordEnc) {
|
|
388
|
+
const pw = (0, sync_config_1.decryptPassword)(cfg.passwordEnc, cfg.passwordFp);
|
|
389
|
+
if (pw)
|
|
390
|
+
env.SSHPASS = pw;
|
|
391
|
+
}
|
|
392
|
+
return await new Promise((resolve) => {
|
|
393
|
+
let err = '';
|
|
394
|
+
// Invoke the wrapper directly — same exec path as rsync's -e would.
|
|
395
|
+
const child = (0, child_process_1.spawn)(wrapper, [`${cfg.user}@${cfg.host}`, 'true'], { env, stdio: ['ignore', 'ignore', 'pipe'] });
|
|
396
|
+
const timeout = setTimeout(() => {
|
|
397
|
+
try {
|
|
398
|
+
child.kill('SIGTERM');
|
|
399
|
+
}
|
|
400
|
+
catch { /* ignore */ }
|
|
401
|
+
resolve({ ok: false, message: '连接超时(30s)' });
|
|
402
|
+
}, 30000);
|
|
403
|
+
child.stderr.on('data', (b) => { err += b.toString(); });
|
|
404
|
+
child.on('error', (e) => { clearTimeout(timeout); resolve({ ok: false, message: `spawn 失败: ${e.message}` }); });
|
|
405
|
+
child.on('close', (code) => {
|
|
406
|
+
clearTimeout(timeout);
|
|
407
|
+
if (code === 0)
|
|
408
|
+
resolve({ ok: true, message: '连接成功' });
|
|
409
|
+
else
|
|
410
|
+
resolve({ ok: false, message: err.trim() || `ssh 退出码 ${code}` });
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
//# sourceMappingURL=sync-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-service.js","sourceRoot":"","sources":["../src/sync-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-prefs.d.ts","sourceRoot":"","sources":["../src/user-prefs.ts"],"names":[],"mappings":"AA0CA,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAGrF;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAK/E"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getUserPref = getUserPref;
|
|
37
|
+
exports.setUserPref = setUserPref;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
/**
|
|
42
|
+
* Per-user preferences persisted across restarts.
|
|
43
|
+
*
|
|
44
|
+
* Shape on disk: `{ [username]: { [key]: value } }`
|
|
45
|
+
*
|
|
46
|
+
* Keeps tiny, non-secret client-side state (project order, ui prefs) in one
|
|
47
|
+
* file rather than scattering one file per user × feature. Not for anything
|
|
48
|
+
* security-sensitive — no auth data here.
|
|
49
|
+
*/
|
|
50
|
+
const PREFS_FILE = path.join(config_1.DATA_DIR, 'user-prefs.json');
|
|
51
|
+
let cache = null;
|
|
52
|
+
let cacheMtime = 0;
|
|
53
|
+
function readAll() {
|
|
54
|
+
try {
|
|
55
|
+
const stat = fs.statSync(PREFS_FILE);
|
|
56
|
+
if (cache && stat.mtimeMs === cacheMtime)
|
|
57
|
+
return cache;
|
|
58
|
+
const raw = fs.readFileSync(PREFS_FILE, 'utf-8');
|
|
59
|
+
cache = JSON.parse(raw);
|
|
60
|
+
cacheMtime = stat.mtimeMs;
|
|
61
|
+
return cache;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
cache = {};
|
|
65
|
+
cacheMtime = 0;
|
|
66
|
+
return cache;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function writeAll(prefs) {
|
|
70
|
+
(0, config_1.atomicWriteSync)(PREFS_FILE, JSON.stringify(prefs, null, 2));
|
|
71
|
+
cache = prefs;
|
|
72
|
+
try {
|
|
73
|
+
cacheMtime = fs.statSync(PREFS_FILE).mtimeMs;
|
|
74
|
+
}
|
|
75
|
+
catch { /* ignore */ }
|
|
76
|
+
}
|
|
77
|
+
function getUserPref(username, key) {
|
|
78
|
+
const all = readAll();
|
|
79
|
+
return all[username]?.[key];
|
|
80
|
+
}
|
|
81
|
+
function setUserPref(username, key, value) {
|
|
82
|
+
const all = readAll();
|
|
83
|
+
const forUser = { ...(all[username] ?? {}) };
|
|
84
|
+
forUser[key] = value;
|
|
85
|
+
writeAll({ ...all, [username]: forUser });
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=user-prefs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-prefs.js","sourceRoot":"","sources":["../src/user-prefs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,kCAGC;AAED,kCAKC;AApDD,uCAAyB;AACzB,2CAA6B;AAC7B,qCAAqD;AAErD;;;;;;;;GAQG;AAEH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAQ,EAAE,iBAAiB,CAAC,CAAC;AAI1D,IAAI,KAAK,GAAiB,IAAI,CAAC;AAC/B,IAAI,UAAU,GAAG,CAAC,CAAC;AAEnB,SAAS,OAAO;IACd,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QACrC,IAAI,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU;YAAE,OAAO,KAAK,CAAC;QACvD,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACjD,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAU,CAAC;QACjC,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,KAAK,GAAG,EAAE,CAAC;QACX,UAAU,GAAG,CAAC,CAAC;QACf,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAAY;IAC5B,IAAA,wBAAe,EAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5D,KAAK,GAAG,KAAK,CAAC;IACd,IAAI,CAAC;QAAC,UAAU,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;AAC9E,CAAC;AAED,SAAgB,WAAW,CAAc,QAAgB,EAAE,GAAW;IACpE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAkB,CAAC;AAC/C,CAAC;AAED,SAAgB,WAAW,CAAC,QAAgB,EAAE,GAAW,EAAE,KAAc;IACvE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,OAAO,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACrB,QAAQ,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5C,CAAC"}
|