html2pptx-local-mcp 1.1.32 → 1.1.33

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.
@@ -1,3 +1,4 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
1
2
  export interface EditOptions {
2
3
  port?: string;
3
4
  baseUrl?: string;
@@ -17,7 +18,7 @@ declare function normalizeBaseUrl(raw: string): URL;
17
18
  declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
18
19
  declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
19
20
  declare function buildEditorUrl(baseUrl: URL, rel: string, bridgeUrl: string, sessionToken: string): URL;
20
- declare function createBridgeServer(ctx: BridgeContext): any;
21
+ declare function createBridgeServer(ctx: BridgeContext): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
21
22
  declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
22
23
  export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
23
24
  export declare const editCommandInternalsForTest: {
@@ -8,13 +8,11 @@ import * as p from "@clack/prompts";
8
8
  import pc from "picocolors";
9
9
  const AUTO_PORT = 0;
10
10
  const MAX_WRITE_BYTES = 5 * 1024 * 1024;
11
- const MAX_ASSET_BYTES = 64 * 1024 * 1024;
11
+ const MAX_ASSET_BYTES = 8 * 1024 * 1024;
12
12
  const ALLOWED_EXTENSIONS = [".html", ".htm"];
13
13
  const ALLOWED_EXT = new Set(ALLOWED_EXTENSIONS);
14
14
  const ASSET_IMAGE_EXTENSIONS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".avif"];
15
- const ASSET_VIDEO_EXTENSIONS = [".mp4", ".webm", ".mov", ".m4v", ".ogv"];
16
- const ASSET_VIDEO_EXT = new Set(ASSET_VIDEO_EXTENSIONS);
17
- const ASSET_MEDIA_EXT = new Set([...ASSET_IMAGE_EXTENSIONS, ...ASSET_VIDEO_EXTENSIONS]);
15
+ const ASSET_IMAGE_EXT = new Set(ASSET_IMAGE_EXTENSIONS);
18
16
  const ASSET_CONTENT_TYPES = {
19
17
  ".png": "image/png",
20
18
  ".jpg": "image/jpeg",
@@ -23,11 +21,6 @@ const ASSET_CONTENT_TYPES = {
23
21
  ".webp": "image/webp",
24
22
  ".svg": "image/svg+xml",
25
23
  ".avif": "image/avif",
26
- ".mp4": "video/mp4",
27
- ".webm": "video/webm",
28
- ".mov": "video/quicktime",
29
- ".m4v": "video/x-m4v",
30
- ".ogv": "video/ogg",
31
24
  };
32
25
  const ASSET_CONTENT_TYPE_EXT = {
33
26
  "image/png": ".png",
@@ -36,11 +29,6 @@ const ASSET_CONTENT_TYPE_EXT = {
36
29
  "image/webp": ".webp",
37
30
  "image/svg+xml": ".svg",
38
31
  "image/avif": ".avif",
39
- "video/mp4": ".mp4",
40
- "video/webm": ".webm",
41
- "video/quicktime": ".mov",
42
- "video/x-m4v": ".m4v",
43
- "video/ogg": ".ogv",
44
32
  };
45
33
  const DISALLOWED_TOP_DIRECTORIES = [
46
34
  "public",
@@ -472,8 +460,8 @@ async function safeAssetPath(ctx, rel) {
472
460
  throw new Error("path escape");
473
461
  }
474
462
  const ext = extname(abs).toLowerCase();
475
- if (!ASSET_MEDIA_EXT.has(ext)) {
476
- throw new Error("only image, GIF, and video files are allowed");
463
+ if (!ASSET_IMAGE_EXT.has(ext)) {
464
+ throw new Error("only image files are allowed");
477
465
  }
478
466
  const real = await resolveReal(abs);
479
467
  if (real !== ctx.root && !real.startsWith(ctx.root + sep)) {
@@ -489,21 +477,18 @@ async function safeAssetPath(ctx, rel) {
489
477
  }
490
478
  function assetExt(name, contentType) {
491
479
  const fromName = extname(String(name || "")).toLowerCase();
492
- if (ASSET_MEDIA_EXT.has(fromName))
480
+ if (ASSET_IMAGE_EXT.has(fromName))
493
481
  return fromName;
494
482
  return ASSET_CONTENT_TYPE_EXT[String(contentType || "").toLowerCase()] || "";
495
483
  }
496
484
  function assetSlug(name) {
497
- const base = String(name || "asset").replace(/\.[^.]+$/, "");
485
+ const base = String(name || "image").replace(/\.[^.]+$/, "");
498
486
  const slug = base
499
487
  .normalize("NFKD")
500
488
  .replace(/[^\w.-]+/g, "-")
501
489
  .replace(/^[-.]+|[-.]+$/g, "")
502
490
  .toLowerCase();
503
- return slug || "asset";
504
- }
505
- function assetKind(ext) {
506
- return ASSET_VIDEO_EXT.has(ext) ? "video" : "image";
491
+ return slug || "image";
507
492
  }
508
493
  function assetDirRel(scope, htmlDirRel) {
509
494
  if (scope === "global")
@@ -545,11 +530,10 @@ async function handleAssetGet(ctx, req, reqUrl, res) {
545
530
  if (absDir === ctx.root || absDir.startsWith(ctx.root + sep)) {
546
531
  const names = await readdir(absDir);
547
532
  assets = names
548
- .filter((name) => ASSET_MEDIA_EXT.has(extname(name).toLowerCase()))
533
+ .filter((name) => ASSET_IMAGE_EXT.has(extname(name).toLowerCase()))
549
534
  .map((name) => ({
550
535
  name,
551
536
  src: toPosixPath(relative(htmlDirAbs, join(absDir, name))),
552
- kind: assetKind(extname(name).toLowerCase()),
553
537
  }));
554
538
  }
555
539
  }
@@ -592,21 +576,21 @@ async function handleAssetPost(ctx, req, res) {
592
576
  }
593
577
  const { file, scope: rawScope, name, contentType, dataBase64 } = body || {};
594
578
  if (typeof dataBase64 !== "string" || !dataBase64) {
595
- sendJson(res, 400, { error: "missing asset data" });
579
+ sendJson(res, 400, { error: "missing image data" });
596
580
  return;
597
581
  }
598
582
  const buf = Buffer.from(dataBase64, "base64");
599
583
  if (!buf.length) {
600
- sendJson(res, 400, { error: "empty asset data" });
584
+ sendJson(res, 400, { error: "empty image data" });
601
585
  return;
602
586
  }
603
587
  if (buf.length > MAX_ASSET_BYTES) {
604
- sendJson(res, 413, { error: "asset too large (>64MB)" });
588
+ sendJson(res, 413, { error: "image too large (>8MB)" });
605
589
  return;
606
590
  }
607
591
  const ext = assetExt(name, contentType);
608
- if (!ASSET_MEDIA_EXT.has(ext)) {
609
- sendJson(res, 400, { error: "unsupported asset type" });
592
+ if (!ASSET_IMAGE_EXT.has(ext)) {
593
+ sendJson(res, 400, { error: "unsupported image type" });
610
594
  return;
611
595
  }
612
596
  const scope = rawScope === "global" ? "global" : "project";
@@ -633,7 +617,6 @@ async function handleAssetPost(ctx, req, res) {
633
617
  name: fileName,
634
618
  path: toPosixPath(relative(ctx.root, absTarget)),
635
619
  src: toPosixPath(relative(htmlDirAbs, absTarget)),
636
- kind: assetKind(ext),
637
620
  bytes: buf.length,
638
621
  reused: alreadyIdentical,
639
622
  policy: buildPolicy(ctx),
package/cli/dist/index.js CHANGED
File without changes
@@ -1,5 +1,5 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
2
+ import { existsSync, readdirSync, rmSync } from 'node:fs';
3
3
  import net from 'node:net';
4
4
  import { access } from 'node:fs/promises';
5
5
  import { dirname, join, resolve } from 'node:path';
@@ -64,6 +64,21 @@ export function createLocalEditorServerManager(options = {}) {
64
64
  const baseUrl = `http://localhost:${port}`;
65
65
  const invocation = await resolveNextInvocation(appRoot, options);
66
66
 
67
+ // Give every editor instance its own Turbopack build dir. A shared `.next`
68
+ // gets corrupted two ways, both surfacing as a runtime ChunkLoadError:
69
+ // (a) npx reuses one cache dir across `@latest` version bumps, so a stale
70
+ // `.next` from the previous version is loaded against new source.
71
+ // (b) editors from different projects/sessions run `next dev` against the
72
+ // same appRoot concurrently and overwrite each other's chunks.
73
+ // The bundled next.config reads distDir from HTML2PPTX_EDITOR_DIST_DIR.
74
+ const distDir = `.next-${port}`;
75
+ await pruneStaleEditorDistDirs(appRoot);
76
+ try {
77
+ rmSync(join(appRoot, distDir), { recursive: true, force: true });
78
+ } catch {
79
+ // Best-effort: a fresh build dir is preferred but not required.
80
+ }
81
+
67
82
  // Issue #78: do not pass `--webpack`. Next.js 16.2.x webpack mode fails
68
83
  // to compile App Router `.jsx` files (next-swc-loader emits JSX that the
69
84
  // webpack default parser then chokes on with "Unexpected token"). Use
@@ -79,6 +94,7 @@ export function createLocalEditorServerManager(options = {}) {
79
94
  env: {
80
95
  ...process.env,
81
96
  HTML2PPTX_EDITOR_BASE_URL: baseUrl,
97
+ HTML2PPTX_EDITOR_DIST_DIR: distDir,
82
98
  NEXT_TELEMETRY_DISABLED: '1',
83
99
  PORT: String(port),
84
100
  },
@@ -206,6 +222,30 @@ export async function isEditorServer(port) {
206
222
  }
207
223
  }
208
224
 
225
+ // Remove per-instance `.next-<port>` build dirs whose editor server is gone, so
226
+ // isolated build caches from crashed/closed sessions do not accumulate. The dir
227
+ // for a still-listening editor is kept. Best-effort: never throws.
228
+ async function pruneStaleEditorDistDirs(appRoot) {
229
+ let entries;
230
+ try {
231
+ entries = readdirSync(appRoot, { withFileTypes: true });
232
+ } catch {
233
+ return;
234
+ }
235
+ for (const entry of entries) {
236
+ if (!entry.isDirectory()) continue;
237
+ const match = /^\.next-(\d+)$/.exec(entry.name);
238
+ if (!match) continue;
239
+ const port = Number.parseInt(match[1], 10);
240
+ if (await isEditorServer(port)) continue;
241
+ try {
242
+ rmSync(join(appRoot, entry.name), { recursive: true, force: true });
243
+ } catch {
244
+ // Best-effort cleanup.
245
+ }
246
+ }
247
+ }
248
+
209
249
  function waitForEditorServer(session, timeoutMs) {
210
250
  return new Promise((resolvePromise, rejectPromise) => {
211
251
  const startedAt = Date.now();
@@ -22,6 +22,7 @@ const localEditorSecurityHeaders = [
22
22
  ];
23
23
 
24
24
  export default {
25
+ distDir: process.env.HTML2PPTX_EDITOR_DIST_DIR || '.next',
25
26
  async headers() {
26
27
  return [
27
28
  { source: '/edit-slide', headers: localEditorSecurityHeaders },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "html2pptx-local-mcp",
3
- "version": "1.1.32",
3
+ "version": "1.1.33",
4
4
  "type": "module",
5
5
  "description": "Local stdio MCP server for opening html2pptx slide HTML in the local edit-slide editor.",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- export declare function configShowCommand(): Promise<void>;
@@ -1,16 +0,0 @@
1
- import * as p from "@clack/prompts";
2
- import pc from "picocolors";
3
- import { loadConfig, getConfigPath } from "../config.js";
4
- export async function configShowCommand() {
5
- const config = await loadConfig();
6
- const configPath = getConfigPath();
7
- p.log.info(pc.bold("Config file: ") + pc.dim(configPath));
8
- if (!config.apiKey) {
9
- p.log.warn("No configuration found. Run " + pc.cyan("html2pptx login") + " to set up.");
10
- return;
11
- }
12
- p.log.info(pc.bold("API Key: ") +
13
- pc.dim(config.apiKey.slice(0, 12) + "..." + config.apiKey.slice(-4)));
14
- p.log.info(pc.bold("API Base URL: ") +
15
- (config.baseUrl ?? "https://html2pptx.app") + pc.dim(" (default)"));
16
- }
@@ -1,10 +0,0 @@
1
- interface ConvertOptions {
2
- output?: string;
3
- size?: string;
4
- css?: string;
5
- json?: boolean;
6
- open?: boolean;
7
- baseUrl?: string;
8
- }
9
- export declare function convertCommand(input?: string, options?: ConvertOptions): Promise<void>;
10
- export {};
@@ -1,311 +0,0 @@
1
- import { readFile, writeFile } from "node:fs/promises";
2
- import { exec } from "node:child_process";
3
- import { platform } from "node:os";
4
- import { basename, resolve } from "node:path";
5
- import * as p from "@clack/prompts";
6
- import pc from "picocolors";
7
- import { loadConfig } from "../config.js";
8
- function parseSize(size) {
9
- if (size === "16:9")
10
- return { layout: "LAYOUT_16x9" };
11
- if (size === "4:3")
12
- return { layout: "LAYOUT_4x3" };
13
- const match = size.match(/^(\d+)x(\d+)$/);
14
- if (match)
15
- return { width: parseInt(match[1]), height: parseInt(match[2]) };
16
- return { layout: "LAYOUT_16x9" };
17
- }
18
- function formatDuration(ms) {
19
- if (ms < 1000)
20
- return `${ms}ms`;
21
- return `${(ms / 1000).toFixed(1)}s`;
22
- }
23
- function openFile(filePath) {
24
- const os = platform();
25
- const cmd = os === "darwin" ? "open" : os === "win32" ? "start" : "xdg-open";
26
- exec(`${cmd} "${filePath}"`);
27
- }
28
- async function interactivePrompt() {
29
- p.intro(pc.bgCyan(pc.black(" html2pptx convert ")));
30
- const result = await p.group({
31
- input: () => p.text({
32
- message: "HTML file to convert",
33
- placeholder: "./slides.html",
34
- validate(value) {
35
- if (!value)
36
- return "HTML file path is required";
37
- if (!value.endsWith(".html") && !value.endsWith(".htm"))
38
- return "File must be .html or .htm";
39
- },
40
- }),
41
- size: () => p.select({
42
- message: "Slide size",
43
- options: [
44
- { value: "16:9", label: "16:9 (1600x900)", hint: "default" },
45
- { value: "4:3", label: "4:3 (1024x768)" },
46
- { value: "custom", label: "Custom size" },
47
- ],
48
- }),
49
- customSize: ({ results }) => {
50
- if (results.size !== "custom")
51
- return;
52
- return p.text({
53
- message: "Enter custom size (WxH)",
54
- placeholder: "1920x1080",
55
- validate(value) {
56
- if (!value?.match(/^\d+x\d+$/))
57
- return 'Format: WIDTHxHEIGHT (e.g. 1920x1080)';
58
- },
59
- });
60
- },
61
- css: () => p.text({
62
- message: "External CSS file (optional)",
63
- placeholder: "Press Enter to skip",
64
- }),
65
- output: ({ results }) => {
66
- const defaultName = basename(results.input).replace(/\.html?$/, "") + ".pptx";
67
- return p.text({
68
- message: "Output filename",
69
- placeholder: defaultName,
70
- initialValue: defaultName,
71
- });
72
- },
73
- }, {
74
- onCancel() {
75
- p.cancel("Conversion cancelled.");
76
- process.exit(0);
77
- },
78
- });
79
- return {
80
- input: result.input,
81
- output: result.output,
82
- size: result.customSize ?? result.size,
83
- css: result.css || undefined,
84
- };
85
- }
86
- async function runExport(html, css, size, fileName, baseUrl, apiKey) {
87
- const sizeParams = parseSize(size);
88
- const body = {
89
- html,
90
- fileName,
91
- responseFormat: "url",
92
- ...sizeParams,
93
- };
94
- if (css)
95
- body.css = css;
96
- const res = await fetch(`${baseUrl}/api/v1/export/jobs`, {
97
- method: "POST",
98
- headers: {
99
- "Content-Type": "application/json",
100
- Authorization: `Bearer ${apiKey}`,
101
- },
102
- body: JSON.stringify(body),
103
- });
104
- if (res.status === 401) {
105
- throw new Error("Invalid API key. Run `html2pptx login` to update your key.");
106
- }
107
- if (res.status === 403) {
108
- throw new Error("Access denied. Your API key may be expired or your plan may have changed. Visit https://html2pptx.app/dashboard to check.");
109
- }
110
- if (res.status === 413) {
111
- throw new Error("HTML payload too large for your plan. Try reducing the HTML size or upgrading your plan.");
112
- }
113
- if (res.status === 429) {
114
- throw new Error("Rate limit exceeded. Please wait a moment and try again, or upgrade your plan for higher limits.");
115
- }
116
- if (!res.ok) {
117
- const text = await res.text();
118
- throw new Error(`API error ${res.status}: ${text}`);
119
- }
120
- return (await res.json());
121
- }
122
- async function pollJob(statusUrl, apiKey, baseUrl) {
123
- if (!statusUrl.startsWith(baseUrl)) {
124
- throw new Error("Unexpected status URL from server");
125
- }
126
- const maxAttempts = 60;
127
- for (let i = 0; i < maxAttempts; i++) {
128
- await new Promise((r) => setTimeout(r, 2000));
129
- const res = await fetch(statusUrl, {
130
- headers: { Authorization: `Bearer ${apiKey}` },
131
- });
132
- if (!res.ok) {
133
- const text = await res.text();
134
- throw new Error(`Poll error ${res.status}: ${text}`);
135
- }
136
- const data = (await res.json());
137
- if (data.status === "completed")
138
- return data;
139
- if (data.status === "failed")
140
- throw new Error(data.error ?? "Export failed");
141
- }
142
- throw new Error("Export timed out after 2 minutes");
143
- }
144
- async function downloadFile(url, outputPath) {
145
- const res = await fetch(url);
146
- if (!res.ok)
147
- throw new Error(`Download failed: ${res.status}`);
148
- const buffer = Buffer.from(await res.arrayBuffer());
149
- await writeFile(outputPath, buffer);
150
- return buffer.byteLength;
151
- }
152
- function formatBytes(bytes) {
153
- if (bytes < 1024)
154
- return `${bytes} B`;
155
- if (bytes < 1024 * 1024)
156
- return `${(bytes / 1024).toFixed(1)} KB`;
157
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
158
- }
159
- export async function convertCommand(input, options = {}) {
160
- const config = await loadConfig();
161
- const apiKey = config.apiKey;
162
- const baseUrl = options.baseUrl ?? config.baseUrl ?? "https://html2pptx.app";
163
- const jsonMode = options.json ?? false;
164
- if (!apiKey) {
165
- if (jsonMode) {
166
- console.log(JSON.stringify({ success: false, error: "No API key found. Run html2pptx login first." }));
167
- process.exit(1);
168
- }
169
- p.log.error("No API key found. Run " + pc.cyan("html2pptx login") + " first.");
170
- process.exit(1);
171
- }
172
- let htmlPath;
173
- let outputFile;
174
- let size;
175
- let cssPath;
176
- // If args provided -> direct mode, else -> interactive mode
177
- if (input) {
178
- htmlPath = resolve(input);
179
- outputFile =
180
- options.output ?? basename(htmlPath).replace(/\.html?$/, "") + ".pptx";
181
- size = options.size ?? "16:9";
182
- cssPath = options.css;
183
- }
184
- else {
185
- const answers = await interactivePrompt();
186
- htmlPath = resolve(answers.input);
187
- outputFile = answers.output;
188
- size = answers.size;
189
- cssPath = answers.css;
190
- }
191
- const startTime = Date.now();
192
- const spinner = jsonMode ? null : p.spinner();
193
- spinner?.start("Reading HTML file...");
194
- let html;
195
- try {
196
- html = await readFile(htmlPath, "utf-8");
197
- }
198
- catch {
199
- if (jsonMode) {
200
- console.log(JSON.stringify({ success: false, error: `Could not read file: ${htmlPath}` }));
201
- }
202
- else {
203
- spinner?.stop("Failed");
204
- p.log.error(`Could not read file: ${pc.dim(htmlPath)}`);
205
- }
206
- process.exit(1);
207
- }
208
- let css;
209
- if (cssPath) {
210
- try {
211
- css = await readFile(resolve(cssPath), "utf-8");
212
- }
213
- catch {
214
- if (jsonMode) {
215
- console.log(JSON.stringify({ success: false, error: `Could not read CSS file: ${cssPath}` }));
216
- }
217
- else {
218
- spinner?.stop("Failed");
219
- p.log.error(`Could not read CSS file: ${pc.dim(cssPath)}`);
220
- }
221
- process.exit(1);
222
- }
223
- }
224
- spinner?.message("Sending to html2pptx API...");
225
- let job;
226
- try {
227
- job = await runExport(html, css, size, outputFile, baseUrl, apiKey);
228
- }
229
- catch (e) {
230
- const errMsg = e.message;
231
- if (jsonMode) {
232
- console.log(JSON.stringify({ success: false, error: errMsg }));
233
- }
234
- else {
235
- spinner?.stop("Failed");
236
- p.log.error(errMsg);
237
- }
238
- process.exit(1);
239
- }
240
- const finishConversion = async (downloadUrl) => {
241
- spinner?.message("Downloading PPTX...");
242
- const fileSize = await downloadFile(downloadUrl, resolve(outputFile));
243
- const duration = Date.now() - startTime;
244
- if (jsonMode) {
245
- const result = {
246
- success: true,
247
- file: outputFile,
248
- size: formatBytes(fileSize),
249
- duration: formatDuration(duration),
250
- };
251
- console.log(JSON.stringify(result));
252
- }
253
- else {
254
- spinner?.stop(pc.green("Done!"));
255
- p.log.success(`Saved to ${pc.cyan(outputFile)} ${pc.dim(`(${formatBytes(fileSize)}, ${formatDuration(duration)})`)}`);
256
- // Next steps guidance
257
- p.log.info(pc.dim("Next: ") +
258
- `Open the file or run ${pc.cyan("html2pptx status")} to check your usage.`);
259
- }
260
- if (options.open) {
261
- openFile(resolve(outputFile));
262
- }
263
- };
264
- // If job completed immediately
265
- if (job.status === "completed" && job.downloadUrl) {
266
- try {
267
- await finishConversion(job.downloadUrl);
268
- return;
269
- }
270
- catch (e) {
271
- const errMsg = e.message;
272
- if (jsonMode) {
273
- console.log(JSON.stringify({ success: false, error: errMsg }));
274
- }
275
- else {
276
- spinner?.stop("Failed");
277
- p.log.error(errMsg);
278
- }
279
- process.exit(1);
280
- }
281
- }
282
- // Poll for completion
283
- spinner?.message("Converting... this may take a moment");
284
- try {
285
- const result = await pollJob(job.statusUrl, apiKey, baseUrl);
286
- if (result.downloadUrl) {
287
- await finishConversion(result.downloadUrl);
288
- }
289
- else {
290
- if (jsonMode) {
291
- console.log(JSON.stringify({ success: false, error: "No download URL returned" }));
292
- }
293
- else {
294
- spinner?.stop("Failed");
295
- p.log.error("No download URL returned");
296
- }
297
- process.exit(1);
298
- }
299
- }
300
- catch (e) {
301
- const errMsg = e.message;
302
- if (jsonMode) {
303
- console.log(JSON.stringify({ success: false, error: errMsg }));
304
- }
305
- else {
306
- spinner?.stop("Failed");
307
- p.log.error(errMsg);
308
- }
309
- process.exit(1);
310
- }
311
- }
@@ -1,34 +0,0 @@
1
- import { type IncomingMessage, type ServerResponse } from "node:http";
2
- export interface EditOptions {
3
- port?: string;
4
- baseUrl?: string;
5
- noOpen?: boolean;
6
- open?: boolean;
7
- json?: boolean;
8
- }
9
- interface BridgeContext {
10
- root: string;
11
- editorOrigin: string;
12
- localStateDir: string;
13
- sessionToken: string;
14
- }
15
- declare function generateSessionToken(): string;
16
- declare function parsePort(value: string | undefined): number;
17
- declare function normalizeBaseUrl(raw: string): URL;
18
- declare function readRegisteredEditorBaseUrl(root: string): Promise<string | null>;
19
- declare function resolveEditorBaseUrl(root: string, explicitBaseUrl: string | undefined): Promise<URL>;
20
- declare function buildEditorUrl(baseUrl: URL, rel: string, bridgeUrl: string, sessionToken: string): URL;
21
- declare function createBridgeServer(ctx: BridgeContext): import("http").Server<typeof IncomingMessage, typeof ServerResponse>;
22
- declare function listen(server: ReturnType<typeof createBridgeServer>, requestedPort: number): Promise<number>;
23
- export declare function editCommand(input: string | undefined, options?: EditOptions): Promise<void>;
24
- export declare const editCommandInternalsForTest: {
25
- buildEditorUrl: typeof buildEditorUrl;
26
- createBridgeServer: typeof createBridgeServer;
27
- generateSessionToken: typeof generateSessionToken;
28
- listen: typeof listen;
29
- normalizeBaseUrl: typeof normalizeBaseUrl;
30
- parsePort: typeof parsePort;
31
- readRegisteredEditorBaseUrl: typeof readRegisteredEditorBaseUrl;
32
- resolveEditorBaseUrl: typeof resolveEditorBaseUrl;
33
- };
34
- export {};