latexmk-mcp 1.0.0
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/.gitattributes +5 -0
- package/GEMINI.md +60 -0
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/package.json +27 -0
- package/src/index.ts +671 -0
- package/src/test.ts +1 -0
- package/tsconfig.json +27 -0
package/.gitattributes
ADDED
package/GEMINI.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# GEMINI.md - Instructional Context for `latexmk-mcp`
|
|
2
|
+
|
|
3
|
+
This file provides context and guidelines for interacting with the `latexmk-mcp` project.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
`latexmk-mcp` is a **Model Context Protocol (MCP)** server that exposes the `latexmk` LaTeX build tool as a set of tools for MCP-compatible AI assistants. It allows an AI to compile, check, clean, and inspect LaTeX documents directly through a standardized interface.
|
|
8
|
+
|
|
9
|
+
### Key Technologies
|
|
10
|
+
- **Runtime:** Node.js (Primary), Bun (Optional for dev)
|
|
11
|
+
- **Language:** TypeScript
|
|
12
|
+
- **Schemas:** Zod
|
|
13
|
+
- **Core SDK:** `@modelcontextprotocol/sdk`
|
|
14
|
+
- **External Tool:** `latexmk` (Must be on `$PATH`)
|
|
15
|
+
|
|
16
|
+
### Architecture
|
|
17
|
+
The server follows a standard MCP pattern:
|
|
18
|
+
1. **Tool Definitions:** Located in `src/index.ts`, defining the inputs via Zod schemas and metadata for the MCP `list_tools` request.
|
|
19
|
+
2. **Handlers:** Asynchronous functions (`handleCompile`, `handleClean`, etc.) that validate input, prepare arguments, and execute `latexmk` as a subprocess.
|
|
20
|
+
3. **Transport:** Communicates via **stdio**, allowing easy integration with clients like Claude Desktop.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Building and Running
|
|
25
|
+
|
|
26
|
+
### Prerequisites
|
|
27
|
+
- Node.js 18+ and `npm`.
|
|
28
|
+
- `latexmk` and a TeX distribution (e.g., TeX Live, MiKTeX) must be installed on the system.
|
|
29
|
+
|
|
30
|
+
### Key Commands
|
|
31
|
+
- **Build:** `npm run build`
|
|
32
|
+
- Compiles TypeScript files from `src/` to `dist/`.
|
|
33
|
+
- **Run (Production):** `npm start`
|
|
34
|
+
- Executes the compiled JavaScript in `dist/index.js`.
|
|
35
|
+
- **Run (Development):** `npm run dev`
|
|
36
|
+
- Runs `src/index.ts` directly using `bun`.
|
|
37
|
+
- **Test:** *TODO: Implement automated tests (e.g., using Vitest or Bun's built-in test runner).*
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Development Conventions
|
|
42
|
+
|
|
43
|
+
### Code Style and Standards
|
|
44
|
+
- **ESM:** The project is a pure ES Module (`"type": "module"` in `package.json`).
|
|
45
|
+
- **TypeScript:** Uses `NodeNext` for module resolution. Avoid using `any`; prefer Zod-inferred types for tool inputs.
|
|
46
|
+
- **Surgical Changes:** When modifying tools, ensure the Zod schema and the handler remain in sync.
|
|
47
|
+
|
|
48
|
+
### File Structure
|
|
49
|
+
- `src/index.ts`: The main entry point containing all tool definitions and handlers.
|
|
50
|
+
- `dist/`: Compiled JavaScript output (ignored by Git).
|
|
51
|
+
- `tsconfig.json`: Configured for ESM and `NodeNext` resolution.
|
|
52
|
+
- `README.md`: Contains setup and configuration instructions for users.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Future Improvements (Roadmap)
|
|
57
|
+
- [ ] Add a comprehensive test suite with sample `.tex` files.
|
|
58
|
+
- [ ] Implement a file watcher for automatic recompilation.
|
|
59
|
+
- [ ] Add support for more advanced `latexmk` configuration files (`.latexmkrc`).
|
|
60
|
+
- [ ] Improve error parsing for more granular feedback to the user.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 coolport
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# latexmk-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes the [`latexmk`](https://personal.psu.edu/~jcc8/software/latexmk/) LaTeX build tool as MCP tools — letting any MCP-compatible AI assistant compile, check, clean, and inspect LaTeX documents.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- **Node.js** 18+
|
|
10
|
+
- **latexmk** installed and on `$PATH` (usually ships with TeX Live or MiKTeX)
|
|
11
|
+
- A TeX distribution with your required engines (`pdflatex`, `xelatex`, `lualatex`, …)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Debian/Ubuntu
|
|
15
|
+
sudo apt install latexmk texlive-full
|
|
16
|
+
|
|
17
|
+
# macOS (Homebrew + MacTeX)
|
|
18
|
+
brew install --cask mactex
|
|
19
|
+
|
|
20
|
+
# Arch Linux
|
|
21
|
+
sudo pacman -S texlive-most
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Installation & Build
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone <this-repo>
|
|
30
|
+
cd latexmk-mcp
|
|
31
|
+
npm install
|
|
32
|
+
npm run build # outputs to dist/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Running
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Direct
|
|
41
|
+
node dist/index.js
|
|
42
|
+
|
|
43
|
+
# Via npm
|
|
44
|
+
npm start
|
|
45
|
+
|
|
46
|
+
# During development (no build step)
|
|
47
|
+
npm run dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The server communicates over **stdio** (standard MCP transport).
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Claude Desktop Configuration
|
|
55
|
+
|
|
56
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"latexmk": {
|
|
62
|
+
"command": "node",
|
|
63
|
+
"args": ["/absolute/path/to/latexmk-mcp/dist/index.js"]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Tools
|
|
72
|
+
|
|
73
|
+
### `latexmk_compile`
|
|
74
|
+
Full compile of a LaTeX document with configurable engine, output format, bibliography processor, and latexmk flags.
|
|
75
|
+
|
|
76
|
+
| Parameter | Type | Default | Description |
|
|
77
|
+
|---|---|---|---|
|
|
78
|
+
| `tex_content` | string | — | Raw LaTeX source (mutually exclusive with `file_path`) |
|
|
79
|
+
| `file_path` | string | — | Path to existing `.tex` file |
|
|
80
|
+
| `output_format` | `pdf\|dvi\|ps\|xdv` | `pdf` | Target format |
|
|
81
|
+
| `engine` | `pdflatex\|xelatex\|lualatex\|latex\|pdftex` | `pdflatex` | TeX engine |
|
|
82
|
+
| `bibtex` | `bibtex\|biber\|none` | `none` | Bibliography processor |
|
|
83
|
+
| `shell_escape` | boolean | `false` | Enable `--shell-escape` |
|
|
84
|
+
| `synctex` | boolean | `false` | Generate SyncTeX data |
|
|
85
|
+
| `extra_args` | string[] | `[]` | Extra latexmk CLI flags |
|
|
86
|
+
| `working_dir` | string | temp dir | Build directory |
|
|
87
|
+
|
|
88
|
+
**Returns:** `success`, `exit_code`, `output_file` path, `errors[]`, `warnings[]`, `working_dir`, `stdout`, `stderr`.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `latexmk_draft_compile`
|
|
93
|
+
Fast single-pass compile (no reruns, no bibliography) — ideal for quick syntax/error checks while editing.
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Default | Description |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| `tex_content` | string | — | Raw LaTeX source |
|
|
98
|
+
| `file_path` | string | — | Path to `.tex` file |
|
|
99
|
+
| `engine` | string | `pdflatex` | TeX engine |
|
|
100
|
+
| `working_dir` | string | temp dir | Build directory |
|
|
101
|
+
|
|
102
|
+
**Returns:** `success`, `errors[]`, `warnings[]`, `stdout`, `stderr`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### `latexmk_clean`
|
|
107
|
+
Remove build artifacts using `latexmk -c` (auxiliaries only) or `latexmk -C` (auxiliaries + output files).
|
|
108
|
+
|
|
109
|
+
| Parameter | Type | Default | Description |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `working_dir` | string | **required** | Directory to clean |
|
|
112
|
+
| `job_name` | string | — | Clean a specific job only |
|
|
113
|
+
| `clean_all` | boolean | `false` | `-C` instead of `-c` |
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### `latexmk_check`
|
|
118
|
+
Detect whether `latexmk` is installed and which TeX engines are available on the system.
|
|
119
|
+
|
|
120
|
+
**Returns:** `latexmk_available`, `latexmk_version`, `latexmk_path`, `engines_available` map.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `latexmk_list_dependencies`
|
|
125
|
+
List all file dependencies of a document (included `.tex` files, `.bib` files, packages, images…) via `latexmk -deps`.
|
|
126
|
+
|
|
127
|
+
| Parameter | Type | Description |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `tex_content` | string | Raw LaTeX source |
|
|
130
|
+
| `file_path` | string | Path to `.tex` file |
|
|
131
|
+
| `working_dir` | string | Working directory |
|
|
132
|
+
|
|
133
|
+
**Returns:** `dependencies[]` (deduplicated list of file paths).
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run dev # run with tsx (no build needed)
|
|
141
|
+
npm run build # compile TypeScript → dist/
|
|
142
|
+
npm start # run compiled output
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "latexmk-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for the latexmk LaTeX build tool",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"latexmk-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "bun run src/index.ts",
|
|
14
|
+
"prepublishOnly": "bun run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["mcp", "latex", "latexmk", "tex"],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
21
|
+
"zod": "^3.23.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"typescript": "^5.0.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
Tool,
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { execFile } from "child_process";
|
|
11
|
+
import { promisify } from "util";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import * as fs from "fs/promises";
|
|
14
|
+
import * as path from "path";
|
|
15
|
+
import * as os from "os";
|
|
16
|
+
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
|
|
19
|
+
const CompileSchema = z.object({
|
|
20
|
+
tex_content: z.string().optional().describe("LaTeX source content (if not providing file_path)"),
|
|
21
|
+
file_path: z.string().optional().describe("Absolute path to an existing .tex file"),
|
|
22
|
+
output_format: z
|
|
23
|
+
.enum(["pdf", "dvi", "ps", "xdv"])
|
|
24
|
+
.default("pdf")
|
|
25
|
+
.describe("Target output format"),
|
|
26
|
+
engine: z
|
|
27
|
+
.enum(["pdflatex", "xelatex", "lualatex", "latex", "pdftex"])
|
|
28
|
+
.default("pdflatex")
|
|
29
|
+
.describe("TeX engine to use"),
|
|
30
|
+
bibtex: z
|
|
31
|
+
.enum(["bibtex", "biber", "none"])
|
|
32
|
+
.default("none")
|
|
33
|
+
.describe("Bibliography processor"),
|
|
34
|
+
shell_escape: z.boolean().default(false).describe("Enable shell-escape (--shell-escape)"),
|
|
35
|
+
synctex: z.boolean().default(false).describe("Generate SyncTeX data"),
|
|
36
|
+
extra_args: z.array(z.string()).default([]).describe("Extra latexmk CLI arguments"),
|
|
37
|
+
working_dir: z.string().optional().describe("Working directory (defaults to system temp)"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
type CompileOptions = z.infer<typeof CompileSchema>;
|
|
41
|
+
|
|
42
|
+
const CleanSchema = z.object({
|
|
43
|
+
working_dir: z.string().describe("Directory containing the LaTeX build artifacts to clean"),
|
|
44
|
+
job_name: z.string().optional().describe("Specific job name (base filename without extension)"),
|
|
45
|
+
clean_all: z.boolean().default(false).describe("Use -C (remove output files too) instead of -c"),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
type CleanOptions = z.infer<typeof CleanSchema>;
|
|
49
|
+
|
|
50
|
+
const PreviewSchema = z.object({
|
|
51
|
+
tex_content: z.string().optional().describe("LaTeX source content"),
|
|
52
|
+
file_path: z.string().optional().describe("Absolute path to an existing .tex file"),
|
|
53
|
+
engine: z
|
|
54
|
+
.enum(["pdflatex", "xelatex", "lualatex", "latex"])
|
|
55
|
+
.default("pdflatex")
|
|
56
|
+
.describe("TeX engine"),
|
|
57
|
+
working_dir: z.string().optional().describe("Working directory"),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
type PreviewOptions = z.infer<typeof PreviewSchema>;
|
|
61
|
+
|
|
62
|
+
const CheckSchema = z.object({
|
|
63
|
+
working_dir: z.string().optional().describe("Directory to check for latexmk availability"),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const ListDependenciesSchema = z.object({
|
|
67
|
+
tex_content: z.string().optional().describe("LaTeX source content"),
|
|
68
|
+
file_path: z.string().optional().describe("Absolute path to an existing .tex file"),
|
|
69
|
+
working_dir: z.string().optional().describe("Working directory"),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
type ListDependenciesOptions = z.infer<typeof ListDependenciesSchema>;
|
|
73
|
+
|
|
74
|
+
// Helpers
|
|
75
|
+
|
|
76
|
+
async function createTempDir(): Promise<string> {
|
|
77
|
+
return fs.mkdtemp(path.join(os.tmpdir(), "latexmk-mcp-"));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function writeTex(content: string, dir: string, name = "document"): Promise<string> {
|
|
81
|
+
const filePath = path.join(dir, `${name}.tex`);
|
|
82
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
83
|
+
return filePath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function readFileIfExists(filePath: string): Promise<string | null> {
|
|
87
|
+
try {
|
|
88
|
+
return await fs.readFile(filePath, "utf-8");
|
|
89
|
+
} catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildLatexmkArgs(opts: {
|
|
95
|
+
outputFormat: string;
|
|
96
|
+
engine: string;
|
|
97
|
+
bibtex: string;
|
|
98
|
+
shellEscape: boolean;
|
|
99
|
+
synctex: boolean;
|
|
100
|
+
extraArgs: string[];
|
|
101
|
+
jobName?: string;
|
|
102
|
+
outputDir?: string;
|
|
103
|
+
}): string[] {
|
|
104
|
+
const args: string[] = ["-interaction=nonstopmode", "-halt-on-error", "-f"];
|
|
105
|
+
|
|
106
|
+
// Output format
|
|
107
|
+
switch (opts.outputFormat) {
|
|
108
|
+
case "pdf":
|
|
109
|
+
args.push("-pdf");
|
|
110
|
+
break;
|
|
111
|
+
case "dvi":
|
|
112
|
+
args.push("-dvi");
|
|
113
|
+
break;
|
|
114
|
+
case "ps":
|
|
115
|
+
args.push("-ps");
|
|
116
|
+
break;
|
|
117
|
+
case "xdv":
|
|
118
|
+
args.push("-xdv");
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Engine override
|
|
123
|
+
switch (opts.engine) {
|
|
124
|
+
case "xelatex":
|
|
125
|
+
args.push("-xelatex");
|
|
126
|
+
break;
|
|
127
|
+
case "lualatex":
|
|
128
|
+
args.push("-lualatex");
|
|
129
|
+
break;
|
|
130
|
+
case "pdftex":
|
|
131
|
+
case "pdflatex":
|
|
132
|
+
if (opts.outputFormat === "pdf") args.push("-pdflatex");
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Bibliography
|
|
137
|
+
if (opts.bibtex === "bibtex") args.push("-bibtex");
|
|
138
|
+
else if (opts.bibtex === "biber") args.push('-bibtex', '-e', '$biber=q/biber/');
|
|
139
|
+
else args.push("-bibtex-");
|
|
140
|
+
|
|
141
|
+
if (opts.shellEscape) args.push("-shell-escape");
|
|
142
|
+
if (opts.synctex) args.push("-synctex=1");
|
|
143
|
+
|
|
144
|
+
if (opts.outputDir) args.push(`-outdir=${opts.outputDir}`);
|
|
145
|
+
if (opts.jobName) args.push(`-jobname=${opts.jobName}`);
|
|
146
|
+
|
|
147
|
+
return [...args, ...opts.extraArgs];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function parseLatexLog(log: string): {
|
|
151
|
+
errors: string[];
|
|
152
|
+
warnings: string[];
|
|
153
|
+
info: string[];
|
|
154
|
+
} {
|
|
155
|
+
const lines = log.split("\n");
|
|
156
|
+
const errors: string[] = [];
|
|
157
|
+
const warnings: string[] = [];
|
|
158
|
+
const info: string[] = [];
|
|
159
|
+
|
|
160
|
+
for (let i = 0; i < lines.length; i++) {
|
|
161
|
+
const line = lines[i]?.trim();
|
|
162
|
+
if (!line) continue;
|
|
163
|
+
|
|
164
|
+
if (/^!(?: LaTeX| Package| Class)? Error/i.test(line)) {
|
|
165
|
+
// Collect multi-line error context
|
|
166
|
+
const ctx = [line];
|
|
167
|
+
while (i + 1 < lines.length && lines[i + 1]?.startsWith(" ")) {
|
|
168
|
+
ctx.push(lines[++i]!.trim());
|
|
169
|
+
}
|
|
170
|
+
errors.push(ctx.join(" "));
|
|
171
|
+
} else if (/^(LaTeX|Package|Class) Warning/i.test(line)) {
|
|
172
|
+
warnings.push(line);
|
|
173
|
+
} else if (line.startsWith("Latexmk:")) {
|
|
174
|
+
info.push(line);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { errors, warnings, info };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Tool Handlers
|
|
182
|
+
|
|
183
|
+
async function handleCompile(rawArgs: unknown) {
|
|
184
|
+
const args: CompileOptions = CompileSchema.parse(rawArgs);
|
|
185
|
+
|
|
186
|
+
if (!args.tex_content && !args.file_path) {
|
|
187
|
+
throw new Error("Either tex_content or file_path must be provided.");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const ownDir = !args.working_dir && !args.file_path;
|
|
191
|
+
const workDir = args.working_dir ?? (await createTempDir());
|
|
192
|
+
|
|
193
|
+
let texPath: string;
|
|
194
|
+
let jobName = "document";
|
|
195
|
+
|
|
196
|
+
if (args.file_path) {
|
|
197
|
+
texPath = path.resolve(args.file_path);
|
|
198
|
+
jobName = path.basename(texPath, ".tex");
|
|
199
|
+
} else {
|
|
200
|
+
texPath = await writeTex(args.tex_content!, workDir, jobName);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const lmkArgs = buildLatexmkArgs({
|
|
204
|
+
outputFormat: args.output_format,
|
|
205
|
+
engine: args.engine,
|
|
206
|
+
bibtex: args.bibtex,
|
|
207
|
+
shellEscape: args.shell_escape,
|
|
208
|
+
synctex: args.synctex,
|
|
209
|
+
extraArgs: args.extra_args,
|
|
210
|
+
jobName,
|
|
211
|
+
outputDir: workDir,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
lmkArgs.push(texPath);
|
|
215
|
+
|
|
216
|
+
let stdout = "";
|
|
217
|
+
let stderr = "";
|
|
218
|
+
let exitCode = 0;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const result = await execFileAsync("latexmk", lmkArgs, {
|
|
222
|
+
cwd: workDir,
|
|
223
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
224
|
+
});
|
|
225
|
+
stdout = result.stdout;
|
|
226
|
+
stderr = result.stderr;
|
|
227
|
+
} catch (err: unknown) {
|
|
228
|
+
const e = err as { stdout?: string; stderr?: string; code?: number };
|
|
229
|
+
stdout = e.stdout ?? "";
|
|
230
|
+
stderr = e.stderr ?? "";
|
|
231
|
+
exitCode = e.code ?? 1;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const logFile = path.join(workDir, `${jobName}.log`);
|
|
235
|
+
const logContent = (await readFileIfExists(logFile)) ?? "";
|
|
236
|
+
const parsed = parseLatexLog(logContent + "\n" + stdout);
|
|
237
|
+
|
|
238
|
+
const outputExt = args.output_format === "dvi" ? "dvi" : args.output_format === "ps" ? "ps" : args.output_format === "xdv" ? "xdv" : "pdf";
|
|
239
|
+
const outputFile = path.join(workDir, `${jobName}.${outputExt}`);
|
|
240
|
+
let outputExists = false;
|
|
241
|
+
try {
|
|
242
|
+
await fs.access(outputFile);
|
|
243
|
+
outputExists = true;
|
|
244
|
+
} catch { /* noop */ }
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
success: exitCode === 0 && outputExists,
|
|
248
|
+
exit_code: exitCode,
|
|
249
|
+
output_file: outputExists ? outputFile : null,
|
|
250
|
+
working_dir: workDir,
|
|
251
|
+
errors: parsed.errors,
|
|
252
|
+
warnings: parsed.warnings,
|
|
253
|
+
latexmk_info: parsed.info,
|
|
254
|
+
stdout: stdout.slice(0, 4000),
|
|
255
|
+
stderr: stderr.slice(0, 2000),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function handleClean(rawArgs: unknown) {
|
|
260
|
+
const args: CleanOptions = CleanSchema.parse(rawArgs);
|
|
261
|
+
const flag = args.clean_all ? "-C" : "-c";
|
|
262
|
+
|
|
263
|
+
const lmkArgs = [flag];
|
|
264
|
+
if (args.job_name) lmkArgs.push(`-jobname=${args.job_name}`);
|
|
265
|
+
|
|
266
|
+
let stdout = "";
|
|
267
|
+
let stderr = "";
|
|
268
|
+
let exitCode = 0;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const result = await execFileAsync("latexmk", lmkArgs, {
|
|
272
|
+
cwd: path.resolve(args.working_dir),
|
|
273
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
274
|
+
});
|
|
275
|
+
stdout = result.stdout;
|
|
276
|
+
stderr = result.stderr;
|
|
277
|
+
} catch (err: unknown) {
|
|
278
|
+
const e = err as { stdout?: string; stderr?: string; code?: number };
|
|
279
|
+
stdout = e.stdout ?? "";
|
|
280
|
+
stderr = e.stderr ?? "";
|
|
281
|
+
exitCode = e.code ?? 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
success: exitCode === 0,
|
|
286
|
+
exit_code: exitCode,
|
|
287
|
+
clean_all: args.clean_all,
|
|
288
|
+
stdout,
|
|
289
|
+
stderr,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function handleDraftCompile(rawArgs: unknown) {
|
|
294
|
+
// Fast single-pass compile to check for errors quickly (no reruns)
|
|
295
|
+
const args: PreviewOptions = PreviewSchema.parse(rawArgs);
|
|
296
|
+
|
|
297
|
+
if (!args.tex_content && !args.file_path) {
|
|
298
|
+
throw new Error("Either tex_content or file_path must be provided.");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const workDir = args.working_dir ?? (await createTempDir());
|
|
302
|
+
let texPath: string;
|
|
303
|
+
let jobName = "document";
|
|
304
|
+
|
|
305
|
+
if (args.file_path) {
|
|
306
|
+
texPath = path.resolve(args.file_path);
|
|
307
|
+
jobName = path.basename(texPath, ".tex");
|
|
308
|
+
} else {
|
|
309
|
+
texPath = await writeTex(args.tex_content!, workDir, jobName);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const lmkArgs = [
|
|
313
|
+
"-pdf",
|
|
314
|
+
"-interaction=nonstopmode",
|
|
315
|
+
"-f",
|
|
316
|
+
"-bibtex-",
|
|
317
|
+
`-outdir=${workDir}`,
|
|
318
|
+
`-jobname=${jobName}`,
|
|
319
|
+
texPath,
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
let stdout = "";
|
|
323
|
+
let stderr = "";
|
|
324
|
+
let exitCode = 0;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const result = await execFileAsync("latexmk", lmkArgs, {
|
|
328
|
+
cwd: workDir,
|
|
329
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
330
|
+
});
|
|
331
|
+
stdout = result.stdout;
|
|
332
|
+
stderr = result.stderr;
|
|
333
|
+
} catch (err: unknown) {
|
|
334
|
+
const e = err as { stdout?: string; stderr?: string; code?: number };
|
|
335
|
+
stdout = e.stdout ?? "";
|
|
336
|
+
stderr = e.stderr ?? "";
|
|
337
|
+
exitCode = e.code ?? 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const logFile = path.join(workDir, `${jobName}.log`);
|
|
341
|
+
const logContent = (await readFileIfExists(logFile)) ?? "";
|
|
342
|
+
const parsed = parseLatexLog(logContent + "\n" + stdout);
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
success: exitCode === 0,
|
|
346
|
+
exit_code: exitCode,
|
|
347
|
+
working_dir: workDir,
|
|
348
|
+
errors: parsed.errors,
|
|
349
|
+
warnings: parsed.warnings,
|
|
350
|
+
stdout: stdout.slice(0, 4000),
|
|
351
|
+
stderr: stderr.slice(0, 2000),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function handleCheck(_rawArgs: unknown) {
|
|
356
|
+
let version = "";
|
|
357
|
+
let available = false;
|
|
358
|
+
let path_found = "";
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const { stdout } = await execFileAsync("latexmk", ["--version"]);
|
|
362
|
+
version = stdout.trim().split("\n")[0] ?? "";
|
|
363
|
+
available = true;
|
|
364
|
+
} catch {
|
|
365
|
+
// latexmk not found
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const { stdout } = await execFileAsync("which", ["latexmk"]);
|
|
370
|
+
path_found = stdout.trim();
|
|
371
|
+
} catch {
|
|
372
|
+
// ignore
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Check for common TeX engines
|
|
376
|
+
const engines: Record<string, boolean> = {};
|
|
377
|
+
for (const eng of ["pdflatex", "xelatex", "lualatex", "latex"]) {
|
|
378
|
+
try {
|
|
379
|
+
await execFileAsync("which", [eng]);
|
|
380
|
+
engines[eng] = true;
|
|
381
|
+
} catch {
|
|
382
|
+
engines[eng] = false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
latexmk_available: available,
|
|
388
|
+
latexmk_version: version,
|
|
389
|
+
latexmk_path: path_found,
|
|
390
|
+
engines_available: engines,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function handleListDependencies(rawArgs: unknown) {
|
|
395
|
+
const args: ListDependenciesOptions = ListDependenciesSchema.parse(rawArgs);
|
|
396
|
+
|
|
397
|
+
if (!args.tex_content && !args.file_path) {
|
|
398
|
+
throw new Error("Either tex_content or file_path must be provided.");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const workDir = args.working_dir ?? (await createTempDir());
|
|
402
|
+
let texPath: string;
|
|
403
|
+
let jobName = "document";
|
|
404
|
+
|
|
405
|
+
if (args.file_path) {
|
|
406
|
+
texPath = path.resolve(args.file_path);
|
|
407
|
+
jobName = path.basename(texPath, ".tex");
|
|
408
|
+
} else {
|
|
409
|
+
texPath = await writeTex(args.tex_content!, workDir, jobName);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Use -deps flag to print dependencies
|
|
413
|
+
const lmkArgs = [
|
|
414
|
+
"-pdf",
|
|
415
|
+
"-deps",
|
|
416
|
+
"-bibtex-",
|
|
417
|
+
"-f",
|
|
418
|
+
`-outdir=${workDir}`,
|
|
419
|
+
`-jobname=${jobName}`,
|
|
420
|
+
texPath,
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
let stdout = "";
|
|
424
|
+
let exitCode = 0;
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
const result = await execFileAsync("latexmk", lmkArgs, {
|
|
428
|
+
cwd: workDir,
|
|
429
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
430
|
+
});
|
|
431
|
+
stdout = result.stdout;
|
|
432
|
+
} catch (err: unknown) {
|
|
433
|
+
const e = err as { stdout?: string; code?: number };
|
|
434
|
+
stdout = e.stdout ?? "";
|
|
435
|
+
exitCode = e.code ?? 1;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Parse dependencies from the output
|
|
439
|
+
const deps: string[] = [];
|
|
440
|
+
const depRegex = /^\s{2,}(.+\.(?:tex|bib|sty|cls|clo|def|cfg|fd|enc|tfm|pfb|png|jpg|pdf|eps|svg))\s*\\?$/gim;
|
|
441
|
+
let match;
|
|
442
|
+
while ((match = depRegex.exec(stdout)) !== null) {
|
|
443
|
+
const dep = match[1];
|
|
444
|
+
if (dep) {
|
|
445
|
+
deps.push(dep.trim());
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
success: exitCode === 0,
|
|
451
|
+
dependencies: [...new Set(deps)],
|
|
452
|
+
working_dir: workDir,
|
|
453
|
+
raw_output: stdout.slice(0, 3000),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Tool Definitions
|
|
458
|
+
|
|
459
|
+
const TOOLS: Tool[] = [
|
|
460
|
+
{
|
|
461
|
+
name: "latexmk_compile",
|
|
462
|
+
description:
|
|
463
|
+
"Compile a LaTeX document using latexmk. Accepts raw LaTeX source or a path to an existing .tex file. Returns compile success/failure, errors, warnings, and the path to the output file.",
|
|
464
|
+
inputSchema: {
|
|
465
|
+
type: "object",
|
|
466
|
+
properties: {
|
|
467
|
+
tex_content: {
|
|
468
|
+
type: "string",
|
|
469
|
+
description: "LaTeX source content (mutually exclusive with file_path)",
|
|
470
|
+
},
|
|
471
|
+
file_path: {
|
|
472
|
+
type: "string",
|
|
473
|
+
description: "Absolute path to an existing .tex file (mutually exclusive with tex_content)",
|
|
474
|
+
},
|
|
475
|
+
output_format: {
|
|
476
|
+
type: "string",
|
|
477
|
+
enum: ["pdf", "dvi", "ps", "xdv"],
|
|
478
|
+
default: "pdf",
|
|
479
|
+
description: "Target output format",
|
|
480
|
+
},
|
|
481
|
+
engine: {
|
|
482
|
+
type: "string",
|
|
483
|
+
enum: ["pdflatex", "xelatex", "lualatex", "latex", "pdftex"],
|
|
484
|
+
default: "pdflatex",
|
|
485
|
+
description: "TeX engine to use",
|
|
486
|
+
},
|
|
487
|
+
bibtex: {
|
|
488
|
+
type: "string",
|
|
489
|
+
enum: ["bibtex", "biber", "none"],
|
|
490
|
+
default: "none",
|
|
491
|
+
description: "Bibliography processor",
|
|
492
|
+
},
|
|
493
|
+
shell_escape: {
|
|
494
|
+
type: "boolean",
|
|
495
|
+
default: false,
|
|
496
|
+
description: "Enable --shell-escape",
|
|
497
|
+
},
|
|
498
|
+
synctex: {
|
|
499
|
+
type: "boolean",
|
|
500
|
+
default: false,
|
|
501
|
+
description: "Generate SyncTeX data",
|
|
502
|
+
},
|
|
503
|
+
extra_args: {
|
|
504
|
+
type: "array",
|
|
505
|
+
items: { type: "string" },
|
|
506
|
+
default: [],
|
|
507
|
+
description: "Extra latexmk CLI arguments to pass through",
|
|
508
|
+
},
|
|
509
|
+
working_dir: {
|
|
510
|
+
type: "string",
|
|
511
|
+
description: "Working directory. Defaults to a fresh temp directory.",
|
|
512
|
+
},
|
|
513
|
+
},
|
|
514
|
+
oneOf: [{ required: ["tex_content"] }, { required: ["file_path"] }],
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
name: "latexmk_draft_compile",
|
|
519
|
+
description:
|
|
520
|
+
"Run a fast single-pass draft compile to quickly surface errors without running multiple passes or bibliography. Good for syntax/error checking during editing.",
|
|
521
|
+
inputSchema: {
|
|
522
|
+
type: "object",
|
|
523
|
+
properties: {
|
|
524
|
+
tex_content: {
|
|
525
|
+
type: "string",
|
|
526
|
+
description: "LaTeX source content",
|
|
527
|
+
},
|
|
528
|
+
file_path: {
|
|
529
|
+
type: "string",
|
|
530
|
+
description: "Absolute path to an existing .tex file",
|
|
531
|
+
},
|
|
532
|
+
engine: {
|
|
533
|
+
type: "string",
|
|
534
|
+
enum: ["pdflatex", "xelatex", "lualatex", "latex"],
|
|
535
|
+
default: "pdflatex",
|
|
536
|
+
},
|
|
537
|
+
working_dir: {
|
|
538
|
+
type: "string",
|
|
539
|
+
description: "Working directory",
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
oneOf: [{ required: ["tex_content"] }, { required: ["file_path"] }],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "latexmk_clean",
|
|
547
|
+
description:
|
|
548
|
+
"Clean LaTeX build artifacts in a directory using `latexmk -c` (auxiliary files only) or `latexmk -C` (auxiliary + output files).",
|
|
549
|
+
inputSchema: {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties: {
|
|
552
|
+
working_dir: {
|
|
553
|
+
type: "string",
|
|
554
|
+
description: "Directory containing the LaTeX build artifacts",
|
|
555
|
+
},
|
|
556
|
+
job_name: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "Specific job name (base filename without extension). Cleans all if omitted.",
|
|
559
|
+
},
|
|
560
|
+
clean_all: {
|
|
561
|
+
type: "boolean",
|
|
562
|
+
default: false,
|
|
563
|
+
description: "If true, uses -C to also remove output files (PDF/DVI/PS). If false, uses -c for auxiliary files only.",
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
required: ["working_dir"],
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "latexmk_check",
|
|
571
|
+
description:
|
|
572
|
+
"Check whether latexmk is installed and which TeX engines are available on this system.",
|
|
573
|
+
inputSchema: {
|
|
574
|
+
type: "object",
|
|
575
|
+
properties: {
|
|
576
|
+
working_dir: {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "Optional working directory (not required for this check)",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "latexmk_list_dependencies",
|
|
585
|
+
description:
|
|
586
|
+
"List all file dependencies of a LaTeX document (included .tex files, .bib files, packages, images, etc.) using `latexmk -deps`.",
|
|
587
|
+
inputSchema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
tex_content: {
|
|
591
|
+
type: "string",
|
|
592
|
+
description: "LaTeX source content",
|
|
593
|
+
},
|
|
594
|
+
file_path: {
|
|
595
|
+
type: "string",
|
|
596
|
+
description: "Absolute path to an existing .tex file",
|
|
597
|
+
},
|
|
598
|
+
working_dir: {
|
|
599
|
+
type: "string",
|
|
600
|
+
description: "Working directory",
|
|
601
|
+
},
|
|
602
|
+
},
|
|
603
|
+
oneOf: [{ required: ["tex_content"] }, { required: ["file_path"] }],
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
];
|
|
607
|
+
|
|
608
|
+
// Server
|
|
609
|
+
|
|
610
|
+
const server = new Server(
|
|
611
|
+
{ name: "latexmk-mcp", version: "1.0.0" },
|
|
612
|
+
{ capabilities: { tools: {} },
|
|
613
|
+
instructions: "Compile, clean, and inspect LaTeX documents using latexmk."
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
);
|
|
617
|
+
|
|
618
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
619
|
+
|
|
620
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
621
|
+
const { name, arguments: args } = request.params;
|
|
622
|
+
|
|
623
|
+
try {
|
|
624
|
+
let result: unknown;
|
|
625
|
+
switch (name) {
|
|
626
|
+
case "latexmk_compile":
|
|
627
|
+
result = await handleCompile(args);
|
|
628
|
+
break;
|
|
629
|
+
case "latexmk_draft_compile":
|
|
630
|
+
result = await handleDraftCompile(args);
|
|
631
|
+
break;
|
|
632
|
+
case "latexmk_clean":
|
|
633
|
+
result = await handleClean(args);
|
|
634
|
+
break;
|
|
635
|
+
case "latexmk_check":
|
|
636
|
+
result = await handleCheck(args);
|
|
637
|
+
break;
|
|
638
|
+
case "latexmk_list_dependencies":
|
|
639
|
+
result = await handleListDependencies(args);
|
|
640
|
+
break;
|
|
641
|
+
default:
|
|
642
|
+
return {
|
|
643
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
644
|
+
isError: true,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
650
|
+
};
|
|
651
|
+
} catch (err: unknown) {
|
|
652
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
653
|
+
return {
|
|
654
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
655
|
+
isError: true,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// Entry
|
|
661
|
+
|
|
662
|
+
async function main() {
|
|
663
|
+
const transport = new StdioServerTransport();
|
|
664
|
+
await server.connect(transport);
|
|
665
|
+
console.error("latexmk MCP server running on stdio");
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
main().catch((err) => {
|
|
669
|
+
console.error("Fatal:", err);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
});
|
package/src/test.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Test
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
/* Base environment */
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"moduleDetection": "force",
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
|
|
9
|
+
/* Building for npm */
|
|
10
|
+
"module": "NodeNext",
|
|
11
|
+
"moduleResolution": "NodeNext",
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"noEmit": false, // Enabled for npm builds
|
|
16
|
+
|
|
17
|
+
/* Best practices */
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
|
|
23
|
+
/* Project structure */
|
|
24
|
+
"rootDir": "src"
|
|
25
|
+
},
|
|
26
|
+
"include": ["src/**/*"]
|
|
27
|
+
}
|