qtex 1.1.8 → 1.1.10
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/index.js +4 -2
- package/package.json +1 -1
- package/src/updater.js +5 -0
- package/src/ws-client.js +38 -5
package/index.js
CHANGED
|
@@ -18,7 +18,8 @@ const optionsSchema = {
|
|
|
18
18
|
output: { type: 'string', short: 'o' },
|
|
19
19
|
help: { type: 'boolean', short: 'h' },
|
|
20
20
|
update: { type: 'boolean', short: 'u' },
|
|
21
|
-
version: { type: 'boolean', short: 'v' }
|
|
21
|
+
version: { type: 'boolean', short: 'v' },
|
|
22
|
+
server: { type: 'string', short: 's' }
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
async function main() {
|
|
@@ -41,6 +42,7 @@ ${colors.bold}USAGE:${colors.reset}
|
|
|
41
42
|
${colors.bold}OPTIONS:${colors.reset}
|
|
42
43
|
-w, --watch Watch for changes and recompile
|
|
43
44
|
-o, --output <file> Define output filename (default: output.pdf)
|
|
45
|
+
-s, --server <url> Custom WebSocket Server URL
|
|
44
46
|
-u, --update Update to the latest version
|
|
45
47
|
-v, --version Show version information
|
|
46
48
|
-h, --help Show this help message
|
|
@@ -61,7 +63,7 @@ ${colors.bold}OPTIONS:${colors.reset}
|
|
|
61
63
|
|
|
62
64
|
if (values.watch) {
|
|
63
65
|
const { TachyonWS } = await import('./src/ws-client.js');
|
|
64
|
-
const wsClient = new TachyonWS(directory, values);
|
|
66
|
+
const wsClient = new TachyonWS(directory, { ...values, serverUrl: values.server });
|
|
65
67
|
await wsClient.connect();
|
|
66
68
|
|
|
67
69
|
const server = startServer();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qtex",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.10",
|
|
4
4
|
"description": "Ultra-fast cloud LaTeX compiler. Compile .tex documents in milliseconds without installing TeXLive or MikTeX. Zero config, sub-second builds, live watch mode.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
package/src/updater.js
CHANGED
|
@@ -94,6 +94,11 @@ export async function selfUpdate(currentVersion) {
|
|
|
94
94
|
const latestVersion = data.tag_name.replace('v', '');
|
|
95
95
|
|
|
96
96
|
if (!isNewer(latestVersion, currentVersion)) {
|
|
97
|
+
if (isNewer(currentVersion, latestVersion)) {
|
|
98
|
+
ui.warn(`You are using a version (${colors.bold}v${currentVersion}${colors.reset}) that is ahead of the latest release (${colors.bold}v${latestVersion}${colors.reset}).`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
ui.success(`qtex is already up to date (${colors.bold}v${currentVersion}${colors.reset}).`);
|
|
98
103
|
return;
|
|
99
104
|
}
|
package/src/ws-client.js
CHANGED
|
@@ -13,6 +13,8 @@ export class TachyonWS {
|
|
|
13
13
|
this.isConnected = false;
|
|
14
14
|
this.spinner = new Spinner('');
|
|
15
15
|
this.isCompiling = false;
|
|
16
|
+
this.localHashes = new Map(); // path -> local SHA256 (to detect changes)
|
|
17
|
+
this.serverHashes = new Map(); // path -> server XXHash (to send HashRef)
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
async connect() {
|
|
@@ -30,6 +32,13 @@ export class TachyonWS {
|
|
|
30
32
|
const data = JSON.parse(event.data);
|
|
31
33
|
|
|
32
34
|
if (data.type === 'compile_success') {
|
|
35
|
+
// Update Server Hashes from response
|
|
36
|
+
if (data.blobs) {
|
|
37
|
+
for (const [path, hash] of Object.entries(data.blobs)) {
|
|
38
|
+
this.serverHashes.set(path, hash);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
if (this.spinner.timer) {
|
|
34
43
|
this.spinner.succeed(`${colors.green}PDF updated in ${colors.bold}${data.compile_time_ms}ms${colors.reset}`);
|
|
35
44
|
}
|
|
@@ -76,6 +85,14 @@ export class TachyonWS {
|
|
|
76
85
|
|
|
77
86
|
try {
|
|
78
87
|
const project = await this.buildProjectPayload();
|
|
88
|
+
// Count strings vs objects to see optimization
|
|
89
|
+
const fileCount = Object.keys(project.files).length;
|
|
90
|
+
const optimizedCount = Object.values(project.files).filter(v => typeof v === 'object' && v.type === 'hash').length;
|
|
91
|
+
|
|
92
|
+
if (optimizedCount > 0) {
|
|
93
|
+
this.spinner.update(`${colors.blue}Syncing project (${colors.green}${optimizedCount}/${fileCount} cached${colors.blue})...`);
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
this.socket.send(JSON.stringify(project));
|
|
80
97
|
} catch (e) {
|
|
81
98
|
this.spinner.fail(`Failed to sync: ${e.message}`);
|
|
@@ -86,8 +103,10 @@ export class TachyonWS {
|
|
|
86
103
|
async buildProjectPayload() {
|
|
87
104
|
const absoluteDir = resolve(this.directory);
|
|
88
105
|
const files = await this.getProjectFiles(absoluteDir);
|
|
106
|
+
const { createHash } = await import('node:crypto');
|
|
107
|
+
|
|
89
108
|
const payload = {
|
|
90
|
-
main: "main.tex",
|
|
109
|
+
main: "main.tex",
|
|
91
110
|
files: {}
|
|
92
111
|
};
|
|
93
112
|
|
|
@@ -96,13 +115,27 @@ export class TachyonWS {
|
|
|
96
115
|
const textExts = ['.tex', '.sty', '.cls', '.bib', '.txt'];
|
|
97
116
|
const base = basename(fileObj.path);
|
|
98
117
|
|
|
118
|
+
const buffer = await readFile(fileObj.path);
|
|
119
|
+
|
|
120
|
+
// Calculate local hash to detect changes
|
|
121
|
+
const localHash = createHash('sha256').update(buffer).digest('hex');
|
|
122
|
+
|
|
99
123
|
if (textExts.includes(ext)) {
|
|
100
|
-
|
|
124
|
+
// Text files - send content always (could optimize later)
|
|
125
|
+
payload.files[fileObj.relative] = buffer.toString('utf8');
|
|
101
126
|
if (base === 'main.tex') payload.main = fileObj.relative;
|
|
102
127
|
} else {
|
|
103
|
-
// Binary (images)
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
// Binary (images) - Use fingerprinting
|
|
129
|
+
// Check if file hasn't changed locally AND we have a valid server hash
|
|
130
|
+
if (this.localHashes.get(fileObj.relative) === localHash && this.serverHashes.has(fileObj.relative)) {
|
|
131
|
+
// Send reference to existing server blob
|
|
132
|
+
payload.files[fileObj.relative] = { type: 'hash', value: this.serverHashes.get(fileObj.relative) };
|
|
133
|
+
} else {
|
|
134
|
+
// Send full content
|
|
135
|
+
payload.files[fileObj.relative] = buffer.toString('base64');
|
|
136
|
+
// Update local hash state
|
|
137
|
+
this.localHashes.set(fileObj.relative, localHash);
|
|
138
|
+
}
|
|
106
139
|
}
|
|
107
140
|
}
|
|
108
141
|
|