harnery 0.2.0 → 0.2.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/dist/commands/outline.d.ts.map +1 -1
- package/dist/commands/outline.js +24 -9
- package/dist/commands/read.js +4 -4
- package/dist/lib/readability/client.d.ts +8 -1
- package/dist/lib/readability/client.d.ts.map +1 -1
- package/dist/lib/readability/client.js +7 -5
- package/package.json +1 -1
- package/src/commands/outline.ts +43 -24
- package/src/commands/read.ts +4 -4
- package/src/lib/readability/client.ts +18 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../src/commands/outline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"outline.d.ts","sourceRoot":"","sources":["../../src/commands/outline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAqDnD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAwBhF"}
|
package/dist/commands/outline.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, resolve } from "node:path";
|
|
3
|
-
import * as ts from "typescript";
|
|
4
3
|
import { resolveBinName } from "../core/config.js";
|
|
4
|
+
/**
|
|
5
|
+
* `typescript` is a devDependency, not a runtime dependency, so it is loaded
|
|
6
|
+
* lazily here and only when outlining a TS/JS file. A static top-level import
|
|
7
|
+
* would make the whole CLI fail to boot for an end user who installed harnery
|
|
8
|
+
* without dev deps (every command is registered at startup). PHP/Python
|
|
9
|
+
* outlines use regex and never touch this.
|
|
10
|
+
*/
|
|
11
|
+
async function loadTypeScript() {
|
|
12
|
+
try {
|
|
13
|
+
return (await import("typescript"));
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("outlining TS/JS files needs the `typescript` package; install it (npm i -D typescript) or use outline on PHP/Python files");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
5
19
|
export function registerOutlineCommand(program, emit) {
|
|
6
20
|
program
|
|
7
21
|
.command("outline <file>")
|
|
@@ -37,7 +51,7 @@ async function runOutline(file, opts) {
|
|
|
37
51
|
let parsed;
|
|
38
52
|
if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"].includes(ext)) {
|
|
39
53
|
language = ext.slice(1);
|
|
40
|
-
parsed = outlineTypeScript(content, absPath, ext);
|
|
54
|
+
parsed = await outlineTypeScript(content, absPath, ext);
|
|
41
55
|
}
|
|
42
56
|
else if (ext === ".php") {
|
|
43
57
|
language = "php";
|
|
@@ -64,7 +78,8 @@ async function runOutline(file, opts) {
|
|
|
64
78
|
symbols: parsed.symbols,
|
|
65
79
|
};
|
|
66
80
|
}
|
|
67
|
-
function outlineTypeScript(content, path, ext) {
|
|
81
|
+
async function outlineTypeScript(content, path, ext) {
|
|
82
|
+
const ts = await loadTypeScript();
|
|
68
83
|
const isTsx = ext === ".tsx" || ext === ".jsx";
|
|
69
84
|
const scriptKind = isTsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
70
85
|
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
@@ -101,7 +116,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
101
116
|
symbols.push({
|
|
102
117
|
kind: "function",
|
|
103
118
|
name: stmt.name.text,
|
|
104
|
-
signature: renderFnSig(stmt, content, source),
|
|
119
|
+
signature: renderFnSig(ts, stmt, content, source),
|
|
105
120
|
line: lineOf(stmt),
|
|
106
121
|
exported,
|
|
107
122
|
});
|
|
@@ -117,7 +132,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
117
132
|
members.push({
|
|
118
133
|
kind: "method",
|
|
119
134
|
name: member.name.text || text(member.name),
|
|
120
|
-
signature: renderFnSig(member, content, source),
|
|
135
|
+
signature: renderFnSig(ts, member, content, source),
|
|
121
136
|
line: lineOf(member),
|
|
122
137
|
});
|
|
123
138
|
}
|
|
@@ -125,7 +140,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
125
140
|
members.push({
|
|
126
141
|
kind: "method",
|
|
127
142
|
name: "constructor",
|
|
128
|
-
signature: renderFnSig(member, content, source),
|
|
143
|
+
signature: renderFnSig(ts, member, content, source),
|
|
129
144
|
line: lineOf(member),
|
|
130
145
|
});
|
|
131
146
|
}
|
|
@@ -165,7 +180,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
165
180
|
members.push({
|
|
166
181
|
kind: "method",
|
|
167
182
|
name: member.name.text || text(member.name),
|
|
168
|
-
signature: renderFnSig(member, content, source),
|
|
183
|
+
signature: renderFnSig(ts, member, content, source),
|
|
169
184
|
line: lineOf(member),
|
|
170
185
|
});
|
|
171
186
|
}
|
|
@@ -216,7 +231,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
216
231
|
sig = `: ${text(decl.type)}`;
|
|
217
232
|
else if (decl.initializer &&
|
|
218
233
|
(ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
219
|
-
sig = renderFnSig(decl.initializer, content, source);
|
|
234
|
+
sig = renderFnSig(ts, decl.initializer, content, source);
|
|
220
235
|
}
|
|
221
236
|
symbols.push({
|
|
222
237
|
kind,
|
|
@@ -260,7 +275,7 @@ function outlineTypeScript(content, path, ext) {
|
|
|
260
275
|
}
|
|
261
276
|
return { imports, symbols };
|
|
262
277
|
}
|
|
263
|
-
function renderFnSig(fn, content, source) {
|
|
278
|
+
function renderFnSig(ts, fn, content, source) {
|
|
264
279
|
const params = (fn.parameters || [])
|
|
265
280
|
.map((p) => {
|
|
266
281
|
const name = ts.isIdentifier(p.name)
|
package/dist/commands/read.js
CHANGED
|
@@ -17,9 +17,9 @@ export function registerReadCommand(program, emitParam) {
|
|
|
17
17
|
.option("--selector <css>", "Use this CSS selector instead of Readability (fallback when extraction misses content)")
|
|
18
18
|
.option("--raw", "Output cleaned HTML instead of markdown (debugging)")
|
|
19
19
|
.option("--max-chars <n>", "Truncate output to N characters (0 = disable)", "100000")
|
|
20
|
-
.action((htmlFile, opts) => {
|
|
20
|
+
.action(async (htmlFile, opts) => {
|
|
21
21
|
try {
|
|
22
|
-
runRead(htmlFile, opts);
|
|
22
|
+
await runRead(htmlFile, opts);
|
|
23
23
|
}
|
|
24
24
|
catch (err) {
|
|
25
25
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -28,9 +28,9 @@ export function registerReadCommand(program, emitParam) {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
-
function runRead(htmlFile, opts) {
|
|
31
|
+
async function runRead(htmlFile, opts) {
|
|
32
32
|
const input = htmlFile && htmlFile !== "-" ? readFileSync(htmlFile, "utf-8") : readFileSync(0, "utf-8");
|
|
33
|
-
const result = htmlToMarkdown(input, {
|
|
33
|
+
const result = await htmlToMarkdown(input, {
|
|
34
34
|
url: opts.url,
|
|
35
35
|
selector: opts.selector,
|
|
36
36
|
raw: opts.raw,
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Turndown (HTML → markdown).
|
|
4
4
|
*
|
|
5
5
|
* The library backing the `read` command.
|
|
6
|
+
*
|
|
7
|
+
* jsdom, @mozilla/readability, and turndown are heavy and (jsdom's tree)
|
|
8
|
+
* ESM-fragile, so they are loaded lazily inside `htmlToMarkdown` rather than
|
|
9
|
+
* at module top level. A static import would pull them into CLI startup (every
|
|
10
|
+
* command is registered eagerly), and jsdom's transitive `html-encoding-sniffer`
|
|
11
|
+
* crashes a plain `node` process via ERR_REQUIRE_ESM. Lazy-loading keeps the
|
|
12
|
+
* CLI booting for users who never touch `read`/`browse`.
|
|
6
13
|
*/
|
|
7
14
|
export interface ReadabilityOptions {
|
|
8
15
|
/** Base URL for resolving relative links. Default `http://local/`. */
|
|
@@ -28,5 +35,5 @@ export interface ReadabilityResult {
|
|
|
28
35
|
* Throws if input is empty, the selector matches nothing, or Readability
|
|
29
36
|
* fails to find a main article (caller should retry with `selector`).
|
|
30
37
|
*/
|
|
31
|
-
export declare function htmlToMarkdown(html: string, opts?: ReadabilityOptions): ReadabilityResult
|
|
38
|
+
export declare function htmlToMarkdown(html: string, opts?: ReadabilityOptions): Promise<ReadabilityResult>;
|
|
32
39
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/readability/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/readability/client.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,iBAAiB,CAAC,CA8D5B"}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/// <reference path="./turndown-plugin-gfm.d.ts" />
|
|
2
|
-
import { Readability } from "@mozilla/readability";
|
|
3
|
-
import { JSDOM } from "jsdom";
|
|
4
|
-
import TurndownService from "turndown";
|
|
5
|
-
import { tables } from "turndown-plugin-gfm";
|
|
6
2
|
const DEFAULT_MAX_CHARS = 100_000;
|
|
7
3
|
/**
|
|
8
4
|
* Convert raw HTML to clean markdown.
|
|
@@ -10,10 +6,16 @@ const DEFAULT_MAX_CHARS = 100_000;
|
|
|
10
6
|
* Throws if input is empty, the selector matches nothing, or Readability
|
|
11
7
|
* fails to find a main article (caller should retry with `selector`).
|
|
12
8
|
*/
|
|
13
|
-
export function htmlToMarkdown(html, opts = {}) {
|
|
9
|
+
export async function htmlToMarkdown(html, opts = {}) {
|
|
14
10
|
if (!html.trim()) {
|
|
15
11
|
throw new Error("Empty HTML input");
|
|
16
12
|
}
|
|
13
|
+
const [{ Readability }, { JSDOM }, { default: TurndownService }, { tables }] = await Promise.all([
|
|
14
|
+
import("@mozilla/readability"),
|
|
15
|
+
import("jsdom"),
|
|
16
|
+
import("turndown"),
|
|
17
|
+
import("turndown-plugin-gfm"),
|
|
18
|
+
]);
|
|
17
19
|
const dom = new JSDOM(html, { url: opts.url ?? "http://local/" });
|
|
18
20
|
const doc = dom.window.document;
|
|
19
21
|
preprocessDom(doc);
|
package/package.json
CHANGED
package/src/commands/outline.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, resolve } from "node:path";
|
|
3
3
|
import type { Command } from "commander";
|
|
4
|
-
import * as
|
|
4
|
+
import type * as TS from "typescript";
|
|
5
5
|
import type { EmitContext } from "../commander.ts";
|
|
6
6
|
import { resolveBinName } from "../core/config.ts";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* `typescript` is a devDependency, not a runtime dependency, so it is loaded
|
|
10
|
+
* lazily here and only when outlining a TS/JS file. A static top-level import
|
|
11
|
+
* would make the whole CLI fail to boot for an end user who installed harnery
|
|
12
|
+
* without dev deps (every command is registered at startup). PHP/Python
|
|
13
|
+
* outlines use regex and never touch this.
|
|
14
|
+
*/
|
|
15
|
+
async function loadTypeScript(): Promise<typeof TS> {
|
|
16
|
+
try {
|
|
17
|
+
return (await import("typescript")) as unknown as typeof TS;
|
|
18
|
+
} catch {
|
|
19
|
+
throw new Error(
|
|
20
|
+
"outlining TS/JS files needs the `typescript` package; install it (npm i -D typescript) or use outline on PHP/Python files",
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
/**
|
|
9
26
|
* `harn outline <file>`: print the structural skeleton of a code file (imports
|
|
10
27
|
* + top-level decls + line numbers). TS/JS/TSX/JSX use the TypeScript compiler
|
|
@@ -77,7 +94,7 @@ async function runOutline(file: string, opts: OutlineOpts): Promise<OutlineResul
|
|
|
77
94
|
|
|
78
95
|
if ([".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs"].includes(ext)) {
|
|
79
96
|
language = ext.slice(1);
|
|
80
|
-
parsed = outlineTypeScript(content, absPath, ext);
|
|
97
|
+
parsed = await outlineTypeScript(content, absPath, ext);
|
|
81
98
|
} else if (ext === ".php") {
|
|
82
99
|
language = "php";
|
|
83
100
|
parsed = outlinePhp(content);
|
|
@@ -105,11 +122,12 @@ async function runOutline(file: string, opts: OutlineOpts): Promise<OutlineResul
|
|
|
105
122
|
};
|
|
106
123
|
}
|
|
107
124
|
|
|
108
|
-
function outlineTypeScript(
|
|
125
|
+
async function outlineTypeScript(
|
|
109
126
|
content: string,
|
|
110
127
|
path: string,
|
|
111
128
|
ext: string,
|
|
112
|
-
): { imports: string[]; symbols: SymbolEntry[] } {
|
|
129
|
+
): Promise<{ imports: string[]; symbols: SymbolEntry[] }> {
|
|
130
|
+
const ts = await loadTypeScript();
|
|
113
131
|
const isTsx = ext === ".tsx" || ext === ".jsx";
|
|
114
132
|
const scriptKind = isTsx ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
115
133
|
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
@@ -117,18 +135,18 @@ function outlineTypeScript(
|
|
|
117
135
|
const imports: string[] = [];
|
|
118
136
|
const symbols: SymbolEntry[] = [];
|
|
119
137
|
|
|
120
|
-
const lineOf = (node:
|
|
138
|
+
const lineOf = (node: TS.Node) =>
|
|
121
139
|
source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
|
|
122
|
-
const text = (node:
|
|
140
|
+
const text = (node: TS.Node) =>
|
|
123
141
|
content.slice(node.getStart(source), node.getEnd()).replace(/\s+/g, " ").trim();
|
|
124
|
-
const isExported = (node:
|
|
125
|
-
const mods = (node as { modifiers?: ReadonlyArray<
|
|
142
|
+
const isExported = (node: TS.Node): boolean => {
|
|
143
|
+
const mods = (node as { modifiers?: ReadonlyArray<TS.ModifierLike> }).modifiers;
|
|
126
144
|
return mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
127
145
|
};
|
|
128
146
|
|
|
129
147
|
for (const stmt of source.statements) {
|
|
130
148
|
if (ts.isImportDeclaration(stmt)) {
|
|
131
|
-
const moduleSpec = (stmt.moduleSpecifier as
|
|
149
|
+
const moduleSpec = (stmt.moduleSpecifier as TS.StringLiteral).text;
|
|
132
150
|
const clause = stmt.importClause;
|
|
133
151
|
const isTypeOnly = clause?.isTypeOnly ? "type " : "";
|
|
134
152
|
let what = "";
|
|
@@ -151,7 +169,7 @@ function outlineTypeScript(
|
|
|
151
169
|
symbols.push({
|
|
152
170
|
kind: "function",
|
|
153
171
|
name: stmt.name.text,
|
|
154
|
-
signature: renderFnSig(stmt, content, source),
|
|
172
|
+
signature: renderFnSig(ts, stmt, content, source),
|
|
155
173
|
line: lineOf(stmt),
|
|
156
174
|
exported,
|
|
157
175
|
});
|
|
@@ -168,21 +186,21 @@ function outlineTypeScript(
|
|
|
168
186
|
if (ts.isMethodDeclaration(member) && member.name) {
|
|
169
187
|
members.push({
|
|
170
188
|
kind: "method",
|
|
171
|
-
name: (member.name as
|
|
172
|
-
signature: renderFnSig(member, content, source),
|
|
189
|
+
name: (member.name as TS.Identifier).text || text(member.name),
|
|
190
|
+
signature: renderFnSig(ts, member, content, source),
|
|
173
191
|
line: lineOf(member),
|
|
174
192
|
});
|
|
175
193
|
} else if (ts.isConstructorDeclaration(member)) {
|
|
176
194
|
members.push({
|
|
177
195
|
kind: "method",
|
|
178
196
|
name: "constructor",
|
|
179
|
-
signature: renderFnSig(member, content, source),
|
|
197
|
+
signature: renderFnSig(ts, member, content, source),
|
|
180
198
|
line: lineOf(member),
|
|
181
199
|
});
|
|
182
200
|
} else if (ts.isPropertyDeclaration(member) && member.name) {
|
|
183
201
|
members.push({
|
|
184
202
|
kind: "property",
|
|
185
|
-
name: (member.name as
|
|
203
|
+
name: (member.name as TS.Identifier).text || text(member.name),
|
|
186
204
|
signature: member.type ? `: ${text(member.type)}` : "",
|
|
187
205
|
line: lineOf(member),
|
|
188
206
|
});
|
|
@@ -205,15 +223,15 @@ function outlineTypeScript(
|
|
|
205
223
|
if (ts.isPropertySignature(member) && member.name) {
|
|
206
224
|
members.push({
|
|
207
225
|
kind: "property",
|
|
208
|
-
name: (member.name as
|
|
226
|
+
name: (member.name as TS.Identifier).text || text(member.name),
|
|
209
227
|
signature: member.type ? `: ${text(member.type)}` : "",
|
|
210
228
|
line: lineOf(member),
|
|
211
229
|
});
|
|
212
230
|
} else if (ts.isMethodSignature(member) && member.name) {
|
|
213
231
|
members.push({
|
|
214
232
|
kind: "method",
|
|
215
|
-
name: (member.name as
|
|
216
|
-
signature: renderFnSig(member, content, source),
|
|
233
|
+
name: (member.name as TS.Identifier).text || text(member.name),
|
|
234
|
+
signature: renderFnSig(ts, member, content, source),
|
|
217
235
|
line: lineOf(member),
|
|
218
236
|
});
|
|
219
237
|
}
|
|
@@ -243,7 +261,7 @@ function outlineTypeScript(
|
|
|
243
261
|
exported,
|
|
244
262
|
members: stmt.members.map((m) => ({
|
|
245
263
|
kind: "enumMember",
|
|
246
|
-
name: (m.name as
|
|
264
|
+
name: (m.name as TS.Identifier).text || text(m.name),
|
|
247
265
|
line: lineOf(m),
|
|
248
266
|
})),
|
|
249
267
|
});
|
|
@@ -261,7 +279,7 @@ function outlineTypeScript(
|
|
|
261
279
|
decl.initializer &&
|
|
262
280
|
(ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))
|
|
263
281
|
) {
|
|
264
|
-
sig = renderFnSig(decl.initializer, content, source);
|
|
282
|
+
sig = renderFnSig(ts, decl.initializer, content, source);
|
|
265
283
|
}
|
|
266
284
|
symbols.push({
|
|
267
285
|
kind,
|
|
@@ -275,7 +293,7 @@ function outlineTypeScript(
|
|
|
275
293
|
if (stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
|
|
276
294
|
const names = stmt.exportClause.elements.map((e) => e.name.text).join(", ");
|
|
277
295
|
const fromText = stmt.moduleSpecifier
|
|
278
|
-
? ` from "${(stmt.moduleSpecifier as
|
|
296
|
+
? ` from "${(stmt.moduleSpecifier as TS.StringLiteral).text}"`
|
|
279
297
|
: "";
|
|
280
298
|
symbols.push({
|
|
281
299
|
kind: "re-export",
|
|
@@ -286,7 +304,7 @@ function outlineTypeScript(
|
|
|
286
304
|
} else if (stmt.moduleSpecifier) {
|
|
287
305
|
symbols.push({
|
|
288
306
|
kind: "re-export",
|
|
289
|
-
name: `* from "${(stmt.moduleSpecifier as
|
|
307
|
+
name: `* from "${(stmt.moduleSpecifier as TS.StringLiteral).text}"`,
|
|
290
308
|
line: lineOf(stmt),
|
|
291
309
|
exported: true,
|
|
292
310
|
});
|
|
@@ -294,7 +312,7 @@ function outlineTypeScript(
|
|
|
294
312
|
} else if (ts.isModuleDeclaration(stmt) && stmt.name) {
|
|
295
313
|
symbols.push({
|
|
296
314
|
kind: "namespace",
|
|
297
|
-
name: (stmt.name as
|
|
315
|
+
name: (stmt.name as TS.Identifier).text || text(stmt.name),
|
|
298
316
|
line: lineOf(stmt),
|
|
299
317
|
exported,
|
|
300
318
|
});
|
|
@@ -305,9 +323,10 @@ function outlineTypeScript(
|
|
|
305
323
|
}
|
|
306
324
|
|
|
307
325
|
function renderFnSig(
|
|
308
|
-
|
|
326
|
+
ts: typeof TS,
|
|
327
|
+
fn: TS.FunctionLikeDeclarationBase | TS.MethodSignature,
|
|
309
328
|
content: string,
|
|
310
|
-
source:
|
|
329
|
+
source: TS.SourceFile,
|
|
311
330
|
): string {
|
|
312
331
|
const params = (fn.parameters || [])
|
|
313
332
|
.map((p) => {
|
package/src/commands/read.ts
CHANGED
|
@@ -26,9 +26,9 @@ export function registerReadCommand(program: Command, emitParam: EmitContext): v
|
|
|
26
26
|
)
|
|
27
27
|
.option("--raw", "Output cleaned HTML instead of markdown (debugging)")
|
|
28
28
|
.option("--max-chars <n>", "Truncate output to N characters (0 = disable)", "100000")
|
|
29
|
-
.action((htmlFile: string | undefined, opts: ReadOpts) => {
|
|
29
|
+
.action(async (htmlFile: string | undefined, opts: ReadOpts) => {
|
|
30
30
|
try {
|
|
31
|
-
runRead(htmlFile, opts);
|
|
31
|
+
await runRead(htmlFile, opts);
|
|
32
32
|
} catch (err: unknown) {
|
|
33
33
|
const msg = err instanceof Error ? err.message : String(err);
|
|
34
34
|
emit.error({ code: "read_error", message: msg });
|
|
@@ -45,10 +45,10 @@ interface ReadOpts {
|
|
|
45
45
|
maxChars: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function runRead(htmlFile: string | undefined, opts: ReadOpts): void {
|
|
48
|
+
async function runRead(htmlFile: string | undefined, opts: ReadOpts): Promise<void> {
|
|
49
49
|
const input =
|
|
50
50
|
htmlFile && htmlFile !== "-" ? readFileSync(htmlFile, "utf-8") : readFileSync(0, "utf-8");
|
|
51
|
-
const result = htmlToMarkdown(input, {
|
|
51
|
+
const result = await htmlToMarkdown(input, {
|
|
52
52
|
url: opts.url,
|
|
53
53
|
selector: opts.selector,
|
|
54
54
|
raw: opts.raw,
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/// <reference path="./turndown-plugin-gfm.d.ts" />
|
|
2
|
-
import { Readability } from "@mozilla/readability";
|
|
3
|
-
import { JSDOM } from "jsdom";
|
|
4
|
-
import TurndownService from "turndown";
|
|
5
|
-
import { tables } from "turndown-plugin-gfm";
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
4
|
* HTML → clean markdown via Readability (main-content extraction) +
|
|
9
5
|
* Turndown (HTML → markdown).
|
|
10
6
|
*
|
|
11
7
|
* The library backing the `read` command.
|
|
8
|
+
*
|
|
9
|
+
* jsdom, @mozilla/readability, and turndown are heavy and (jsdom's tree)
|
|
10
|
+
* ESM-fragile, so they are loaded lazily inside `htmlToMarkdown` rather than
|
|
11
|
+
* at module top level. A static import would pull them into CLI startup (every
|
|
12
|
+
* command is registered eagerly), and jsdom's transitive `html-encoding-sniffer`
|
|
13
|
+
* crashes a plain `node` process via ERR_REQUIRE_ESM. Lazy-loading keeps the
|
|
14
|
+
* CLI booting for users who never touch `read`/`browse`.
|
|
12
15
|
*/
|
|
13
16
|
|
|
14
17
|
export interface ReadabilityOptions {
|
|
@@ -39,11 +42,21 @@ const DEFAULT_MAX_CHARS = 100_000;
|
|
|
39
42
|
* Throws if input is empty, the selector matches nothing, or Readability
|
|
40
43
|
* fails to find a main article (caller should retry with `selector`).
|
|
41
44
|
*/
|
|
42
|
-
export function htmlToMarkdown(
|
|
45
|
+
export async function htmlToMarkdown(
|
|
46
|
+
html: string,
|
|
47
|
+
opts: ReadabilityOptions = {},
|
|
48
|
+
): Promise<ReadabilityResult> {
|
|
43
49
|
if (!html.trim()) {
|
|
44
50
|
throw new Error("Empty HTML input");
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
const [{ Readability }, { JSDOM }, { default: TurndownService }, { tables }] = await Promise.all([
|
|
54
|
+
import("@mozilla/readability"),
|
|
55
|
+
import("jsdom"),
|
|
56
|
+
import("turndown"),
|
|
57
|
+
import("turndown-plugin-gfm"),
|
|
58
|
+
]);
|
|
59
|
+
|
|
47
60
|
const dom = new JSDOM(html, { url: opts.url ?? "http://local/" });
|
|
48
61
|
const doc = dom.window.document;
|
|
49
62
|
preprocessDom(doc);
|