readme-assert 6.0.3 → 7.1.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/package.json +31 -40
- package/readme.md +90 -75
- package/src/cli.js +106 -0
- package/src/comment-to-assert.js +138 -0
- package/src/extract.js +66 -0
- package/src/generate.js +96 -0
- package/src/index.js +4 -0
- package/src/run.js +320 -0
- package/cli.js +0 -9
- package/lib/cli.js +0 -62
- package/lib/extract.js +0 -74
- package/lib/global-assert.js +0 -144
- package/lib/index.js +0 -111
- package/lib/runInThisContext.js +0 -45
- package/license +0 -21
package/src/generate.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assemble extracted code blocks into runnable JS modules.
|
|
3
|
+
*
|
|
4
|
+
* Each block becomes its own module unless blocks share a group name,
|
|
5
|
+
* in which case they are merged into a single module.
|
|
6
|
+
*
|
|
7
|
+
* @param {{ blocks: Block[], hasTypescript: boolean }} extracted
|
|
8
|
+
* @returns {{ units: Array<{ code: string, name: string, hasTypescript: boolean }> }}
|
|
9
|
+
*/
|
|
10
|
+
export function generate({ blocks }) {
|
|
11
|
+
if (blocks.length === 0) return { units: [] };
|
|
12
|
+
|
|
13
|
+
// Group blocks: blocks with a group name are merged, others are standalone
|
|
14
|
+
const groups = new Map();
|
|
15
|
+
const units = [];
|
|
16
|
+
|
|
17
|
+
for (const block of blocks) {
|
|
18
|
+
if (block.group) {
|
|
19
|
+
if (!groups.has(block.group)) {
|
|
20
|
+
const entry = { blocks: [], name: block.group };
|
|
21
|
+
groups.set(block.group, entry);
|
|
22
|
+
units.push(entry);
|
|
23
|
+
}
|
|
24
|
+
groups.get(block.group).blocks.push(block);
|
|
25
|
+
} else {
|
|
26
|
+
units.push({ blocks: [block], name: block.tag || `line ${block.startLine}` });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
units: units.map((unit) => ({
|
|
32
|
+
code: assembleUnit(unit.blocks),
|
|
33
|
+
name: unit.name,
|
|
34
|
+
hasTypescript: unit.blocks.some((b) => b.lang === "typescript" || b.lang === "ts"),
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assembleUnit(blocks) {
|
|
40
|
+
// Find the last line number we need to cover
|
|
41
|
+
const maxLine = Math.max(...blocks.map((b) => b.endLine));
|
|
42
|
+
|
|
43
|
+
// Build a line array filled with empty strings
|
|
44
|
+
const lines = new Array(maxLine).fill("");
|
|
45
|
+
|
|
46
|
+
// Place each block's code at its source position
|
|
47
|
+
for (const block of blocks) {
|
|
48
|
+
const codeLines = block.code.replace(/\n$/, "").split("\n");
|
|
49
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
50
|
+
lines[block.startLine - 1 + i] = codeLines[i];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Separate import/export lines from body lines
|
|
55
|
+
const imports = [];
|
|
56
|
+
const bodyLines = [];
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const trimmed = lines[i].trimStart();
|
|
60
|
+
if (isImportOrExport(trimmed)) {
|
|
61
|
+
imports.push(lines[i]);
|
|
62
|
+
bodyLines.push(""); // keep line padding
|
|
63
|
+
} else {
|
|
64
|
+
bodyLines.push(lines[i]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const hasESM = imports.length > 0;
|
|
69
|
+
const hasCJS = /\brequire\s*\(/.test(bodyLines.join("\n"));
|
|
70
|
+
|
|
71
|
+
// Place assert import on line 0 (before markdown line 1) so line numbers
|
|
72
|
+
// in the generated code match the original markdown positions exactly.
|
|
73
|
+
let assertLine;
|
|
74
|
+
if (hasESM) {
|
|
75
|
+
assertLine = 'import assert from "node:assert/strict";';
|
|
76
|
+
} else if (hasCJS) {
|
|
77
|
+
assertLine = 'const assert = require("node:assert/strict");';
|
|
78
|
+
} else {
|
|
79
|
+
assertLine = 'const { default: assert } = await import("node:assert/strict");';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// imports go on line 0 too (they're already removed from bodyLines).
|
|
83
|
+
// Each piece is its own statement, so a single space between them is
|
|
84
|
+
// enough; joining with "; " produced a double semicolon like ";; ".
|
|
85
|
+
const header = [assertLine, ...imports].join(" ");
|
|
86
|
+
bodyLines[0] = header;
|
|
87
|
+
return bodyLines.join("\n") + "\n";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isImportOrExport(line) {
|
|
91
|
+
return (
|
|
92
|
+
/^import\s/.test(line) ||
|
|
93
|
+
/^import\(/.test(line) ||
|
|
94
|
+
/^export\s/.test(line)
|
|
95
|
+
);
|
|
96
|
+
}
|
package/src/index.js
ADDED
package/src/run.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { extractBlocks } from "./extract.js";
|
|
6
|
+
import { generate } from "./generate.js";
|
|
7
|
+
import { commentToAssert } from "./comment-to-assert.js";
|
|
8
|
+
|
|
9
|
+
const tmpFiles = new Set();
|
|
10
|
+
|
|
11
|
+
function cleanupTmpFiles() {
|
|
12
|
+
for (const f of tmpFiles) {
|
|
13
|
+
try { fs.unlinkSync(f); } catch {}
|
|
14
|
+
}
|
|
15
|
+
tmpFiles.clear();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
process.on("exit", cleanupTmpFiles);
|
|
19
|
+
|
|
20
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
21
|
+
process.once(signal, () => {
|
|
22
|
+
cleanupTmpFiles();
|
|
23
|
+
process.kill(process.pid, signal);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Process a markdown file into executable code units.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} filePath
|
|
31
|
+
* @param {{ auto?: boolean, all?: boolean, main?: string }} options
|
|
32
|
+
* @returns {Promise<Array<{ code: string, name: string }>>}
|
|
33
|
+
*/
|
|
34
|
+
export async function processMarkdown(filePath, options = {}) {
|
|
35
|
+
const markdown = fs.readFileSync(filePath, "utf-8");
|
|
36
|
+
const extracted = extractBlocks(markdown, {
|
|
37
|
+
auto: options.auto,
|
|
38
|
+
all: options.all,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (extracted.blocks.length === 0) {
|
|
42
|
+
const err = new Error(`No test code blocks found in ${filePath}`);
|
|
43
|
+
err.code = "NO_TEST_BLOCKS";
|
|
44
|
+
throw err;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { units } = generate(extracted);
|
|
48
|
+
|
|
49
|
+
// Resolve package info for import renaming
|
|
50
|
+
let packageName, localPath;
|
|
51
|
+
const pkgPath = findPackageJson(path.dirname(filePath));
|
|
52
|
+
if (pkgPath) {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
54
|
+
if (pkg.name) {
|
|
55
|
+
const mainEntry = options.main || resolveMainEntry(pkg) || "./index.js";
|
|
56
|
+
packageName = pkg.name;
|
|
57
|
+
localPath = path.resolve(path.dirname(pkgPath), mainEntry);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const unit of units) {
|
|
63
|
+
let code = unit.code;
|
|
64
|
+
|
|
65
|
+
if (packageName) {
|
|
66
|
+
code = renameImports(code, packageName, localPath);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const transformed = commentToAssert(code, {
|
|
70
|
+
typescript: unit.hasTypescript,
|
|
71
|
+
});
|
|
72
|
+
code = transformed.code;
|
|
73
|
+
|
|
74
|
+
if (unit.hasTypescript) {
|
|
75
|
+
const esbuild = await import("esbuild");
|
|
76
|
+
const result = await esbuild.transform(code, {
|
|
77
|
+
loader: "ts",
|
|
78
|
+
sourcemap: false,
|
|
79
|
+
});
|
|
80
|
+
code = result.code;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
results.push({ code, name: unit.name });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Run a markdown file as a test.
|
|
91
|
+
*
|
|
92
|
+
* Each code block (or group) is written to a temp file and executed
|
|
93
|
+
* sequentially. Stops on first failure.
|
|
94
|
+
*
|
|
95
|
+
* When `options.stream` is true, each child's stdout chunk is written
|
|
96
|
+
* to `process.stdout` as it arrives so long-running blocks don't look
|
|
97
|
+
* stalled. Captured stdout is still returned in the result for
|
|
98
|
+
* programmatic callers.
|
|
99
|
+
*
|
|
100
|
+
* @param {string} filePath
|
|
101
|
+
* @param {{ auto?: boolean, all?: boolean, main?: string, stream?: boolean }} options
|
|
102
|
+
* @returns {Promise<{ exitCode: number, stdout: string, stderr: string, results: Array }>}
|
|
103
|
+
*/
|
|
104
|
+
export async function run(filePath, options = {}) {
|
|
105
|
+
const units = await processMarkdown(filePath, options);
|
|
106
|
+
const dir = path.dirname(filePath);
|
|
107
|
+
let allStdout = "";
|
|
108
|
+
let allStderr = "";
|
|
109
|
+
const results = [];
|
|
110
|
+
|
|
111
|
+
const useRequire = options.require?.length > 0;
|
|
112
|
+
const stream = options.stream ?? false;
|
|
113
|
+
|
|
114
|
+
for (const unit of units) {
|
|
115
|
+
let code = unit.code;
|
|
116
|
+
|
|
117
|
+
// --require hooks only work with CJS, so downgrade dynamic import to require
|
|
118
|
+
if (useRequire && code.includes("await import(")) {
|
|
119
|
+
code = code.replace(
|
|
120
|
+
'const { default: assert } = await import("node:assert/strict");',
|
|
121
|
+
'const assert = require("node:assert/strict");',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const isESM = /^import\s/m.test(code) || /^export\s/m.test(code) || code.includes("await import(");
|
|
126
|
+
const ext = isESM ? ".mjs" : ".cjs";
|
|
127
|
+
const tmpFile = path.join(dir, `.readme-assert-${randomUUID().slice(0, 8)}${ext}`);
|
|
128
|
+
tmpFiles.add(tmpFile);
|
|
129
|
+
fs.writeFileSync(tmpFile, code);
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const nodeArgs = [];
|
|
133
|
+
for (const r of options.require || []) nodeArgs.push("--require", r);
|
|
134
|
+
for (const i of options.import || []) nodeArgs.push("--import", i);
|
|
135
|
+
nodeArgs.push(tmpFile);
|
|
136
|
+
const result = await exec("node", nodeArgs, dir, filePath, stream);
|
|
137
|
+
allStdout += result.stdout;
|
|
138
|
+
allStderr += result.stderr;
|
|
139
|
+
results.push({ name: unit.name, ...result });
|
|
140
|
+
|
|
141
|
+
if (result.exitCode !== 0) {
|
|
142
|
+
return { exitCode: result.exitCode, stdout: allStdout, stderr: allStderr, results };
|
|
143
|
+
}
|
|
144
|
+
} finally {
|
|
145
|
+
try { fs.unlinkSync(tmpFile); } catch {}
|
|
146
|
+
tmpFiles.delete(tmpFile);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { exitCode: 0, stdout: allStdout, stderr: allStderr, results };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function exec(cmd, args, cwd, mdPath, stream) {
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
const child = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
156
|
+
let stdout = "";
|
|
157
|
+
let stderr = "";
|
|
158
|
+
|
|
159
|
+
child.stdout.on("data", (chunk) => {
|
|
160
|
+
if (stream) process.stdout.write(chunk);
|
|
161
|
+
stdout += chunk;
|
|
162
|
+
});
|
|
163
|
+
child.stderr.on("data", (d) => (stderr += d));
|
|
164
|
+
|
|
165
|
+
child.on("close", (exitCode) => {
|
|
166
|
+
// Rewrite temp file paths to the markdown file path
|
|
167
|
+
const tmpFile = args[args.length - 1];
|
|
168
|
+
stderr = stderr.replaceAll(tmpFile, mdPath);
|
|
169
|
+
stdout = stdout.replaceAll(tmpFile, mdPath);
|
|
170
|
+
|
|
171
|
+
if (exitCode !== 0) {
|
|
172
|
+
stderr = formatError(stderr, mdPath);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
resolve({ exitCode, stdout, stderr });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function formatError(stderr, mdPath) {
|
|
181
|
+
// Extract location from stack trace
|
|
182
|
+
const locMatch = stderr.match(new RegExp(`${escapeRegExp(mdPath)}:(\\d+):(\\d+)`));
|
|
183
|
+
const line = locMatch ? parseInt(locMatch[1]) : null;
|
|
184
|
+
|
|
185
|
+
// Extract actual/expected from the error object dump
|
|
186
|
+
const actualMatch = stderr.match(/actual: (.+)/);
|
|
187
|
+
const expectedMatch = stderr.match(/expected: (.+)/);
|
|
188
|
+
const operatorMatch = stderr.match(/operator: '(.+)'/);
|
|
189
|
+
|
|
190
|
+
// Extract the error message line
|
|
191
|
+
const msgMatch = stderr.match(/AssertionError.*?:\s*(.+)/);
|
|
192
|
+
// Also catch non-assertion errors (ReferenceError, TypeError, etc.)
|
|
193
|
+
const genericMatch = !msgMatch && stderr.match(/(\w*Error.*)/);
|
|
194
|
+
|
|
195
|
+
const parts = [];
|
|
196
|
+
|
|
197
|
+
// Location header
|
|
198
|
+
const relPath = path.relative(process.cwd(), mdPath);
|
|
199
|
+
if (line) {
|
|
200
|
+
parts.push(`\n FAIL ${relPath}:${line}\n`);
|
|
201
|
+
} else {
|
|
202
|
+
parts.push(`\n FAIL ${relPath}\n`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Source context from the markdown
|
|
206
|
+
if (line) {
|
|
207
|
+
try {
|
|
208
|
+
const mdLines = fs.readFileSync(mdPath, "utf-8").split("\n");
|
|
209
|
+
const start = Math.max(0, line - 3);
|
|
210
|
+
const end = Math.min(mdLines.length, line + 2);
|
|
211
|
+
for (let i = start; i < end; i++) {
|
|
212
|
+
const lineNum = String(i + 1).padStart(4);
|
|
213
|
+
const marker = i + 1 === line ? " > " : " ";
|
|
214
|
+
parts.push(`${marker}${lineNum} | ${mdLines[i]}`);
|
|
215
|
+
}
|
|
216
|
+
parts.push("");
|
|
217
|
+
} catch {
|
|
218
|
+
// ignore read errors
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Actual vs expected
|
|
223
|
+
if (actualMatch && expectedMatch) {
|
|
224
|
+
parts.push(` expected: ${expectedMatch[1].replace(/,\s*$/, "")}`);
|
|
225
|
+
parts.push(` received: ${actualMatch[1].replace(/,\s*$/, "")}`);
|
|
226
|
+
parts.push("");
|
|
227
|
+
} else if (msgMatch) {
|
|
228
|
+
parts.push(` ${msgMatch[0]}`);
|
|
229
|
+
parts.push("");
|
|
230
|
+
} else if (genericMatch) {
|
|
231
|
+
parts.push(` ${genericMatch[1]}`);
|
|
232
|
+
parts.push("");
|
|
233
|
+
} else {
|
|
234
|
+
// Fallback: strip Node internals and return cleaned stderr
|
|
235
|
+
parts.push(
|
|
236
|
+
stderr
|
|
237
|
+
.split("\n")
|
|
238
|
+
.filter((l) => !l.match(/^\s*(at [a-z].*\(node:|node:internal|Node\.js v|triggerUncaught|\^$)/i))
|
|
239
|
+
.join("\n")
|
|
240
|
+
.trim(),
|
|
241
|
+
);
|
|
242
|
+
parts.push("");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return parts.join("\n");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function escapeRegExp(s) {
|
|
249
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renameImports(code, packageName, localPath) {
|
|
253
|
+
const escaped = packageName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
254
|
+
return code
|
|
255
|
+
.replace(
|
|
256
|
+
new RegExp(`(from\\s+['"])${escaped}(['"])`, "g"),
|
|
257
|
+
`$1${localPath}$2`,
|
|
258
|
+
)
|
|
259
|
+
.replace(
|
|
260
|
+
new RegExp(`(from\\s+['"])${escaped}/`, "g"),
|
|
261
|
+
`$1${path.dirname(localPath)}/`,
|
|
262
|
+
)
|
|
263
|
+
.replace(
|
|
264
|
+
new RegExp(`(require\\s*\\(\\s*['"])${escaped}(['"]\\s*\\))`, "g"),
|
|
265
|
+
`$1${localPath}$2`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function findPackageJson(dir) {
|
|
270
|
+
let current = path.resolve(dir);
|
|
271
|
+
while (true) {
|
|
272
|
+
const candidate = path.join(current, "package.json");
|
|
273
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
274
|
+
const parent = path.dirname(current);
|
|
275
|
+
if (parent === current) return null;
|
|
276
|
+
current = parent;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Resolve a package's main entry point from its package.json.
|
|
282
|
+
*
|
|
283
|
+
* Handles `main`, plus the various shapes of `exports`:
|
|
284
|
+
* - string: "exports": "./lib/main.js"
|
|
285
|
+
* - subpath map: "exports": { ".": "./lib/main.js" }
|
|
286
|
+
* - conditional: "exports": { "import": "./esm.js", "require": "./cjs.js" }
|
|
287
|
+
* - nested: "exports": { ".": { "import": "./esm.js" } }
|
|
288
|
+
*
|
|
289
|
+
* Returns null if no entry can be determined.
|
|
290
|
+
*/
|
|
291
|
+
export function resolveMainEntry(pkg) {
|
|
292
|
+
if (pkg.main) return pkg.main;
|
|
293
|
+
|
|
294
|
+
const exp = pkg.exports;
|
|
295
|
+
if (!exp) return null;
|
|
296
|
+
if (typeof exp === "string") return exp;
|
|
297
|
+
if (typeof exp !== "object") return null;
|
|
298
|
+
|
|
299
|
+
// If any key starts with ".", this is a subpath map and the root export
|
|
300
|
+
// lives at "."; otherwise the object itself is the conditional map.
|
|
301
|
+
const isSubpathMap = Object.keys(exp).some((k) => k.startsWith("."));
|
|
302
|
+
const root = isSubpathMap ? exp["."] : exp;
|
|
303
|
+
|
|
304
|
+
return resolveExportCondition(root);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function resolveExportCondition(node) {
|
|
308
|
+
if (node == null) return null;
|
|
309
|
+
if (typeof node === "string") return node;
|
|
310
|
+
if (typeof node !== "object") return null;
|
|
311
|
+
|
|
312
|
+
// Prefer import > default > require
|
|
313
|
+
for (const key of ["import", "default", "require"]) {
|
|
314
|
+
if (key in node) {
|
|
315
|
+
const resolved = resolveExportCondition(node[key]);
|
|
316
|
+
if (resolved) return resolved;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
package/cli.js
DELETED
package/lib/cli.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var _yargs = _interopRequireDefault(require("yargs"));
|
|
4
|
-
|
|
5
|
-
var _ = _interopRequireDefault(require("."));
|
|
6
|
-
|
|
7
|
-
var _package = require("../package.json");
|
|
8
|
-
|
|
9
|
-
var _fs = _interopRequireDefault(require("fs"));
|
|
10
|
-
|
|
11
|
-
var _path = _interopRequireDefault(require("path"));
|
|
12
|
-
|
|
13
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
14
|
-
|
|
15
|
-
var argv = _yargs["default"].usage("\nRun readme as test\n\nUsage: $0 [options]").option("auto", {
|
|
16
|
-
alias: "a",
|
|
17
|
-
description: "Auto discover test code block",
|
|
18
|
-
type: "boolean"
|
|
19
|
-
}).option("all", {
|
|
20
|
-
alias: "l",
|
|
21
|
-
description: "Run all supported code blocks",
|
|
22
|
-
type: "boolean"
|
|
23
|
-
}).option("babel", {
|
|
24
|
-
description: "Use babelrc when transpiling",
|
|
25
|
-
"default": false,
|
|
26
|
-
type: "boolean"
|
|
27
|
-
}).option("file", {
|
|
28
|
-
alias: "f",
|
|
29
|
-
description: "readme.md file to read"
|
|
30
|
-
}).option("main", {
|
|
31
|
-
alias: "m",
|
|
32
|
-
description: "Points to the entry point of the module",
|
|
33
|
-
type: "string"
|
|
34
|
-
}).option("print-code", {
|
|
35
|
-
alias: "p",
|
|
36
|
-
description: "Print the transformed code",
|
|
37
|
-
type: "boolean"
|
|
38
|
-
}).option("require", {
|
|
39
|
-
alias: "r",
|
|
40
|
-
description: "Require a given module",
|
|
41
|
-
type: "array"
|
|
42
|
-
}).alias("h", "help").version(_package.version).help().argv;
|
|
43
|
-
|
|
44
|
-
function resolve(file) {
|
|
45
|
-
try {
|
|
46
|
-
_fs["default"].statSync(_path["default"].join(process.cwd(), file));
|
|
47
|
-
|
|
48
|
-
return _path["default"].resolve(file);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
var filename = argv.file ? _path["default"].resolve(argv.file) : resolve("README.md") || resolve("readme.md");
|
|
55
|
-
|
|
56
|
-
if (filename == null) {
|
|
57
|
-
console.log(_fs["default"].statSync(_path["default"].join(process.cwd(), "README.md")));
|
|
58
|
-
console.error("could not locate readme.md");
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
(0, _["default"])(argv.main, argv.require || [], argv["print-code"], argv.babel, argv.file ? _path["default"].resolve(argv.file) : resolve("README.md") || resolve("readme.md"), argv.auto, argv.all);
|
package/lib/extract.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports["default"] = extractCode;
|
|
7
|
-
|
|
8
|
-
var _gfmCodeBlocks = _interopRequireDefault(require("gfm-code-blocks"));
|
|
9
|
-
|
|
10
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
|
|
11
|
-
|
|
12
|
-
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
|
|
13
|
-
|
|
14
|
-
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
|
|
15
|
-
|
|
16
|
-
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
|
|
17
|
-
|
|
18
|
-
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
|
|
19
|
-
|
|
20
|
-
function isSupportedLang(block) {
|
|
21
|
-
var lang = block.lang.split(" ")[0];
|
|
22
|
-
return lang === "javascript" || lang === "js" || lang === "typescript" || lang === "ts";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isTypescript(block) {
|
|
26
|
-
var lang = block.lang.split(" ")[0];
|
|
27
|
-
return lang === "typescript" || lang === "ts";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function isTest(block) {
|
|
31
|
-
var tag = block.lang.split(" ")[1];
|
|
32
|
-
return tag === "test" || tag === "should";
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
var arrowRegex = /\/(\/|\*)\s?(=>|→|throws)/;
|
|
36
|
-
|
|
37
|
-
function isAutomaticTest(block) {
|
|
38
|
-
return block.code.match(arrowRegex);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function extractCode(markdown) {
|
|
42
|
-
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
|
|
43
|
-
_ref$auto = _ref.auto,
|
|
44
|
-
auto = _ref$auto === void 0 ? false : _ref$auto,
|
|
45
|
-
_ref$all = _ref.all,
|
|
46
|
-
all = _ref$all === void 0 ? false : _ref$all;
|
|
47
|
-
|
|
48
|
-
var hasTypescript = false;
|
|
49
|
-
var code = new Array(markdown.length).fill(" ");
|
|
50
|
-
var newline = /\n/gm;
|
|
51
|
-
var result;
|
|
52
|
-
|
|
53
|
-
while (result = newline.exec(markdown)) {
|
|
54
|
-
code[result.index] = "\n";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
var blocks = (0, _gfmCodeBlocks["default"])(markdown).filter(all ? function () {
|
|
58
|
-
return true;
|
|
59
|
-
} : auto ? isAutomaticTest : isTest).filter(isSupportedLang);
|
|
60
|
-
|
|
61
|
-
if (blocks.length === 0) {
|
|
62
|
-
console.error("\nREADME has no test code blocks\n");
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
blocks.forEach(function (block) {
|
|
67
|
-
hasTypescript = hasTypescript || isTypescript(block);
|
|
68
|
-
code.splice.apply(code, [block.start, block.end - block.start].concat(_toConsumableArray(block.code.padStart(block.end - block.start, " ").split(""))));
|
|
69
|
-
});
|
|
70
|
-
return {
|
|
71
|
-
code: code.join(""),
|
|
72
|
-
hasTypescript: hasTypescript
|
|
73
|
-
};
|
|
74
|
-
}
|