@doccident/doccident 0.0.3 → 0.0.5
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/LICENSE +195 -3
- package/README.md +556 -6
- package/bin/cmd.js +12 -1
- package/dist/doctest.js +229 -20
- package/dist/languages/basic.js +51 -0
- package/dist/languages/c.js +169 -0
- package/dist/languages/cobol.js +55 -0
- package/dist/languages/csharp.js +77 -0
- package/dist/languages/fortran.js +165 -0
- package/dist/languages/go.js +159 -0
- package/dist/languages/interface.js +2 -0
- package/dist/languages/java.js +92 -0
- package/dist/languages/javascript.js +51 -0
- package/dist/languages/pascal.js +88 -0
- package/dist/languages/perl.js +36 -0
- package/dist/languages/python.js +48 -0
- package/dist/languages/r.js +37 -0
- package/dist/languages/rust.js +104 -0
- package/dist/languages/shell.js +46 -0
- package/dist/parse-code-snippets-from-markdown.js +137 -7
- package/dist/reporter.js +39 -0
- package/package.json +4 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rustHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
const rustHandler = (code, _snippet, config, sandbox, isSharedSandbox) => {
|
|
9
|
+
let success = false;
|
|
10
|
+
let stack = "";
|
|
11
|
+
const context = sandbox;
|
|
12
|
+
let rustCode = code;
|
|
13
|
+
// Handle shared state
|
|
14
|
+
if (isSharedSandbox) {
|
|
15
|
+
if (!context._rustCode) {
|
|
16
|
+
context._rustCode = "";
|
|
17
|
+
}
|
|
18
|
+
context._rustCode += code + "\n";
|
|
19
|
+
rustCode = context._rustCode;
|
|
20
|
+
}
|
|
21
|
+
let finalSource = "";
|
|
22
|
+
// Parse attributes vs body
|
|
23
|
+
// We want to lift crate-level attributes (#![...]) to the top
|
|
24
|
+
// Everything else goes inside main
|
|
25
|
+
// Check if code already has fn main() at the top level?
|
|
26
|
+
// If users provide a full program with main(), wrapping it in another main is weird but valid.
|
|
27
|
+
// However, for shared state, we generally assume "script mode".
|
|
28
|
+
// If not shared, we stick to the old logic (check for main, if not present wrap).
|
|
29
|
+
if (!isSharedSandbox) {
|
|
30
|
+
if (!rustCode.includes('fn main()')) {
|
|
31
|
+
finalSource = `fn main() {
|
|
32
|
+
${rustCode}
|
|
33
|
+
}`;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
finalSource = rustCode;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Shared state mode
|
|
41
|
+
const lines = rustCode.split('\n');
|
|
42
|
+
const attributes = [];
|
|
43
|
+
const body = [];
|
|
44
|
+
for (const line of lines) {
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
if (trimmed.startsWith('#![') || trimmed.startsWith('extern crate ')) {
|
|
47
|
+
attributes.push(line);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
body.push(line);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
finalSource = `${attributes.join('\n')}
|
|
54
|
+
|
|
55
|
+
fn main() {
|
|
56
|
+
${body.join('\n')}
|
|
57
|
+
}`;
|
|
58
|
+
}
|
|
59
|
+
const uniqueId = `${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
|
60
|
+
const tempSourceFile = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_rust_${uniqueId}.rs`);
|
|
61
|
+
const tempExeFile = (0, path_1.join)((0, os_1.tmpdir)(), `doccident_rust_${uniqueId}`);
|
|
62
|
+
const timeout = config.timeout || 30000;
|
|
63
|
+
try {
|
|
64
|
+
(0, fs_1.writeFileSync)(tempSourceFile, finalSource);
|
|
65
|
+
// Compile
|
|
66
|
+
const compileResult = (0, child_process_1.spawnSync)('rustc', [tempSourceFile, '-o', tempExeFile], { encoding: 'utf-8', timeout });
|
|
67
|
+
if (compileResult.status !== 0) {
|
|
68
|
+
stack = compileResult.stderr || "Rust compilation failed";
|
|
69
|
+
if (isSharedSandbox) {
|
|
70
|
+
stack += `\n\nGenerated Source:\n${finalSource}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Run
|
|
75
|
+
const runResult = (0, child_process_1.spawnSync)(tempExeFile, [], { encoding: 'utf-8', timeout });
|
|
76
|
+
if (runResult.error && runResult.error.code === 'ETIMEDOUT') {
|
|
77
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
78
|
+
}
|
|
79
|
+
if (runResult.status === 0) {
|
|
80
|
+
success = true;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
stack = runResult.stderr || "Rust execution failed with non-zero exit code";
|
|
84
|
+
}
|
|
85
|
+
return { success, stack, output: runResult.stdout };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
stack = e.message || "Failed to execute rustc or run binary";
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
try {
|
|
93
|
+
if ((0, fs_1.existsSync)(tempSourceFile))
|
|
94
|
+
(0, fs_1.unlinkSync)(tempSourceFile);
|
|
95
|
+
if ((0, fs_1.existsSync)(tempExeFile))
|
|
96
|
+
(0, fs_1.unlinkSync)(tempExeFile);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Ignore cleanup error
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { success, stack };
|
|
103
|
+
};
|
|
104
|
+
exports.rustHandler = rustHandler;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shellHandler = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const shellHandler = (code, snippet, config, sandbox, isSharedSandbox) => {
|
|
6
|
+
let success = false;
|
|
7
|
+
let stack = "";
|
|
8
|
+
const context = sandbox;
|
|
9
|
+
const shell = snippet.language || 'bash';
|
|
10
|
+
// If sharing code, we need to accumulate previous shell snippets
|
|
11
|
+
if (isSharedSandbox) {
|
|
12
|
+
if (!context._shellContext) {
|
|
13
|
+
context._shellContext = {};
|
|
14
|
+
}
|
|
15
|
+
if (!context._shellContext[shell]) {
|
|
16
|
+
context._shellContext[shell] = "";
|
|
17
|
+
}
|
|
18
|
+
context._shellContext[shell] += code + "\n";
|
|
19
|
+
code = context._shellContext[shell];
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
const args = snippet.args || [];
|
|
23
|
+
const env = snippet.env ? { ...process.env, ...snippet.env } : undefined;
|
|
24
|
+
// Use the detected shell
|
|
25
|
+
// shell -s arg1 arg2 reads from stdin and passes args
|
|
26
|
+
const spawnArgs = ['-s', ...args];
|
|
27
|
+
const timeout = config.timeout || 30000;
|
|
28
|
+
const result = (0, child_process_1.spawnSync)(shell, spawnArgs, { input: code, encoding: 'utf-8', env, timeout });
|
|
29
|
+
if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
30
|
+
return { success: false, stack: `Execution timed out after ${timeout}ms` };
|
|
31
|
+
}
|
|
32
|
+
if (result.status === 0) {
|
|
33
|
+
success = true;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const exitCode = result.status !== null ? result.status : 'signal';
|
|
37
|
+
stack = result.stderr || result.stdout || `${shell} execution failed with non-zero exit code: ${exitCode}`;
|
|
38
|
+
}
|
|
39
|
+
return { success, stack, output: result.stdout };
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
stack = e.message || `Failed to spawn ${shell}`;
|
|
43
|
+
return { success, stack };
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.shellHandler = shellHandler;
|
|
@@ -1,15 +1,75 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
3
|
+
// Capture indentation (group 1) and language (group 2)
|
|
4
|
+
const START_REGEX = /^(\s*)```\W*(JavaScript|js|es6|ts|typescript|python|py|bash|sh|zsh|shell|go|rust|rs|fortran|f90|f95|cobol|cob|c|basic|java|perl|pl|csharp|cs|r|pascal|pas|text|txt|output)\s?$/i;
|
|
5
|
+
const isStartOfSnippet = (line) => line.match(START_REGEX);
|
|
4
6
|
const isEndOfSnippet = (line) => line.trim() === "```";
|
|
5
7
|
const isSkip = (line) => line.trim() === "<!-- skip-example -->";
|
|
6
8
|
const isCodeSharedInFile = (line) => line.trim() === "<!-- share-code-between-examples -->";
|
|
7
|
-
|
|
9
|
+
const isId = (line) => line.match(/^<!--\s*id:\s*(\S+)\s*-->$/);
|
|
10
|
+
const isOutputOf = (line) => line.match(/^<!--\s*output:\s*(\S+)(?:\s+(match:regex|match:fuzzy|ignore-whitespace))?\s*-->$/);
|
|
11
|
+
const isArgs = (line) => line.match(/^<!--\s*args:\s*(.+)\s*-->$/);
|
|
12
|
+
const isEnv = (line) => line.match(/^<!--\s*env:\s*(.+)\s*-->$/);
|
|
13
|
+
function startNewSnippet(snippets, fileName, lineNumber, language, indentation) {
|
|
8
14
|
const skip = snippets.skip;
|
|
9
15
|
snippets.skip = false;
|
|
16
|
+
const id = snippets.id;
|
|
17
|
+
snippets.id = undefined;
|
|
18
|
+
const outputOf = snippets.outputOf;
|
|
19
|
+
snippets.outputOf = undefined;
|
|
20
|
+
const outputMode = snippets.outputMode;
|
|
21
|
+
snippets.outputMode = undefined;
|
|
22
|
+
const args = snippets.args;
|
|
23
|
+
snippets.args = undefined;
|
|
24
|
+
const env = snippets.env;
|
|
25
|
+
snippets.env = undefined;
|
|
26
|
+
let normalizedLang = 'javascript';
|
|
27
|
+
const langLower = language.toLowerCase();
|
|
28
|
+
if (['python', 'py'].includes(langLower)) {
|
|
29
|
+
normalizedLang = 'python';
|
|
30
|
+
}
|
|
31
|
+
else if (['bash', 'sh', 'zsh', 'shell'].includes(langLower)) {
|
|
32
|
+
normalizedLang = langLower === 'shell' ? 'bash' : langLower;
|
|
33
|
+
}
|
|
34
|
+
else if (langLower === 'go') {
|
|
35
|
+
normalizedLang = 'go';
|
|
36
|
+
}
|
|
37
|
+
else if (['rust', 'rs'].includes(langLower)) {
|
|
38
|
+
normalizedLang = 'rust';
|
|
39
|
+
}
|
|
40
|
+
else if (['fortran', 'f90', 'f95'].includes(langLower)) {
|
|
41
|
+
normalizedLang = 'fortran';
|
|
42
|
+
}
|
|
43
|
+
else if (['cobol', 'cob'].includes(langLower)) {
|
|
44
|
+
normalizedLang = 'cobol';
|
|
45
|
+
}
|
|
46
|
+
else if (langLower === 'c') {
|
|
47
|
+
normalizedLang = 'c';
|
|
48
|
+
}
|
|
49
|
+
else if (langLower === 'basic') {
|
|
50
|
+
normalizedLang = 'basic';
|
|
51
|
+
}
|
|
52
|
+
else if (langLower === 'java') {
|
|
53
|
+
normalizedLang = 'java';
|
|
54
|
+
}
|
|
55
|
+
else if (['perl', 'pl'].includes(langLower)) {
|
|
56
|
+
normalizedLang = 'perl';
|
|
57
|
+
}
|
|
58
|
+
else if (['csharp', 'cs'].includes(langLower)) {
|
|
59
|
+
normalizedLang = 'csharp';
|
|
60
|
+
}
|
|
61
|
+
else if (langLower === 'r') {
|
|
62
|
+
normalizedLang = 'r';
|
|
63
|
+
}
|
|
64
|
+
else if (['pascal', 'pas'].includes(langLower)) {
|
|
65
|
+
normalizedLang = 'pascal';
|
|
66
|
+
}
|
|
67
|
+
else if (['text', 'txt', 'output'].includes(langLower)) {
|
|
68
|
+
normalizedLang = 'text';
|
|
69
|
+
}
|
|
10
70
|
return Object.assign(snippets, {
|
|
11
71
|
snippets: snippets.snippets.concat([
|
|
12
|
-
{ code: "", fileName, lineNumber, complete: false, skip: skip ?? false }
|
|
72
|
+
{ code: "", language: normalizedLang, fileName, lineNumber, complete: false, skip: skip ?? false, indentation, id, outputOf, outputMode, args, env }
|
|
13
73
|
])
|
|
14
74
|
});
|
|
15
75
|
}
|
|
@@ -17,15 +77,20 @@ function addLineToLastSnippet(line) {
|
|
|
17
77
|
return function addLine(snippets) {
|
|
18
78
|
const lastSnippet = snippets.snippets[snippets.snippets.length - 1];
|
|
19
79
|
if (lastSnippet && !lastSnippet.complete) {
|
|
20
|
-
|
|
80
|
+
let lineToAdd = line;
|
|
81
|
+
if (lastSnippet.indentation && line.startsWith(lastSnippet.indentation)) {
|
|
82
|
+
lineToAdd = line.slice(lastSnippet.indentation.length);
|
|
83
|
+
}
|
|
84
|
+
lastSnippet.code += lineToAdd + "\n";
|
|
21
85
|
}
|
|
22
86
|
return snippets;
|
|
23
87
|
};
|
|
24
88
|
}
|
|
25
|
-
function endSnippet(snippets, _fileName,
|
|
89
|
+
function endSnippet(snippets, _fileName, lineNumber) {
|
|
26
90
|
const lastSnippet = snippets.snippets[snippets.snippets.length - 1];
|
|
27
91
|
if (lastSnippet) {
|
|
28
92
|
lastSnippet.complete = true;
|
|
93
|
+
lastSnippet.endLine = lineNumber;
|
|
29
94
|
}
|
|
30
95
|
return snippets;
|
|
31
96
|
}
|
|
@@ -37,9 +102,64 @@ function shareCodeInFile(snippets) {
|
|
|
37
102
|
snippets.shareCodeInFile = true;
|
|
38
103
|
return snippets;
|
|
39
104
|
}
|
|
105
|
+
function setId(id) {
|
|
106
|
+
return (snippets) => {
|
|
107
|
+
snippets.id = id;
|
|
108
|
+
return snippets;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function setOutputOf(id, mode) {
|
|
112
|
+
return (snippets) => {
|
|
113
|
+
snippets.outputOf = id;
|
|
114
|
+
if (mode === 'match:regex') {
|
|
115
|
+
snippets.outputMode = 'regex';
|
|
116
|
+
}
|
|
117
|
+
else if (mode === 'match:fuzzy' || mode === 'ignore-whitespace') {
|
|
118
|
+
snippets.outputMode = 'ignore-whitespace';
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
snippets.outputMode = 'exact';
|
|
122
|
+
}
|
|
123
|
+
return snippets;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function setArgs(argsStr) {
|
|
127
|
+
return (snippets) => {
|
|
128
|
+
// Simple space splitting, maybe improve for quotes?
|
|
129
|
+
// Assuming simple space separation for now.
|
|
130
|
+
// Filter out empty strings that can occur from leading/trailing whitespace
|
|
131
|
+
snippets.args = argsStr.split(/\s+/).filter(arg => arg.length > 0);
|
|
132
|
+
return snippets;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function setEnv(envStr) {
|
|
136
|
+
return (snippets) => {
|
|
137
|
+
const env = {};
|
|
138
|
+
// Parse KEY=VALUE pairs separated by spaces
|
|
139
|
+
const pairs = envStr.split(/\s+/);
|
|
140
|
+
for (const pair of pairs) {
|
|
141
|
+
const [key, value] = pair.split('=');
|
|
142
|
+
if (key && value) {
|
|
143
|
+
env[key] = value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
snippets.env = env;
|
|
147
|
+
return snippets;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
40
150
|
function parseLine(line) {
|
|
41
|
-
|
|
42
|
-
|
|
151
|
+
const argsMatch = isArgs(line);
|
|
152
|
+
if (argsMatch) {
|
|
153
|
+
return setArgs(argsMatch[1]);
|
|
154
|
+
}
|
|
155
|
+
const envMatch = isEnv(line);
|
|
156
|
+
if (envMatch) {
|
|
157
|
+
return setEnv(envMatch[1]);
|
|
158
|
+
}
|
|
159
|
+
const startMatch = isStartOfSnippet(line);
|
|
160
|
+
if (startMatch) {
|
|
161
|
+
// startMatch[1] is indentation, startMatch[2] is language
|
|
162
|
+
return (snippets, fileName, lineNumber) => startNewSnippet(snippets, fileName, lineNumber, startMatch[2], startMatch[1]);
|
|
43
163
|
}
|
|
44
164
|
if (isEndOfSnippet(line)) {
|
|
45
165
|
return endSnippet;
|
|
@@ -50,6 +170,16 @@ function parseLine(line) {
|
|
|
50
170
|
if (isCodeSharedInFile(line)) {
|
|
51
171
|
return shareCodeInFile;
|
|
52
172
|
}
|
|
173
|
+
const idMatch = isId(line);
|
|
174
|
+
if (idMatch) {
|
|
175
|
+
// console.log("Found ID:", idMatch[1]);
|
|
176
|
+
return setId(idMatch[1]);
|
|
177
|
+
}
|
|
178
|
+
const outputMatch = isOutputOf(line);
|
|
179
|
+
if (outputMatch) {
|
|
180
|
+
// console.log("Found OutputOf:", outputMatch[1], "Mode:", outputMatch[2]);
|
|
181
|
+
return setOutputOf(outputMatch[1], outputMatch[2]);
|
|
182
|
+
}
|
|
53
183
|
return addLineToLastSnippet(line);
|
|
54
184
|
}
|
|
55
185
|
function parseCodeSnippets(args) {
|
package/dist/reporter.js
CHANGED
|
@@ -27,6 +27,45 @@ function printResults(results) {
|
|
|
27
27
|
else {
|
|
28
28
|
console.log(chalk_1.default.red("Failed: " + failingCount));
|
|
29
29
|
}
|
|
30
|
+
// Summary Table
|
|
31
|
+
console.log("\nSummary Table:");
|
|
32
|
+
const headers = { lang: 'Language', file: 'File', line: 'Line', status: 'Status', time: 'Time (ms)' };
|
|
33
|
+
const rows = results.map(r => {
|
|
34
|
+
return {
|
|
35
|
+
lang: r.codeSnippet.language || 'text',
|
|
36
|
+
file: r.codeSnippet.fileName,
|
|
37
|
+
line: r.codeSnippet.lineNumber.toString(),
|
|
38
|
+
status: r.status === 'pass' ? '✅' : (r.status === 'skip' ? '⏭️' : '❌'),
|
|
39
|
+
time: r.executionTime ? r.executionTime.toFixed(2) : '-'
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
const widths = {
|
|
43
|
+
lang: headers.lang.length,
|
|
44
|
+
file: headers.file.length,
|
|
45
|
+
line: headers.line.length,
|
|
46
|
+
status: headers.status.length,
|
|
47
|
+
time: headers.time.length
|
|
48
|
+
};
|
|
49
|
+
rows.forEach(row => {
|
|
50
|
+
widths.lang = Math.max(widths.lang, row.lang.length);
|
|
51
|
+
widths.file = Math.max(widths.file, row.file.length);
|
|
52
|
+
widths.line = Math.max(widths.line, row.line.length);
|
|
53
|
+
// Emojis are tricky. Let's assume they take 2 visual columns.
|
|
54
|
+
// '✅'.length is 1, '❌'.length is 1, '⏭️'.length is 2.
|
|
55
|
+
// We'll trust the string length for now but maybe ensure minimum for status?
|
|
56
|
+
widths.status = Math.max(widths.status, [...row.status].length);
|
|
57
|
+
widths.time = Math.max(widths.time, row.time.length);
|
|
58
|
+
});
|
|
59
|
+
const padRight = (str, width) => str + ' '.repeat(Math.max(0, width - str.length));
|
|
60
|
+
const padLeft = (str, width) => ' '.repeat(Math.max(0, width - str.length)) + str;
|
|
61
|
+
// Header
|
|
62
|
+
console.log(`| ${padRight(headers.lang, widths.lang)} | ${padRight(headers.file, widths.file)} | ${padLeft(headers.line, widths.line)} | ${padRight(headers.status, widths.status)} | ${padLeft(headers.time, widths.time)} |`);
|
|
63
|
+
// Separator
|
|
64
|
+
console.log(`|-${'-'.repeat(widths.lang)}-|-${'-'.repeat(widths.file)}-|-${'-'.repeat(widths.line)}-|-${'-'.repeat(widths.status)}-|-${'-'.repeat(widths.time)}-|`);
|
|
65
|
+
// Rows
|
|
66
|
+
rows.forEach(row => {
|
|
67
|
+
console.log(`| ${padRight(row.lang, widths.lang)} | ${padRight(row.file, widths.file)} | ${padLeft(row.line, widths.line)} | ${padRight(row.status, widths.status)} | ${padLeft(row.time, widths.time)} |`);
|
|
68
|
+
});
|
|
30
69
|
}
|
|
31
70
|
function printFailure(result) {
|
|
32
71
|
console.log(chalk_1.default.red(`Failed - ${markDownErrorLocation(result)}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doccident/doccident",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Test all the code in your markdown docs!",
|
|
5
5
|
"main": "dist/doctest.js",
|
|
6
6
|
"files": [
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"build": "tsc",
|
|
15
15
|
"lint": "eslint .",
|
|
16
16
|
"test": "vitest run",
|
|
17
|
-
"
|
|
17
|
+
"test:readme": "node bin/cmd.js README.md",
|
|
18
|
+
"precommit": "npm run lint && npm run test && npm run test:readme",
|
|
18
19
|
"clean": "rm -rf dist",
|
|
19
20
|
"prepublishOnly": "npm run clean && npm run build"
|
|
20
21
|
},
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
"Nick Johnstone",
|
|
32
33
|
"Billaud Cipher <BillaudCipher@proton.me>"
|
|
33
34
|
],
|
|
34
|
-
"license": "
|
|
35
|
+
"license": "Apache-2.0",
|
|
35
36
|
"bugs": {
|
|
36
37
|
"url": "https://github.com/BillaudCipher/doccident/issues"
|
|
37
38
|
},
|