qtex 1.1.2 → 1.1.4

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 CHANGED
@@ -1,18 +1,26 @@
1
- # ⚡ qtex
1
+ # ⚡ qtex — Cloud LaTeX Compiler
2
2
 
3
3
  <p align="center">
4
- <img src="https://raw.githubusercontent.com/srsergiolazaro/qtex/main/docs/assets/banner.jpeg" alt="qtex Banner" width="500">
4
+ <img src="https://raw.githubusercontent.com/srsergiolazaro/qtex/main/docs/assets/banner.jpeg" alt="qtex - Cloud LaTeX Compiler" width="500">
5
5
  </p>
6
6
 
7
7
  <p align="center">
8
- <a href="https://www.npmjs.com/package/qtex"><img src="https://img.shields.io/npm/v/qtex?style=flat-square&logo=npm&color=cb3837" alt="NPM Version"></a>
9
- <a href="./LICENSE"><img src="https://img.shields.io/badge/License-Fair_Source-blue.svg?style=flat-square" alt="License"></a>
10
- <a href="https://latex.taptapp.xyz"><img src="https://img.shields.io/badge/Engine-Tectonic-8b5cf6?style=flat-square&logo=rust" alt="Engine"></a>
11
- <a href="#"><img src="https://img.shields.io/badge/Latency-<1s-22c55e?style=flat-square" alt="Latency"></a>
8
+ <a href="https://www.npmjs.com/package/qtex"><img src="https://img.shields.io/npm/v/qtex?style=flat-square&logo=npm&color=cb3837" alt="npm version"></a>
9
+ <a href="https://www.npmjs.com/package/qtex"><img src="https://img.shields.io/npm/dm/qtex?style=flat-square&color=blue" alt="npm downloads"></a>
10
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-Fair_Source-blue?style=flat-square" alt="License"></a>
11
+ <a href="https://latex.taptapp.xyz"><img src="https://img.shields.io/badge/engine-Tectonic-8b5cf6?style=flat-square&logo=rust" alt="Tectonic Engine"></a>
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <strong>Cloud LaTeX compiler. Sub-second builds. Zero setup.</strong>
15
+ <strong>Compile LaTeX documents in the cloud. Sub-second builds. Zero configuration.</strong><br>
16
+ No TeXLive. No MikTeX. No 5GB downloads. Just fast PDF generation.
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="#install">Install</a> •
21
+ <a href="#usage">Usage</a> •
22
+ <a href="#features">Features</a> •
23
+ <a href="https://srsergiolazaro.github.io/qtex/">Website</a>
16
24
  </p>
17
25
 
18
26
  ---
@@ -21,70 +29,86 @@
21
29
 
22
30
  **macOS / Linux**
23
31
  ```bash
24
- curl -fsSL https://raw.githubusercontent.com/srsergiolazaro/qtex/main/install.sh | bash
32
+ curl -fsSL https://srsergiolazaro.github.io/qtex/install.sh | bash
25
33
  ```
26
34
 
27
35
  **Windows (PowerShell)**
28
36
  ```powershell
29
- irm https://raw.githubusercontent.com/srsergiolazaro/qtex/main/install.ps1 | iex
37
+ irm https://srsergiolazaro.github.io/qtex/install.ps1 | iex
30
38
  ```
31
39
 
32
- **Or use directly without installing:**
40
+ **Or use directly with npx (zero install):**
33
41
  ```bash
34
42
  npx qtex ./my-project
35
43
  ```
36
44
 
37
- ---
38
-
39
- ## Features
40
-
41
- | Feature | Description |
42
- |---------|-------------|
43
- | ⚡ **Fast** | Rust-based Tectonic engine. Compile in milliseconds. |
44
- | 📦 **Zero Config** | No TeXLive, no MikTeX. No 5GB downloads. |
45
- | 👀 **Watch Mode** | Auto-recompile on save. Supports `.tex`, `.bib`, `.sty`, images. |
46
- | 🔍 **Validation** | Pre-flight syntax checks before compilation. |
47
- | 📂 **Multi-file** | Recursive asset discovery with nested folders. |
48
- | 🔒 **Private** | Stateless & ephemeral. Files never stored. |
45
+ **Or install globally via npm:**
46
+ ```bash
47
+ npm install -g qtex
48
+ ```
49
49
 
50
50
  ---
