@hirokisakabe/pom-cli 0.3.1 → 0.4.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/README.md +17 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +4 -8
- package/dist/cli.js +5 -1
- package/dist/preview.d.ts +1 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +50 -9
- package/dist/watch.d.ts +3 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +37 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ CLI tool for [pom](https://github.com/hirokisakabe/pom) — preview and build pr
|
|
|
8
8
|
npm install -g @hirokisakabe/pom-cli
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
One command is all it takes — no global install required:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @hirokisakabe/pom-cli preview slides.pom.xml
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
This starts a local preview server, opens your browser automatically, and live-reloads the preview every time the file is saved. This also works well when invoked from agent skills or scripts.
|
|
20
|
+
|
|
11
21
|
## Usage
|
|
12
22
|
|
|
13
23
|
### Preview
|
|
@@ -19,7 +29,13 @@ pom preview slides.pom.xml
|
|
|
19
29
|
pom preview slides.pom.md
|
|
20
30
|
```
|
|
21
31
|
|
|
22
|
-
|
|
32
|
+
The browser opens http://localhost:3000 automatically. The page updates whenever the file is saved — including atomic saves performed by editors like Vim.
|
|
33
|
+
|
|
34
|
+
To suppress the automatic browser open (e.g. in CI or headless environments):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pom preview slides.pom.xml --no-open
|
|
38
|
+
```
|
|
23
39
|
|
|
24
40
|
To use a different port (e.g. when 3000 is already in use):
|
|
25
41
|
|
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAWA,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,OAAO,CAAC,IAAI,CAAC,CAwEf;AAED,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC,OAAO,CAAC,IAAI,CAAC,CAuDf"}
|
package/dist/build.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { buildPptx, DiagnosticsError } from "@hirokisakabe/pom";
|
|
4
4
|
import { parseMd } from "@hirokisakabe/pom-md";
|
|
5
|
+
import { watchInputFile } from "./watch.js";
|
|
5
6
|
function makeLog(verbose) {
|
|
6
7
|
if (!verbose)
|
|
7
8
|
return (_msg) => { };
|
|
@@ -110,13 +111,8 @@ export async function runBuildWatch(inputFile, outputFile, options = {}) {
|
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
await doBuild();
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
clearTimeout(debounceTimer);
|
|
117
|
-
debounceTimer = setTimeout(() => {
|
|
118
|
-
watchLog("File changed, rebuilding...");
|
|
119
|
-
void doBuild();
|
|
120
|
-
}, 100);
|
|
114
|
+
watchInputFile(absInput, () => {
|
|
115
|
+
watchLog("File changed, rebuilding...");
|
|
116
|
+
void doBuild();
|
|
121
117
|
});
|
|
122
118
|
}
|
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ program
|
|
|
17
17
|
.argument("<input>", "Input file (.pom.xml or .pom.md)")
|
|
18
18
|
.option("--port <number>", "Port to listen on")
|
|
19
19
|
.option("--verbose", "Show build step timing on stderr")
|
|
20
|
+
.option("--no-open", "Do not open the browser automatically")
|
|
20
21
|
.action((input, options) => {
|
|
21
22
|
let port;
|
|
22
23
|
if (options.port !== undefined) {
|
|
@@ -27,7 +28,10 @@ program
|
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
|
-
runPreview(input, port, {
|
|
31
|
+
runPreview(input, port, {
|
|
32
|
+
verbose: options.verbose,
|
|
33
|
+
open: options.open,
|
|
34
|
+
});
|
|
31
35
|
}
|
|
32
36
|
catch (err) {
|
|
33
37
|
console.error(err instanceof Error ? err.message : String(err));
|
package/dist/preview.d.ts
CHANGED
package/dist/preview.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAqZA,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAAqB,EAC3B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GAClD,IAAI,CAmIN"}
|
package/dist/preview.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
1
2
|
import fs from "fs";
|
|
2
3
|
import http from "http";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
@@ -5,6 +6,7 @@ import path from "path";
|
|
|
5
6
|
import { buildPptx } from "@hirokisakabe/pom";
|
|
6
7
|
import { parseMd } from "@hirokisakabe/pom-md";
|
|
7
8
|
import { convertPptxToSvg } from "pptx-glimpse";
|
|
9
|
+
import { watchInputFile } from "./watch.js";
|
|
8
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
11
|
const DEFAULT_PORT = 3000;
|
|
10
12
|
function makeLog(verbose) {
|
|
@@ -12,6 +14,28 @@ function makeLog(verbose) {
|
|
|
12
14
|
return (_msg) => { };
|
|
13
15
|
return (msg) => process.stderr.write(`[pom] ${msg}\n`);
|
|
14
16
|
}
|
|
17
|
+
function openBrowser(url) {
|
|
18
|
+
let cmd;
|
|
19
|
+
let args;
|
|
20
|
+
if (process.platform === "darwin") {
|
|
21
|
+
cmd = "open";
|
|
22
|
+
args = [url];
|
|
23
|
+
}
|
|
24
|
+
else if (process.platform === "win32") {
|
|
25
|
+
// 空文字はウィンドウタイトル。省略すると URL がタイトル扱いされる
|
|
26
|
+
cmd = "cmd";
|
|
27
|
+
args = ["/c", "start", "", url];
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
cmd = "xdg-open";
|
|
31
|
+
args = [url];
|
|
32
|
+
}
|
|
33
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
34
|
+
child.on("error", () => {
|
|
35
|
+
console.log(`Could not open browser automatically. Open ${url} manually.`);
|
|
36
|
+
});
|
|
37
|
+
child.unref();
|
|
38
|
+
}
|
|
15
39
|
const EXTRA_FONT_MAPPING = {
|
|
16
40
|
"游ゴシック Light": "Noto Sans CJK JP",
|
|
17
41
|
"Yu Gothic Light": "Noto Sans CJK JP",
|
|
@@ -371,7 +395,15 @@ export function runPreview(inputFile, port = DEFAULT_PORT, options = {}) {
|
|
|
371
395
|
client.write(`event: update\ndata: ${data}\n\n`);
|
|
372
396
|
}
|
|
373
397
|
}
|
|
398
|
+
let isBuilding = false;
|
|
399
|
+
let pendingRefresh = false;
|
|
374
400
|
function refresh() {
|
|
401
|
+
if (isBuilding) {
|
|
402
|
+
pendingRefresh = true;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
isBuilding = true;
|
|
406
|
+
pendingRefresh = false;
|
|
375
407
|
const totalStart = Date.now();
|
|
376
408
|
log("File changed, rebuilding...");
|
|
377
409
|
broadcastBuilding();
|
|
@@ -385,29 +417,34 @@ export function runPreview(inputFile, port = DEFAULT_PORT, options = {}) {
|
|
|
385
417
|
type: "error",
|
|
386
418
|
message: err instanceof Error ? err.message : String(err),
|
|
387
419
|
});
|
|
420
|
+
})
|
|
421
|
+
.finally(() => {
|
|
422
|
+
isBuilding = false;
|
|
423
|
+
if (pendingRefresh)
|
|
424
|
+
refresh();
|
|
388
425
|
});
|
|
389
426
|
}
|
|
427
|
+
isBuilding = true;
|
|
390
428
|
const initialStart = Date.now();
|
|
391
429
|
generateSvgs(absInput, verbose)
|
|
392
430
|
.then((result) => {
|
|
393
431
|
currentResult = result;
|
|
394
|
-
initialBuildDone = true;
|
|
395
432
|
log(`Initial build done (total: ${Date.now() - initialStart}ms)`);
|
|
396
433
|
broadcast(result);
|
|
397
434
|
})
|
|
398
435
|
.catch((err) => {
|
|
399
|
-
initialBuildDone = true;
|
|
400
436
|
broadcast({
|
|
401
437
|
type: "error",
|
|
402
438
|
message: err instanceof Error ? err.message : String(err),
|
|
403
439
|
});
|
|
440
|
+
})
|
|
441
|
+
.finally(() => {
|
|
442
|
+
initialBuildDone = true;
|
|
443
|
+
isBuilding = false;
|
|
444
|
+
if (pendingRefresh)
|
|
445
|
+
refresh();
|
|
404
446
|
});
|
|
405
|
-
|
|
406
|
-
fs.watch(absInput, () => {
|
|
407
|
-
if (debounceTimer)
|
|
408
|
-
clearTimeout(debounceTimer);
|
|
409
|
-
debounceTimer = setTimeout(refresh, 100);
|
|
410
|
-
});
|
|
447
|
+
watchInputFile(absInput, refresh);
|
|
411
448
|
const html = buildPreviewHtml(path.basename(absInput));
|
|
412
449
|
const server = http.createServer((req, res) => {
|
|
413
450
|
if (req.url === "/_sse") {
|
|
@@ -446,8 +483,12 @@ export function runPreview(inputFile, port = DEFAULT_PORT, options = {}) {
|
|
|
446
483
|
process.exit(1);
|
|
447
484
|
});
|
|
448
485
|
server.listen(port, () => {
|
|
449
|
-
|
|
486
|
+
const url = `http://localhost:${port}`;
|
|
487
|
+
console.log(`Preview server: ${url}`);
|
|
450
488
|
console.log(`Watching: ${absInput}`);
|
|
451
489
|
console.log("Press Ctrl+C to stop");
|
|
490
|
+
if (options.open ?? true) {
|
|
491
|
+
openBrowser(url);
|
|
492
|
+
}
|
|
452
493
|
});
|
|
453
494
|
}
|
package/dist/watch.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAOpB,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,IAAI,GACnB,EAAE,CAAC,SAAS,CA4Bd"}
|
package/dist/watch.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
const DEBOUNCE_MS = 100;
|
|
4
|
+
// Watches the parent directory instead of the file itself so the watcher
|
|
5
|
+
// survives atomic saves (rename + replace) performed by editors like Vim.
|
|
6
|
+
export function watchInputFile(absPath, onChange) {
|
|
7
|
+
const dir = path.dirname(absPath);
|
|
8
|
+
const base = path.basename(absPath);
|
|
9
|
+
let debounceTimer = null;
|
|
10
|
+
let lastMtimeMs = -1;
|
|
11
|
+
try {
|
|
12
|
+
lastMtimeMs = fs.statSync(absPath).mtimeMs;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// 監視開始時点でファイルが無くても、作成され次第のイベントで拾う
|
|
16
|
+
}
|
|
17
|
+
return fs.watch(dir, (_eventType, filename) => {
|
|
18
|
+
// filename can be null on some platforms; treat it as a potential match
|
|
19
|
+
if (filename !== null && filename !== base)
|
|
20
|
+
return;
|
|
21
|
+
// 同一ディレクトリの別ファイル変更 (filename が null の環境) や
|
|
22
|
+
// 自前の出力ファイル書き込みで発火しないよう、mtime の実変化を確認する
|
|
23
|
+
let mtimeMs;
|
|
24
|
+
try {
|
|
25
|
+
mtimeMs = fs.statSync(absPath).mtimeMs;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (mtimeMs === lastMtimeMs)
|
|
31
|
+
return;
|
|
32
|
+
lastMtimeMs = mtimeMs;
|
|
33
|
+
if (debounceTimer)
|
|
34
|
+
clearTimeout(debounceTimer);
|
|
35
|
+
debounceTimer = setTimeout(onChange, DEBOUNCE_MS);
|
|
36
|
+
});
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirokisakabe/pom-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI tool for pom — preview and build presentations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
"node": ">=18"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@hirokisakabe/pom": "^8.
|
|
33
|
+
"@hirokisakabe/pom": "^8.3.0",
|
|
34
34
|
"@hirokisakabe/pom-md": "^3.0.0",
|
|
35
35
|
"commander": "^15.0.0",
|
|
36
|
-
"pptx-glimpse": "^0.
|
|
36
|
+
"pptx-glimpse": "^1.0.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/node": "^25.9.
|
|
40
|
-
"typescript-eslint": "^8.
|
|
39
|
+
"@types/node": "^25.9.2",
|
|
40
|
+
"typescript-eslint": "^8.60.1"
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc && node -e \"require('fs').chmodSync('dist/cli.js', '755')\"",
|