@hirokisakabe/pom-cli 0.2.5 → 0.3.1

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
@@ -21,8 +21,20 @@ pom preview slides.pom.md
21
21
 
22
22
  Open http://localhost:3000 in your browser. The page updates automatically when the file is saved.
23
23
 
24
+ To use a different port (e.g. when 3000 is already in use):
25
+
26
+ ```bash
27
+ pom preview slides.pom.xml --port 3001
28
+ ```
29
+
24
30
  Use the zoom buttons in the toolbar or press `+` / `-` to zoom in and out. The current zoom level is saved across sessions.
25
31
 
32
+ To print per-step timing on stderr when each rebuild completes:
33
+
34
+ ```bash
35
+ pom preview slides.pom.xml --verbose
36
+ ```
37
+
26
38
  ### Build
27
39
 
28
40
  Converts a pom file to a PPTX file.
@@ -32,6 +44,29 @@ pom build slides.pom.xml -o output.pptx
32
44
  pom build slides.pom.md -o output.pptx
33
45
  ```
34
46
 
47
+ To watch for file changes and rebuild automatically:
48
+
49
+ ```bash
50
+ pom build slides.pom.xml -o output.pptx --watch
51
+ ```
52
+
53
+ The process stays running and rebuilds every time the input file is saved. Build progress is printed to stderr:
54
+
55
+ ```
56
+ [pom] Watching: slides.pom.xml
57
+ [pom] Built: output.pptx (367ms)
58
+ [pom] File changed, rebuilding...
59
+ [pom] Built: output.pptx (342ms)
60
+ ```
61
+
62
+ If a build fails, the error is printed but the process continues watching for the next change. Press `Ctrl+C` to stop.
63
+
64
+ To print per-step timing on stderr:
65
+
66
+ ```bash
67
+ pom build slides.pom.xml -o output.pptx --verbose
68
+ ```
69
+
35
70
  ## Fonts
36
71
 
37
72
  This package bundles Carlito and Noto Sans CJK JP fonts for SVG rendering. These fonts are used when converting slides to SVG in the preview server. System fonts are not scanned.
package/dist/build.d.ts CHANGED
@@ -1,2 +1,8 @@
1
- export declare function runBuild(inputFile: string, outputFile: string): Promise<void>;
1
+ export declare function runBuild(inputFile: string, outputFile: string, options?: {
2
+ verbose?: boolean;
3
+ silent?: boolean;
4
+ }): Promise<void>;
5
+ export declare function runBuildWatch(inputFile: string, outputFile: string, options?: {
6
+ verbose?: boolean;
7
+ }): Promise<void>;
2
8
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAKA,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAsDf"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAUA,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,CA2Df"}
package/dist/build.js CHANGED
@@ -1,13 +1,22 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { buildPptx } from "@hirokisakabe/pom";
3
+ import { buildPptx, DiagnosticsError } from "@hirokisakabe/pom";
4
4
  import { parseMd } from "@hirokisakabe/pom-md";
5
- export async function runBuild(inputFile, outputFile) {
5
+ function makeLog(verbose) {
6
+ if (!verbose)
7
+ return (_msg) => { };
8
+ return (msg) => process.stderr.write(`[pom] ${msg}\n`);
9
+ }
10
+ export async function runBuild(inputFile, outputFile, options = {}) {
11
+ const verbose = options.verbose ?? false;
12
+ const log = makeLog(verbose);
13
+ const totalStart = Date.now();
6
14
  const absInput = path.resolve(inputFile);
7
15
  const absOutput = path.resolve(outputFile);
8
16
  if (!fs.existsSync(absInput)) {
9
17
  throw new Error(`Input file not found: ${absInput}`);
10
18
  }
19
+ log(`Reading file: ${absInput}`);
11
20
  const content = fs.readFileSync(absInput, "utf-8");
12
21
  const ext = path.extname(absInput);
13
22
  let xml;
@@ -15,10 +24,12 @@ export async function runBuild(inputFile, outputFile) {
15
24
  let slideHeight = 720;
16
25
  let masterPptxData;
17
26
  if (ext === ".md") {
27
+ const t = Date.now();
18
28
  const result = parseMd(content);
19
29
  xml = result.xml;
20
30
  slideWidth = result.meta.size.w;
21
31
  slideHeight = result.meta.size.h;
32
+ log(`Parsing Markdown... done (${Date.now() - t}ms)`);
22
33
  if (result.meta.masterPptx) {
23
34
  const masterPath = path.resolve(path.dirname(absInput), result.meta.masterPptx);
24
35
  try {
@@ -37,11 +48,75 @@ export async function runBuild(inputFile, outputFile) {
37
48
  else {
38
49
  xml = content;
39
50
  }
40
- const { pptx } = await buildPptx(xml, { w: slideWidth, h: slideHeight }, masterPptxData ? { masterPptx: masterPptxData } : undefined);
51
+ const t1 = Date.now();
52
+ const { pptx } = await buildPptx(xml, { w: slideWidth, h: slideHeight }, {
53
+ ...(masterPptxData ? { masterPptx: masterPptxData } : {}),
54
+ strict: true,
55
+ });
56
+ log(`Building PPTX... done (${Date.now() - t1}ms)`);
57
+ const t2 = Date.now();
41
58
  const buffer = await pptx.write({ outputType: "uint8array" });
42
59
  if (!(buffer instanceof Uint8Array)) {
43
60
  throw new Error("Unexpected output type from pptx.write");
44
61
  }
45
62
  fs.writeFileSync(absOutput, buffer);
46
- console.log(`PPTX saved: ${absOutput}`);
63
+ log(`Writing output... done (${Date.now() - t2}ms)`);
64
+ const total = Date.now() - totalStart;
65
+ log(`Total: ${total}ms`);
66
+ if (!options.silent) {
67
+ console.log(`PPTX saved: ${absOutput}`);
68
+ }
69
+ }
70
+ export async function runBuildWatch(inputFile, outputFile, options = {}) {
71
+ const absInput = path.resolve(inputFile);
72
+ const absOutput = path.resolve(outputFile);
73
+ if (!fs.existsSync(absInput)) {
74
+ throw new Error(`Input file not found: ${absInput}`);
75
+ }
76
+ const watchLog = (msg) => process.stderr.write(`[pom] ${msg}\n`);
77
+ watchLog(`Watching: ${path.basename(absInput)}`);
78
+ let isBuilding = false;
79
+ let pendingRebuild = false;
80
+ async function doBuild() {
81
+ if (isBuilding) {
82
+ pendingRebuild = true;
83
+ return;
84
+ }
85
+ isBuilding = true;
86
+ pendingRebuild = false;
87
+ const start = Date.now();
88
+ try {
89
+ await runBuild(inputFile, outputFile, { ...options, silent: true });
90
+ watchLog(`Built: ${path.basename(absOutput)} (${Date.now() - start}ms)`);
91
+ }
92
+ catch (err) {
93
+ if (err instanceof DiagnosticsError) {
94
+ const count = err.diagnostics.length;
95
+ process.stderr.write(`[pom] Build failed (${count} ${count === 1 ? "error" : "errors"})\n`);
96
+ for (const d of err.diagnostics) {
97
+ process.stderr.write(`[pom] [${d.code}] ${d.message}\n`);
98
+ }
99
+ }
100
+ else {
101
+ process.stderr.write(`[pom] Error: ${err instanceof Error ? err.message : String(err)}\n`);
102
+ }
103
+ }
104
+ finally {
105
+ isBuilding = false;
106
+ if (pendingRebuild) {
107
+ watchLog("File changed, rebuilding...");
108
+ void doBuild();
109
+ }
110
+ }
111
+ }
112
+ await doBuild();
113
+ let debounceTimer = null;
114
+ fs.watch(absInput, () => {
115
+ if (debounceTimer)
116
+ clearTimeout(debounceTimer);
117
+ debounceTimer = setTimeout(() => {
118
+ watchLog("File changed, rebuilding...");
119
+ void doBuild();
120
+ }, 100);
121
+ });
47
122
  }
package/dist/cli.js CHANGED
@@ -1,38 +1,67 @@
1
1
  #!/usr/bin/env node
2
- import { runBuild } from "./build.js";
2
+ import { createRequire } from "node:module";
3
+ import { Command } from "commander";
4
+ import { DiagnosticsError } from "@hirokisakabe/pom";
5
+ import { runBuild, runBuildWatch } from "./build.js";
3
6
  import { runPreview } from "./preview.js";
4
- const args = process.argv.slice(2);
5
- const command = args[0];
6
- if (command === "preview") {
7
- const inputFile = args[1];
8
- if (!inputFile) {
9
- console.error("Usage: pom preview <input.pom.xml|input.pom.md>");
10
- process.exit(1);
7
+ const require = createRequire(import.meta.url);
8
+ const { version } = require("../package.json");
9
+ const program = new Command();
10
+ program
11
+ .name("pom")
12
+ .description("CLI tool for pom preview and build presentations")
13
+ .version(version);
14
+ program
15
+ .command("preview")
16
+ .description("Start a live preview server for a presentation")
17
+ .argument("<input>", "Input file (.pom.xml or .pom.md)")
18
+ .option("--port <number>", "Port to listen on")
19
+ .option("--verbose", "Show build step timing on stderr")
20
+ .action((input, options) => {
21
+ let port;
22
+ if (options.port !== undefined) {
23
+ port = Number(options.port);
24
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
25
+ console.error(`Invalid port: ${options.port}`);
26
+ process.exit(1);
27
+ }
11
28
  }
12
29
  try {
13
- runPreview(inputFile);
30
+ runPreview(input, port, { verbose: options.verbose });
14
31
  }
15
32
  catch (err) {
16
33
  console.error(err instanceof Error ? err.message : String(err));
17
34
  process.exit(1);
18
35
  }
19
- }
20
- else if (command === "build") {
21
- const inputFile = args[1];
22
- const outputIndex = args.indexOf("-o");
23
- const outputFile = outputIndex !== -1 ? args[outputIndex + 1] : undefined;
24
- if (!inputFile || !outputFile) {
25
- console.error("Usage: pom build <input.pom.xml|input.pom.md> -o <output.pptx>");
26
- process.exit(1);
36
+ });
37
+ program
38
+ .command("build")
39
+ .description("Build a presentation to PPTX")
40
+ .argument("<input>", "Input file (.pom.xml or .pom.md)")
41
+ .requiredOption("-o <output>", "Output PPTX file")
42
+ .option("--verbose", "Show build step timing on stderr")
43
+ .option("--watch", "Watch for file changes and rebuild automatically")
44
+ .action((input, options) => {
45
+ if (options.watch) {
46
+ runBuildWatch(input, options.o, { verbose: options.verbose }).catch((err) => {
47
+ console.error(err instanceof Error ? err.message : String(err));
48
+ process.exit(1);
49
+ });
27
50
  }
28
- runBuild(inputFile, outputFile).catch((err) => {
29
- console.error(err instanceof Error ? err.message : String(err));
30
- process.exit(1);
31
- });
32
- }
33
- else {
34
- console.error("Usage:");
35
- console.error(" pom preview <input.pom.xml|input.pom.md>");
36
- console.error(" pom build <input.pom.xml|input.pom.md> -o <output.pptx>");
37
- process.exit(1);
38
- }
51
+ else {
52
+ runBuild(input, options.o, { verbose: options.verbose }).catch((err) => {
53
+ if (err instanceof DiagnosticsError) {
54
+ const count = err.diagnostics.length;
55
+ console.error(`✗ Build failed (${count} ${count === 1 ? "error" : "errors"})\n`);
56
+ for (const d of err.diagnostics) {
57
+ console.error(` [${d.code}] ${d.message}`);
58
+ }
59
+ }
60
+ else {
61
+ console.error(err instanceof Error ? err.message : String(err));
62
+ }
63
+ process.exit(1);
64
+ });
65
+ }
66
+ });
67
+ program.parse();
package/dist/preview.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export declare function runPreview(inputFile: string): void;
1
+ export declare function runPreview(inputFile: string, port?: number, options?: {
2
+ verbose?: boolean;
3
+ }): void;
2
4
  //# sourceMappingURL=preview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AA8WA,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAyGlD"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AA8XA,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,MAAqB,EAC3B,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC,IAAI,CAkHN"}
package/dist/preview.js CHANGED
@@ -7,11 +7,17 @@ import { parseMd } from "@hirokisakabe/pom-md";
7
7
  import { convertPptxToSvg } from "pptx-glimpse";
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  const DEFAULT_PORT = 3000;
10
+ function makeLog(verbose) {
11
+ if (!verbose)
12
+ return (_msg) => { };
13
+ return (msg) => process.stderr.write(`[pom] ${msg}\n`);
14
+ }
10
15
  const EXTRA_FONT_MAPPING = {
11
16
  "游ゴシック Light": "Noto Sans CJK JP",
12
17
  "Yu Gothic Light": "Noto Sans CJK JP",
13
18
  };
14
- async function generateSvgs(inputFile) {
19
+ async function generateSvgs(inputFile, verbose = false) {
20
+ const log = makeLog(verbose);
15
21
  const content = fs.readFileSync(inputFile, "utf-8");
16
22
  const ext = path.extname(inputFile);
17
23
  let xml;
@@ -19,10 +25,12 @@ async function generateSvgs(inputFile) {
19
25
  let slideHeight = 720;
20
26
  let masterPptxData;
21
27
  if (ext === ".md") {
28
+ const t = Date.now();
22
29
  const result = parseMd(content);
23
30
  xml = result.xml;
24
31
  slideWidth = result.meta.size.w;
25
32
  slideHeight = result.meta.size.h;
33
+ log(`Parsing Markdown... done (${Date.now() - t}ms)`);
26
34
  if (result.meta.masterPptx) {
27
35
  const masterPath = path.resolve(path.dirname(inputFile), result.meta.masterPptx);
28
36
  try {
@@ -44,10 +52,12 @@ async function generateSvgs(inputFile) {
44
52
  if (!xml.trim()) {
45
53
  return { type: "empty" };
46
54
  }
55
+ const t1 = Date.now();
47
56
  const { pptx } = await buildPptx(xml, { w: slideWidth, h: slideHeight }, {
48
57
  textMeasurement: "fallback",
49
58
  ...(masterPptxData ? { masterPptx: masterPptxData } : {}),
50
59
  });
60
+ log(`Building PPTX... done (${Date.now() - t1}ms)`);
51
61
  const buffer = await pptx.write({ outputType: "uint8array" });
52
62
  if (!(buffer instanceof Uint8Array)) {
53
63
  throw new Error("Unexpected output type from pptx.write");
@@ -57,12 +67,14 @@ async function generateSvgs(inputFile) {
57
67
  throw new Error(`Bundled fonts directory not found: ${fontsDir}. The package may be corrupted.`);
58
68
  }
59
69
  const fontDirs = [fontsDir];
70
+ const t2 = Date.now();
60
71
  const slides = await convertPptxToSvg(buffer, {
61
72
  width: slideWidth,
62
73
  fontDirs,
63
74
  fontMapping: EXTRA_FONT_MAPPING,
64
75
  skipSystemFonts: true,
65
76
  });
77
+ log(`Converting to SVG... done (${Date.now() - t2}ms)`);
66
78
  const svgs = slides.map((s) => s.svg);
67
79
  return { type: "success", svgs, slideWidth };
68
80
  }
@@ -336,7 +348,9 @@ function buildPreviewHtml(filename) {
336
348
  </body>
337
349
  </html>`;