51
51
 
52
52
  ## Usage
53
53
 
54
54
  ```bash
55
- # Compile a project
56
- qtex ./my-project
55
+ # Compile a LaTeX project
56
+ qtex ./my-thesis
57
57
 
58
- # Watch mode (live recompilation)
59
- qtex ./my-project --watch
58
+ # Live watch mode auto-recompile on save
59
+ qtex ./my-thesis --watch
60
60
 
61
61
  # Custom output filename
62
- qtex ./my-project --output thesis.pdf
62
+ qtex ./my-thesis --output final.pdf
63
63
 
64
- # Help
64
+ # Show all options
65
65
  qtex --help
66
66
  ```
67
67
 
68
68
  ---
69
69
 
70
+ ## Features
71
+
72
+ | | Feature | Description |
73
+ |-|---------|-------------|
74
+ | ⚡ | **Blazing Fast** | Rust-based Tectonic engine compiles in milliseconds |
75
+ | 📦 | **Zero Config** | No TeXLive, no MikTeX, no local dependencies |
76
+ | 👀 | **Watch Mode** | Auto-recompile on `.tex`, `.bib`, `.sty`, and image changes |
77
+ | 🔍 | **Validation** | Pre-flight syntax checks before compilation |
78
+ | 📂 | **Multi-file** | Recursive asset discovery with nested folders |
79
+ | 🔒 | **Private** | Stateless & ephemeral — your files are never stored |
80
+
81
+ ---
82
+
70
83
  ## How it Works
71
84
 
72
85
  <p align="center">
73
- <img src="https://raw.githubusercontent.com/srsergiolazaro/qtex/main/docs/assets/flow.png" alt="qtex Workflow" width="600">
86
+ <img src="https://raw.githubusercontent.com/srsergiolazaro/qtex/main/docs/assets/flow.png" alt="qtex workflow diagram" width="600">
74
87
  </p>
75
88
 
76
- 1. **Scan** — Discover all TeX assets and dependencies
77
- 2. **Validate** — Pre-flight syntax check
78
- 3. **Compile** — Cloud processing via Tectonic
79
- 4. **Sync** — Download PDF locally
89
+ 1. **Scan** — Discover all `.tex` files and dependencies
90
+ 2. **Validate** — Pre-flight syntax check via API
91
+ 3. **Compile** — Cloud processing with Tectonic engine
92
+ 4. **Download** — Get your PDF instantly
93
+
94
+ ---
95
+
96
+ ## Why qtex?
97
+
98
+ | Traditional LaTeX | qtex |
99
+ |-------------------|------|
100
+ | 5GB+ TeXLive install | **Zero install** |
101
+ | Minutes to compile | **Milliseconds** |
102
+ | Complex setup | **One command** |
103
+ | Local resources | **Cloud-powered** |
80
104
 
81
105
  ---
82
106
 
83
- ## Infrastructure
107
+ ## API & Infrastructure
84
108
 
85
109
  - **Endpoint**: `https://latex.taptapp.xyz`
