apidocly 1.0.3
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 +21 -0
- package/README.md +246 -0
- package/bin/apidocly.js +62 -0
- package/lib/generator/data-builder.js +170 -0
- package/lib/generator/index.js +334 -0
- package/lib/index.js +59 -0
- package/lib/parser/annotations.js +230 -0
- package/lib/parser/index.js +86 -0
- package/lib/parser/languages.js +57 -0
- package/lib/utils/config-loader.js +67 -0
- package/lib/utils/file-scanner.js +64 -0
- package/lib/utils/minifier.js +106 -0
- package/package.json +46 -0
- package/template/css/style.css +2670 -0
- package/template/index.html +243 -0
- package/template/js/auth.js +281 -0
- package/template/js/main.js +2933 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const COMMENT_PATTERNS = {
|
|
2
|
+
js: {
|
|
3
|
+
block: /\/\*\*[\s\S]*?\*\//g,
|
|
4
|
+
linePrefix: /^\s*\*\s?/gm,
|
|
5
|
+
start: '/**',
|
|
6
|
+
end: '*/'
|
|
7
|
+
},
|
|
8
|
+
php: {
|
|
9
|
+
block: /\/\*\*[\s\S]*?\*\//g,
|
|
10
|
+
linePrefix: /^\s*\*\s?/gm,
|
|
11
|
+
start: '/**',
|
|
12
|
+
end: '*/'
|
|
13
|
+
},
|
|
14
|
+
python: {
|
|
15
|
+
block: /"""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\'/g,
|
|
16
|
+
linePrefix: /^\s*/gm,
|
|
17
|
+
start: '"""',
|
|
18
|
+
end: '"""'
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function extractCommentBlocks(content, language) {
|
|
23
|
+
const pattern = COMMENT_PATTERNS[language];
|
|
24
|
+
if (!pattern) return [];
|
|
25
|
+
|
|
26
|
+
const blocks = [];
|
|
27
|
+
const matches = content.match(pattern.block);
|
|
28
|
+
|
|
29
|
+
if (!matches) return [];
|
|
30
|
+
|
|
31
|
+
for (const match of matches) {
|
|
32
|
+
if (!match.includes('@api')) continue;
|
|
33
|
+
|
|
34
|
+
let cleaned = match;
|
|
35
|
+
|
|
36
|
+
if (language === 'js' || language === 'php') {
|
|
37
|
+
cleaned = match
|
|
38
|
+
.replace(/^\/\*\*\s*/, '')
|
|
39
|
+
.replace(/\s*\*\/$/, '')
|
|
40
|
+
.replace(pattern.linePrefix, '')
|
|
41
|
+
.trim();
|
|
42
|
+
} else if (language === 'python') {
|
|
43
|
+
cleaned = match
|
|
44
|
+
.replace(/^"""\s*/, '')
|
|
45
|
+
.replace(/\s*"""$/, '')
|
|
46
|
+
.replace(/^'''\s*/, '')
|
|
47
|
+
.replace(/\s*'''$/, '')
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
blocks.push(cleaned);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return blocks;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { extractCommentBlocks, COMMENT_PATTERNS };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
name: 'API Documentation',
|
|
6
|
+
version: '1.0.0',
|
|
7
|
+
description: '',
|
|
8
|
+
title: 'API Documentation',
|
|
9
|
+
url: '',
|
|
10
|
+
sampleUrl: '',
|
|
11
|
+
environments: [],
|
|
12
|
+
template: {
|
|
13
|
+
withCompare: true,
|
|
14
|
+
withGenerator: true
|
|
15
|
+
},
|
|
16
|
+
password: null,
|
|
17
|
+
passwordMessage: 'Enter password to view API documentation',
|
|
18
|
+
header: null,
|
|
19
|
+
footer: null
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CONFIG_FILES = [
|
|
23
|
+
'apidocly.json',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function loadConfig(configPath, inputPath) {
|
|
27
|
+
let config = { ...DEFAULT_CONFIG };
|
|
28
|
+
let configDir = inputPath;
|
|
29
|
+
|
|
30
|
+
if (configPath) {
|
|
31
|
+
if (fs.existsSync(configPath)) {
|
|
32
|
+
const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
33
|
+
config = { ...config, ...userConfig };
|
|
34
|
+
configDir = path.dirname(configPath);
|
|
35
|
+
} else {
|
|
36
|
+
throw new Error(`Config file not found: ${configPath}`);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
for (const filename of CONFIG_FILES) {
|
|
40
|
+
const filePath = path.join(inputPath, filename);
|
|
41
|
+
if (fs.existsSync(filePath)) {
|
|
42
|
+
const userConfig = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
43
|
+
config = { ...config, ...userConfig };
|
|
44
|
+
configDir = inputPath;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (config.header && config.header.filename) {
|
|
51
|
+
const headerPath = path.join(configDir, config.header.filename);
|
|
52
|
+
if (fs.existsSync(headerPath)) {
|
|
53
|
+
config.header.content = fs.readFileSync(headerPath, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (config.footer && config.footer.filename) {
|
|
58
|
+
const footerPath = path.join(configDir, config.footer.filename);
|
|
59
|
+
if (fs.existsSync(footerPath)) {
|
|
60
|
+
config.footer.content = fs.readFileSync(footerPath, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { loadConfig, DEFAULT_CONFIG };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { glob } = require('glob');
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_EXTENSIONS = {
|
|
6
|
+
js: ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'],
|
|
7
|
+
php: ['.php'],
|
|
8
|
+
python: ['.py']
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const ALL_EXTENSIONS = Object.values(SUPPORTED_EXTENSIONS).flat();
|
|
12
|
+
|
|
13
|
+
async function scanFiles(inputPath, options = {}) {
|
|
14
|
+
const { verbose } = options;
|
|
15
|
+
const files = [];
|
|
16
|
+
|
|
17
|
+
const stat = fs.statSync(inputPath);
|
|
18
|
+
|
|
19
|
+
if (stat.isFile()) {
|
|
20
|
+
const ext = path.extname(inputPath).toLowerCase();
|
|
21
|
+
if (ALL_EXTENSIONS.includes(ext)) {
|
|
22
|
+
files.push(inputPath);
|
|
23
|
+
}
|
|
24
|
+
} else if (stat.isDirectory()) {
|
|
25
|
+
const patterns = ALL_EXTENSIONS.map(ext => `**/*${ext}`);
|
|
26
|
+
|
|
27
|
+
for (const pattern of patterns) {
|
|
28
|
+
const matches = await glob(pattern, {
|
|
29
|
+
cwd: inputPath,
|
|
30
|
+
absolute: true,
|
|
31
|
+
ignore: [
|
|
32
|
+
'**/node_modules/**',
|
|
33
|
+
'**/vendor/**',
|
|
34
|
+
'**/.git/**',
|
|
35
|
+
'**/dist/**',
|
|
36
|
+
'**/build/**',
|
|
37
|
+
'**/__pycache__/**',
|
|
38
|
+
'**/venv/**'
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
files.push(...matches);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const uniqueFiles = [...new Set(files)];
|
|
46
|
+
|
|
47
|
+
if (verbose) {
|
|
48
|
+
console.log(` Found ${uniqueFiles.length} source files`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return uniqueFiles;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getLanguage(filePath) {
|
|
55
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
56
|
+
|
|
57
|
+
if (SUPPORTED_EXTENSIONS.js.includes(ext)) return 'js';
|
|
58
|
+
if (SUPPORTED_EXTENSIONS.php.includes(ext)) return 'php';
|
|
59
|
+
if (SUPPORTED_EXTENSIONS.python.includes(ext)) return 'python';
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { scanFiles, getLanguage, SUPPORTED_EXTENSIONS };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS/JS Minifier using Terser
|
|
3
|
+
* Terser provides reliable minification and obfuscation with proper scope analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { minify } = require('terser');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minify CSS
|
|
10
|
+
*/
|
|
11
|
+
function minifyCSS(css) {
|
|
12
|
+
return css
|
|
13
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
14
|
+
.replace(/[\n\r\t]/g, '')
|
|
15
|
+
.replace(/\s*([{}:;,>~+])\s*/g, '$1')
|
|
16
|
+
.replace(/\s{2,}/g, ' ')
|
|
17
|
+
.replace(/;}/g, '}')
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Minify JavaScript using Terser (synchronous wrapper)
|
|
23
|
+
*/
|
|
24
|
+
function minifyJS(js) {
|
|
25
|
+
// Terser is async, but we need sync for compatibility
|
|
26
|
+
// Use basic minification for sync context
|
|
27
|
+
return js
|
|
28
|
+
.replace(/\/\/[^\n]*/g, '') // Remove single-line comments
|
|
29
|
+
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove multi-line comments
|
|
30
|
+
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
31
|
+
.replace(/\s*([{}:;,=()[\]<>!&|?+\-*\/])\s*/g, '$1') // Remove space around operators
|
|
32
|
+
.replace(/;\s*}/g, '}') // Remove trailing semicolons
|
|
33
|
+
.trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Minify JSON (for api_data.js)
|
|
38
|
+
*/
|
|
39
|
+
function minifyJSON(obj) {
|
|
40
|
+
return JSON.stringify(obj);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Minify and obfuscate JavaScript using Terser (async)
|
|
45
|
+
* Returns a Promise
|
|
46
|
+
*/
|
|
47
|
+
async function minifyAndObfuscateJSAsync(js) {
|
|
48
|
+
try {
|
|
49
|
+
const result = await minify(js, {
|
|
50
|
+
compress: {
|
|
51
|
+
dead_code: true,
|
|
52
|
+
drop_debugger: true,
|
|
53
|
+
conditionals: true,
|
|
54
|
+
evaluate: true,
|
|
55
|
+
booleans: true,
|
|
56
|
+
loops: true,
|
|
57
|
+
unused: true,
|
|
58
|
+
hoist_funs: true,
|
|
59
|
+
keep_fargs: false,
|
|
60
|
+
hoist_vars: false,
|
|
61
|
+
if_return: true,
|
|
62
|
+
join_vars: true,
|
|
63
|
+
side_effects: true,
|
|
64
|
+
warnings: false
|
|
65
|
+
},
|
|
66
|
+
mangle: {
|
|
67
|
+
toplevel: false, // Don't mangle top-level names (preserves window.* functions)
|
|
68
|
+
reserved: [
|
|
69
|
+
// Preserve API data variables
|
|
70
|
+
'apiData', 'apiDataEncrypted', 'apiDataAuth', 'apiVersions',
|
|
71
|
+
// Preserve global functions exposed via window.*
|
|
72
|
+
'downloadOpenAPI', 'downloadPostmanCollection', 'downloadInsomniaCollection',
|
|
73
|
+
'switchCodeSample', 'copyCodeSample', 'toggleEndpoint', 'toggleGroup',
|
|
74
|
+
'showEndpoint', 'goBack', 'hashChange'
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
format: {
|
|
78
|
+
comments: false
|
|
79
|
+
},
|
|
80
|
+
sourceMap: false
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return result.code || js;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.warn('Terser minification failed, using basic minification:', error.message);
|
|
86
|
+
return minifyJS(js);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Synchronous wrapper for minifyAndObfuscateJS
|
|
92
|
+
* Uses basic minification (Terser is async-only)
|
|
93
|
+
*/
|
|
94
|
+
function minifyAndObfuscateJS(js) {
|
|
95
|
+
// For sync usage, just do basic minification
|
|
96
|
+
// The async version should be preferred
|
|
97
|
+
return minifyJS(js);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
minifyCSS,
|
|
102
|
+
minifyJS,
|
|
103
|
+
minifyJSON,
|
|
104
|
+
minifyAndObfuscateJS,
|
|
105
|
+
minifyAndObfuscateJSAsync
|
|
106
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apidocly",
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"description": "API documentation generator with beautiful dark UI, password protection, and OpenAPI/Rest client export.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"apidocly": "bin/apidocly.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"api",
|
|
14
|
+
"documentation",
|
|
15
|
+
"generator",
|
|
16
|
+
"apidoc",
|
|
17
|
+
"openapi",
|
|
18
|
+
"swagger",
|
|
19
|
+
"dark-mode",
|
|
20
|
+
"rest-api",
|
|
21
|
+
"api-docs",
|
|
22
|
+
"jsdoc",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"homepage": "https://apidocly.com",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://apidocly.com"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^11.1.0",
|
|
33
|
+
"glob": "11.1.0",
|
|
34
|
+
"terser": "^5.37.0"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14.0.0"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"bin",
|
|
41
|
+
"lib",
|
|
42
|
+
"template",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
]
|
|
46
|
+
}
|