promaster 1.2.0 → 1.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 +7 -6
- package/package.json +1 -1
- package/src/commands/list.js +46 -17
- package/src/download.js +18 -1
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Interactive CLI to browse Markdown from a **public GitHub repo**, grouped into
|
|
4
4
|
three categories: **blog**, **memory**, **books**. Each selected file is
|
|
5
|
-
rendered to a styled, standalone **HTML** page
|
|
5
|
+
rendered to a styled, standalone **HTML** page and opened in your browser —
|
|
6
|
+
nothing is saved to disk.
|
|
6
7
|
|
|
7
8
|
The package ships only the CLI. Your Markdown content lives in your own GitHub
|
|
8
9
|
repo and is fetched at runtime — it is never bundled into npm.
|
|
@@ -33,12 +34,12 @@ promaster list
|
|
|
33
34
|
1. Pick a category: `blog` / `memory` / `books`.
|
|
34
35
|
2. Files are listed newest → oldest (by last git commit date).
|
|
35
36
|
3. Toggle one or more with space, confirm with enter.
|
|
36
|
-
4. Each selection is
|
|
37
|
-
|
|
38
|
-
5.
|
|
37
|
+
4. Each selection is rendered to HTML in a temporary directory and opened in
|
|
38
|
+
your default browser right away.
|
|
39
|
+
5. Press Enter in the CLI when you're done — the temporary files are deleted.
|
|
40
|
+
Nothing is ever written to your project directory.
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
(styles are inlined, with light/dark support).
|
|
42
|
+
The pages render fully offline (styles are inlined, with light/dark support).
|
|
42
43
|
|
|
43
44
|
## Configuration
|
|
44
45
|
|
package/package.json
CHANGED
package/src/commands/list.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { select, checkbox,
|
|
2
|
-
import {
|
|
1
|
+
import { select, checkbox, input } from "@inquirer/prompts";
|
|
2
|
+
import { rmSync } from "node:fs";
|
|
3
|
+
import { rm } from "node:fs/promises";
|
|
3
4
|
import { resolveRepo, CATEGORIES } from "../config.js";
|
|
4
5
|
import { listMarkdown, lastCommitDate, fetchRaw, HttpError } from "../github.js";
|
|
5
|
-
import {
|
|
6
|
+
import { saveTemp } from "../download.js";
|
|
6
7
|
import { openInBrowser } from "../open.js";
|
|
7
8
|
|
|
8
9
|
const CONCURRENCY = 5;
|
|
@@ -54,22 +55,50 @@ export async function runList() {
|
|
|
54
55
|
return;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
saved.push(await save(category, file, content));
|
|
61
|
-
}
|
|
58
|
+
// All categories open immediately, never persist — temp files deleted on exit.
|
|
59
|
+
await openEphemeral(picked, ctx);
|
|
60
|
+
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
// Render picks to OS temp, auto-open in the browser, then delete once the
|
|
63
|
+
// user is done (Enter) or aborts (Ctrl+C). Nothing is left on disk.
|
|
64
|
+
async function openEphemeral(picked, ctx) {
|
|
65
|
+
const dirs = [];
|
|
66
|
+
const cleanup = () => {
|
|
67
|
+
while (dirs.length) {
|
|
68
|
+
try {
|
|
69
|
+
rmSync(dirs.pop(), { recursive: true, force: true });
|
|
70
|
+
} catch {
|
|
71
|
+
/* best effort */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
66
75
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
const onSigint = () => {
|
|
77
|
+
cleanup();
|
|
78
|
+
process.exit(130);
|
|
79
|
+
};
|
|
80
|
+
process.on("SIGINT", onSigint);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
for (const file of picked) {
|
|
84
|
+
const content = await fetchRaw(file.download_url, ctx);
|
|
85
|
+
const { path, dir } = await saveTemp(file, content);
|
|
86
|
+
dirs.push(dir);
|
|
87
|
+
openInBrowser(path);
|
|
88
|
+
console.log(`Opened in browser: ${file.name}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log("\nThese are temporary — nothing is saved to disk.");
|
|
92
|
+
await input({
|
|
93
|
+
message: `Press Enter when you're done to delete the temporary file${
|
|
94
|
+
picked.length === 1 ? "" : "s"
|
|
95
|
+
}`,
|
|
96
|
+
});
|
|
97
|
+
} finally {
|
|
98
|
+
process.off("SIGINT", onSigint);
|
|
99
|
+
for (const dir of dirs.splice(0)) {
|
|
100
|
+
await rm(dir, { recursive: true, force: true }).catch(() => {});
|
|
101
|
+
}
|
|
73
102
|
}
|
|
74
103
|
}
|
|
75
104
|
|
package/src/download.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, extname, resolve } from "node:path";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
3
4
|
import { mdToHtml } from "./render.js";
|
|
4
5
|
|
|
5
6
|
export const OUTPUT_ROOT = "promaster-data";
|
|
@@ -40,3 +41,19 @@ export async function save(category, file, content) {
|
|
|
40
41
|
await writeFile(dest, html, "utf8");
|
|
41
42
|
return dest;
|
|
42
43
|
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Render markdown to HTML in a fresh OS temp directory (never under
|
|
47
|
+
* promaster-data). Returns { path, dir } so the caller can delete `dir`
|
|
48
|
+
* once the user is done viewing — nothing is persisted.
|
|
49
|
+
*/
|
|
50
|
+
export async function saveTemp(file, content) {
|
|
51
|
+
const dir = await mkdtemp(resolve(tmpdir(), "promaster-"));
|
|
52
|
+
const base = safeName(file.name);
|
|
53
|
+
const ext = extname(base);
|
|
54
|
+
const title = ext ? base.slice(0, -ext.length) : base;
|
|
55
|
+
const html = mdToHtml(content, { title });
|
|
56
|
+
const path = resolve(dir, htmlName(file.name));
|
|
57
|
+
await writeFile(path, html, "utf8");
|
|
58
|
+
return { path, dir };
|
|
59
|
+
}
|