@zkov/pi-md-viewer 0.1.2 → 0.1.3
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/dist/src-ts/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { discover, portRangeFromEnv, sendFiles, shutdownInstance } from "./disco
|
|
|
9
9
|
import { FileRegistry } from "./registry.js";
|
|
10
10
|
import { MarkdownRenderer } from "./renderer.js";
|
|
11
11
|
import { ViewerServer } from "./server.js";
|
|
12
|
-
import { launchViewer } from "./viewer-launcher.js";
|
|
12
|
+
import { launchViewer, runPlaywrightViewerHost } from "./viewer-launcher.js";
|
|
13
13
|
const STARTUP_TIMEOUT_MS = 5000;
|
|
14
14
|
function operationResult(payload) {
|
|
15
15
|
return { url: null, added: [], rejected: [], viewerClients: 0, ...payload };
|
|
@@ -50,11 +50,20 @@ function parseArgs(argv, cwd) {
|
|
|
50
50
|
parsed.viewer = requireValue(argv, ++index, arg);
|
|
51
51
|
else if (arg === "--viewer-command")
|
|
52
52
|
parsed.viewerCommand = requireValue(argv, ++index, arg);
|
|
53
|
+
else if (arg === "--playwright-viewer")
|
|
54
|
+
parsed.playwrightViewer = requireValue(argv, ++index, arg);
|
|
55
|
+
else if (arg === "--browser")
|
|
56
|
+
parsed.browser = parseBrowser(requireValue(argv, ++index, arg));
|
|
53
57
|
else
|
|
54
58
|
parsed.paths.push(arg);
|
|
55
59
|
}
|
|
56
60
|
return parsed;
|
|
57
61
|
}
|
|
62
|
+
function parseBrowser(value) {
|
|
63
|
+
if (value === "chromium" || value === "firefox" || value === "webkit")
|
|
64
|
+
return value;
|
|
65
|
+
throw new Error("--browser must be chromium, firefox, or webkit");
|
|
66
|
+
}
|
|
58
67
|
function requireValue(argv, index, flag) {
|
|
59
68
|
const value = argv[index];
|
|
60
69
|
if (value === undefined)
|
|
@@ -78,6 +87,10 @@ export async function main(argv, io = {}) {
|
|
|
78
87
|
let args;
|
|
79
88
|
try {
|
|
80
89
|
args = parseArgs(argv, process.cwd());
|
|
90
|
+
if (args.playwrightViewer) {
|
|
91
|
+
await runPlaywrightViewerHost({ url: args.playwrightViewer, browserName: args.browser ?? "chromium", env });
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
81
94
|
if (args.serve)
|
|
82
95
|
return await serve(args.port, args.paths);
|
|
83
96
|
const result = await operate(args, env);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
2
5
|
import { headedPlaywrightUnavailableReason, systemOpenCommand } from "./platform.js";
|
|
3
6
|
import { expandViewerCommand, parseViewerCommand } from "./viewer-command.js";
|
|
4
7
|
async function defaultSpawnDetached(command, args) {
|
|
@@ -51,37 +54,129 @@ function playwrightLaunchOptions(browserName, platform, env) {
|
|
|
51
54
|
return options;
|
|
52
55
|
}
|
|
53
56
|
async function launchPlaywright(request) {
|
|
54
|
-
const
|
|
57
|
+
const browserName = request.selection.config.playwright.browser;
|
|
58
|
+
if (!request.importPlaywright) {
|
|
59
|
+
await launchDetachedPlaywrightHost(request.url, browserName, request.spawnPlaywrightHost ?? defaultSpawnPlaywrightHost);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await openPlaywrightPage({
|
|
63
|
+
url: request.url,
|
|
64
|
+
browserName,
|
|
65
|
+
platform: request.platform,
|
|
66
|
+
env: request.env,
|
|
67
|
+
importPlaywright: request.importPlaywright,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function openPlaywrightPage(options) {
|
|
71
|
+
const reason = headedPlaywrightUnavailableReason(options.platform, options.env);
|
|
55
72
|
if (reason === "unsupported-platform")
|
|
56
73
|
throw new Error('Viewer "playwright" is not available on this platform');
|
|
57
74
|
if (reason)
|
|
58
75
|
throw new Error(`Viewer "playwright" is not available: ${reason}`);
|
|
59
|
-
const browserName = request.selection.config.playwright.browser;
|
|
60
76
|
let imported;
|
|
61
77
|
try {
|
|
62
|
-
imported =
|
|
78
|
+
imported = options.importPlaywright ? await options.importPlaywright() : await import("playwright");
|
|
63
79
|
}
|
|
64
80
|
catch (error) {
|
|
65
81
|
if (isMissingPlaywrightPackage(error))
|
|
66
|
-
throw new Error(playwrightInstallHint(browserName));
|
|
82
|
+
throw new Error(playwrightInstallHint(options.browserName));
|
|
67
83
|
throw error;
|
|
68
84
|
}
|
|
69
85
|
const api = imported;
|
|
70
|
-
const browserType = api[browserName];
|
|
86
|
+
const browserType = api[options.browserName];
|
|
71
87
|
if (!browserType)
|
|
72
|
-
throw new Error(`Playwright browser "${browserName}" is unavailable`);
|
|
88
|
+
throw new Error(`Playwright browser "${options.browserName}" is unavailable`);
|
|
73
89
|
let browser;
|
|
74
90
|
try {
|
|
75
|
-
browser = await browserType.launch(playwrightLaunchOptions(browserName,
|
|
91
|
+
browser = await browserType.launch(playwrightLaunchOptions(options.browserName, options.platform, options.env));
|
|
76
92
|
}
|
|
77
93
|
catch (error) {
|
|
78
94
|
if (isMissingPlaywrightBrowser(error))
|
|
79
|
-
throw new Error(playwrightBrowserInstallHint(browserName));
|
|
95
|
+
throw new Error(playwrightBrowserInstallHint(options.browserName));
|
|
80
96
|
throw error;
|
|
81
97
|
}
|
|
82
98
|
const context = await browser.newContext({ viewport: null });
|
|
83
99
|
const page = await context.newPage();
|
|
84
|
-
await page.goto(
|
|
100
|
+
await page.goto(options.url);
|
|
101
|
+
return browser;
|
|
102
|
+
}
|
|
103
|
+
async function launchDetachedPlaywrightHost(url, browserName, spawnPlaywrightHost) {
|
|
104
|
+
const result = await spawnPlaywrightHost(["--playwright-viewer", url, "--browser", browserName]);
|
|
105
|
+
if (!result.ok)
|
|
106
|
+
throw new Error(result.message);
|
|
107
|
+
}
|
|
108
|
+
function defaultSpawnPlaywrightHost(args) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const child = spawn(process.execPath, [entrypointPath(), ...args], {
|
|
111
|
+
detached: true,
|
|
112
|
+
stdio: ["ignore", "pipe", "pipe", "ignore"],
|
|
113
|
+
windowsHide: true,
|
|
114
|
+
env: { ...process.env, NODE_NO_WARNINGS: "1" },
|
|
115
|
+
});
|
|
116
|
+
let stdout = "";
|
|
117
|
+
let stderr = "";
|
|
118
|
+
let settled = false;
|
|
119
|
+
const timeout = setTimeout(() => finish({ ok: false, message: "Timed out waiting for Playwright viewer to open" }), 10_000);
|
|
120
|
+
function finish(result) {
|
|
121
|
+
if (settled)
|
|
122
|
+
return;
|
|
123
|
+
settled = true;
|
|
124
|
+
clearTimeout(timeout);
|
|
125
|
+
child.stdout?.destroy();
|
|
126
|
+
child.stderr?.destroy();
|
|
127
|
+
if (result.ok)
|
|
128
|
+
child.unref();
|
|
129
|
+
resolve(result);
|
|
130
|
+
}
|
|
131
|
+
child.once("error", reject);
|
|
132
|
+
child.once("exit", (code) => {
|
|
133
|
+
if (!settled)
|
|
134
|
+
finish({ ok: false, message: stderr.trim() || `Playwright viewer exited with status ${code ?? "unknown"}` });
|
|
135
|
+
});
|
|
136
|
+
child.stdout?.setEncoding("utf8");
|
|
137
|
+
child.stdout?.on("data", (chunk) => {
|
|
138
|
+
stdout += chunk;
|
|
139
|
+
const newline = stdout.indexOf("\n");
|
|
140
|
+
if (newline === -1)
|
|
141
|
+
return;
|
|
142
|
+
const line = stdout.slice(0, newline).trim();
|
|
143
|
+
try {
|
|
144
|
+
const payload = JSON.parse(line);
|
|
145
|
+
finish(payload.ok === true ? { ok: true } : { ok: false, message: typeof payload.message === "string" ? payload.message : "Playwright viewer failed to open" });
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
finish({ ok: false, message: `Invalid Playwright viewer response: ${error instanceof Error ? error.message : String(error)}` });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
child.stderr?.setEncoding("utf8");
|
|
152
|
+
child.stderr?.on("data", (chunk) => { stderr += chunk; });
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function entrypointPath() {
|
|
156
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
157
|
+
const dist = path.join(root, "dist", "src-ts", "main.js");
|
|
158
|
+
if (existsSync(dist))
|
|
159
|
+
return dist;
|
|
160
|
+
return fileURLToPath(import.meta.url).replace(/viewer-launcher\.(ts|js)$/, "main.js");
|
|
161
|
+
}
|
|
162
|
+
export async function runPlaywrightViewerHost(options) {
|
|
163
|
+
const output = options.stdout ?? console.log;
|
|
164
|
+
try {
|
|
165
|
+
const browser = await openPlaywrightPage({
|
|
166
|
+
url: options.url,
|
|
167
|
+
browserName: options.browserName,
|
|
168
|
+
platform: options.platform ?? process.platform,
|
|
169
|
+
env: options.env ?? process.env,
|
|
170
|
+
importPlaywright: options.importPlaywright,
|
|
171
|
+
});
|
|
172
|
+
output(JSON.stringify({ ok: true }));
|
|
173
|
+
await new Promise((resolve) => {
|
|
174
|
+
browser.on?.("disconnected", resolve);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
output(JSON.stringify({ ok: false, message: error instanceof Error ? error.message : String(error) }));
|
|
179
|
+
}
|
|
85
180
|
}
|
|
86
181
|
export async function launchViewer(request) {
|
|
87
182
|
const spawnDetached = request.spawnDetached ?? defaultSpawnDetached;
|
package/package.json
CHANGED
|
@@ -247,22 +247,26 @@ hr { margin: 40px 0; border: 0; border-top: 1px solid var(--line); }
|
|
|
247
247
|
@media (max-width: 980px) {
|
|
248
248
|
.mobile-bar {
|
|
249
249
|
position: sticky;
|
|
250
|
-
z-index:
|
|
250
|
+
z-index: 40;
|
|
251
251
|
top: 0;
|
|
252
252
|
display: flex;
|
|
253
253
|
align-items: center;
|
|
254
254
|
justify-content: space-between;
|
|
255
|
+
min-height: 49px;
|
|
255
256
|
padding: 10px 16px;
|
|
256
257
|
border-bottom: 1px solid var(--line);
|
|
257
258
|
background: rgba(10, 13, 18, .94);
|
|
258
259
|
}
|
|
259
|
-
.layout { display: block; padding:
|
|
260
|
+
.layout { display: block; padding: 28px 18px 80px; }
|
|
260
261
|
.files-panel {
|
|
261
262
|
display: none;
|
|
262
|
-
position:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
263
|
+
position: fixed;
|
|
264
|
+
z-index: 35;
|
|
265
|
+
top: 57px;
|
|
266
|
+
left: 18px;
|
|
267
|
+
right: 18px;
|
|
268
|
+
max-height: calc(100vh - 73px);
|
|
269
|
+
margin: 0 auto;
|
|
266
270
|
max-width: 820px;
|
|
267
271
|
}
|
|
268
272
|
.files-panel.open { display: block; }
|