338
350
  }
339
- export function runPreview(inputFile) {
351
+ export function runPreview(inputFile, port = DEFAULT_PORT, options = {}) {
352
+ const verbose = options.verbose ?? false;
353
+ const log = makeLog(verbose);
340
354
  const absInput = path.resolve(inputFile);
341
355
  if (!fs.existsSync(absInput)) {
342
356
  throw new Error(`Input file not found: ${absInput}`);
@@ -358,9 +372,14 @@ export function runPreview(inputFile) {
358
372
  }
359
373
  }
360
374
  function refresh() {
375
+ const totalStart = Date.now();
376
+ log("File changed, rebuilding...");
361
377
  broadcastBuilding();
362
- generateSvgs(absInput)
363
- .then(broadcast)
378
+ generateSvgs(absInput, verbose)
379
+ .then((result) => {
380
+ log(`Done (total: ${Date.now() - totalStart}ms)`);
381
+ broadcast(result);
382
+ })
364
383
  .catch((err) => {
365
384
  broadcast({
366
385
  type: "error",
@@ -368,10 +387,12 @@ export function runPreview(inputFile) {
368
387
  });
369
388
  });
370
389
  }
371
- generateSvgs(absInput)
390
+ const initialStart = Date.now();
391
+ generateSvgs(absInput, verbose)
372
392
  .then((result) => {
373
393
  currentResult = result;
374
394
  initialBuildDone = true;
395
+ log(`Initial build done (total: ${Date.now() - initialStart}ms)`);
375
396
  broadcast(result);
376
397
  })
377
398
  .catch((err) => {
@@ -417,15 +438,15 @@ export function runPreview(inputFile) {
417
438
  });
418
439
  server.on("error", (err) => {
419
440
  if (err.code === "EADDRINUSE") {
420
- console.error(`Port ${DEFAULT_PORT} is already in use. Is another pom preview running?`);
441
+ console.error(`Port ${port} is already in use. Is another pom preview running?`);
421
442
  }
422
443
  else {
423
444
  console.error(`Server error: ${err.message}`);
424
445
  }
425
446
  process.exit(1);
426
447
  });
427
- server.listen(DEFAULT_PORT, () => {
428
- console.log(`Preview server: http://localhost:${DEFAULT_PORT}`);
448
+ server.listen(port, () => {
449
+ console.log(`Preview server: http://localhost:${port}`);
429
450
  console.log(`Watching: ${absInput}`);
430
451
  console.log("Press Ctrl+C to stop");
431
452
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hirokisakabe/pom-cli",
3
- "version": "0.2.5",
3
+ "version": "0.3.1",
4
4
  "description": "CLI tool for pom — preview and build presentations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,8 @@
32
32
  "dependencies": {
33
33
  "@hirokisakabe/pom": "^8.2.0",
34
34
  "@hirokisakabe/pom-md": "^3.0.0",
35
- "pptx-glimpse": "^0.11.0"
35
+ "commander": "^15.0.0",
36
+ "pptx-glimpse": "^0.11.2"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@types/node": "^25.9.1",