86
- - **Engine**: Tectonic (Rust / XeTeX)
87
- - **Privacy**: Stateless processing. Data never stored.
110
+ - **Engine**: [Tectonic](https://tectonic-typesetting.github.io/) (Rust/XeTeX)
111
+ - **Privacy**: Stateless processing data is never stored
88
112
 
89
113
  ---
90
114
 
@@ -92,8 +116,8 @@ qtex --help
92
116
 
93
117
  **Fair Source License**
94
118
 
95
- - ✅ Free for individuals and teams up to 3 users
96
- - 📧 Contact for enterprise/commercial use
119
+ ✅ Free for individuals and teams 3 users
120
+ 📧 Contact for enterprise licensing
97
121
 
98
122
  See [LICENSE](./LICENSE) for details.
99
123
 
package/index.js CHANGED
@@ -60,20 +60,24 @@ ${colors.bold}OPTIONS:${colors.reset}
60
60
  autoUpdate(packageJson.version);
61
61
 
62
62
  if (values.watch) {
63
+ const { TachyonWS } = await import('./src/ws-client.js');
64
+ const wsClient = new TachyonWS(directory, values);
65
+ await wsClient.connect();
66
+
63
67
  const server = startServer();
64
68
  const viewUrl = `http://localhost:${server.port}/view`;
65
69
  ui.info(`Watching for changes in: ${colors.bold}${directory}${colors.reset}`);
66
70
  ui.info(`View PDF at: ${colors.blue}${colors.underline}${viewUrl}${colors.reset}`);
67
71
 
68
- await compile(directory, values);
72
+ // Initial compile over WS
73
+ await wsClient.sendProject();
69
74
 
70
75
  // Auto-open browser
71
76
  const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
72
77
  exec(`${openCmd} "${viewUrl}"`);
73
78
 
74
- let isCompiling = false;
75
79
  watch(directory, { recursive: true }, async (event, filename) => {
76
- if (filename && !filename.startsWith('.') && !isCompiling) {
80
+ if (filename && !filename.startsWith('.') && !wsClient.isCompiling) {
77
81
  const ext = extname(filename).toLowerCase();
78
82
  const outputFileName = values.output || 'output.pdf';
79
83
 
@@ -82,10 +86,7 @@ ${colors.bold}OPTIONS:${colors.reset}
82
86
  const isOutputFile = basename(filename) === basename(outputFileName);
83
87
 
84
88
  if (watchExts.includes(ext) && !isOutputFile) {
85
- isCompiling = true;
86
- console.log(`\n${colors.blue}🔄 Change detected in ${filename}, recompiling...${colors.reset}`);
87
- await compile(directory, values);
88
- isCompiling = false;
89
+ await wsClient.sendProject();
89
90
  }
90
91
  }
91
92
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qtex",
3
- "version": "1.1.2",
4
- "description": "Vortex CLI: Ultra-fast cloud-based LaTeX compilation. Compile LaTeX documents in milliseconds using a high-performance Rust-powered engine without local TeX installation.",
3
+ "version": "1.1.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": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -12,13 +12,21 @@
12
12
  "latex",
13
13
  "tex",
14
14
  "compiler",
15
- "tectonic",
16
- "rust",
15
+ "pdf",
16
+ "latex-compiler",
17
17
  "cloud-latex",
18
- "pdf-generator",
19
- "vortex",
18
+ "tectonic",
19
+ "xelatex",
20
+ "pdflatex",
21
+ "academic",
22
+ "thesis",
23
+ "paper",
24
+ "document",
25
+ "typesetting",
26
+ "markdown-to-pdf",
20
27
  "cli",
21
- "typesetting"
28
+ "fast",
29
+ "rust"
22
30
  ],
23
31
  "author": "Sergio Lázaro (srsergio)",
24
32
  "repository": {
@@ -28,7 +36,7 @@
28
36
  "bugs": {
29
37
  "url": "https://github.com/srsergiolazaro/qtex/issues"
30
38
  },
31
- "homepage": "https://github.com/srsergiolazaro/qtex#readme",
39
+ "homepage": "https://srsergiolazaro.github.io/qtex/",
32
40
  "license": "SEE LICENSE IN LICENSE",
33
41
  "type": "module",
34
42
  "bin": {
@@ -42,5 +50,8 @@
42
50
  ],
43
51
  "directories": {
44
52
  "example": "example"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
45
56
  }
46
- }
57
+ }
@@ -0,0 +1,136 @@
1
+ import { resolve, extname, join, basename } from 'node:path';
2
+ import { colors, ui, Spinner } from './ui.js';
3
+ import { notifyClients } from './server.js';
4
+ import { readFile, writeFile } from 'node:fs/promises';
5
+
6
+ const WS_URL = 'wss://latex.taptapp.xyz/ws'; // Production URL
7
+
8
+ export class TachyonWS {
9
+ constructor(directory, options) {
10
+ this.directory = directory;
11
+ this.options = options;
12
+ this.socket = null;
13
+ this.isConnected = false;
14
+ this.spinner = new Spinner('');
15
+ this.isCompiling = false;
16
+ }
17
+
18
+ async connect() {
19
+ return new Promise((res, rej) => {
20
+ ui.info(`${colors.cyan}Connecting to Tachyon-Tex Live Engine...${colors.reset}`);
21
+ this.socket = new WebSocket(this.options.serverUrl || WS_URL);
22
+
23
+ this.socket.onopen = () => {
24
+ this.isConnected = true;
25
+ ui.info(`${colors.green}✅ Connected to Live Engine${colors.reset}`);
26
+ res();
27
+ };
28
+
29
+ this.socket.onmessage = async (event) => {
30
+ const data = JSON.parse(event.data);
31
+
32
+ if (data.type === 'compile_success') {
33
+ if (this.spinner.timer) {
34
+ this.spinner.succeed(`${colors.green}PDF updated in ${colors.bold}${data.compile_time_ms}ms${colors.reset}`);
35
+ }
36
+
37
+ const outputFileName = this.options.output || 'output.pdf';
38
+ const outputPath = resolve(process.cwd(), outputFileName);
39
+
40
+ // Decode base64 PDF
41
+ const buffer = Buffer.from(data.pdf, 'base64');
42
+ await writeFile(outputPath, buffer);
43
+
44
+ // Notify local preview clients
45
+ notifyClients(outputPath);
46
+ this.isCompiling = false;
47
+ } else if (data.type === 'compile_error') {
48
+ if (this.spinner.timer) {
49
+ this.spinner.fail('Compilation failed');
50
+ }
51
+ console.log(`\n${colors.yellow}--- LaTeX Error ---${colors.reset}`);
52
+ console.log(`${colors.red}${data.error}${colors.reset}`);
53
+ console.log(`${colors.yellow}-------------------\n${colors.reset}`);
54
+ this.isCompiling = false;
55
+ }
56
+ };
57
+
58
+ this.socket.onerror = (err) => {
59
+ ui.error(`WebSocket Error: ${err.message}`);
60
+ rej(err);
61
+ };
62
+
63
+ this.socket.onclose = () => {
64
+ this.isConnected = false;
65
+ ui.warn('WebSocket connection closed. Retrying in 2s...');
66
+ setTimeout(() => this.connect(), 2000);
67
+ };
68
+ });
69
+ }
70
+
71
+ async sendProject() {
72
+ if (!this.isConnected || this.isCompiling) return;
73
+
74
+ this.isCompiling = true;
75
+ this.spinner = new Spinner(`${colors.blue}Syncing project...`).start();
76
+
77
+ try {
78
+ const project = await this.buildProjectPayload();
79
+ this.socket.send(JSON.stringify(project));
80
+ } catch (e) {
81
+ this.spinner.fail(`Failed to sync: ${e.message}`);
82
+ this.isCompiling = false;
83
+ }
84
+ }
85
+
86
+ async buildProjectPayload() {
87
+ const absoluteDir = resolve(this.directory);
88
+ const files = await this.getProjectFiles(absoluteDir);
89
+ const payload = {
90
+ main: "main.tex", // Heuristic could be improved
91
+ files: {}
92
+ };
93
+
94
+ for (const fileObj of files) {
95
+ const ext = extname(fileObj.path).toLowerCase();
96
+ const textExts = ['.tex', '.sty', '.cls', '.bib', '.txt'];
97
+ const base = basename(fileObj.path);
98
+
99
+ if (textExts.includes(ext)) {
100
+ payload.files[fileObj.relative] = await readFile(fileObj.path, 'utf8');
101
+ if (base === 'main.tex') payload.main = fileObj.relative;
102
+ } else {
103
+ // Binary (images)
104
+ const buffer = await readFile(fileObj.path);
105
+ payload.files[fileObj.relative] = buffer.toString('base64');
106
+ }
107
+ }
108
+
109
+ return payload;
110
+ }
111
+
112
+ async getProjectFiles(dir, baseDir = dir) {
113
+ const { readdir } = await import('node:fs/promises');
114
+ const entries = await readdir(dir, { withFileTypes: true });
115
+ const files = [];
116
+
117
+ for (const entry of entries) {
118
+ const res = join(dir, entry.name);
119
+ if (entry.isDirectory()) {
120
+ if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
121
+ files.push(...(await this.getProjectFiles(res, baseDir)));
122
+ }
123
+ } else {
124
+ const ext = extname(entry.name).toLowerCase();
125
+ const allowed = ['.tex', '.bib', '.sty', '.cls', '.pdf', '.png', '.jpg', '.jpeg'];
126
+ if (allowed.includes(ext)) {
127
+ files.push({
128
+ path: res,
129
+ relative: join(dir.replace(baseDir, ''), entry.name).replace(/^[\\\/]/, '').replace(/\\/g, '/')
130
+ });
131
+ }
132
+ }
133
+ }
134
+ return files;
135
+ }
136
+ }