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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 APIDocly
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # apidocly
2
+
3
+ [![npm version](https://img.shields.io/npm/v/apidocly.svg)](https://www.npmjs.com/package/apidocly)
4
+ [![npm downloads](https://img.shields.io/npm/dm/apidocly.svg)](https://www.npmjs.com/package/apidocly)
5
+
6
+ API documentation generator with shadcn-style dark UI and password protection.
7
+
8
+ A modern, dark-themed package that generates pure HTML/CSS/JS documentation.
9
+
10
+ ## Features
11
+
12
+ - **apidoc-compatible**: Uses the same annotation syntax as apidoc.js
13
+ - **Shadcn Dark UI**: Beautiful, modern dark theme inspired by shadcn/ui
14
+ - **OpenAPI Export**: Automatically generates OpenAPI 3.0 JSON for AI tools and code generators
15
+ - **Password Protection**: Optional client-side password protection with AES-256-GCM encryption
16
+ - **Zero Dependencies Output**: Generated docs are pure HTML/CSS/JS - no frameworks required
17
+ - **Single File Mode**: Generate all-in-one HTML file with embedded assets
18
+ - **Multiple Languages**: Supports JavaScript, TypeScript, PHP, and Python
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ # Global installation
24
+ npm install -g apidocly
25
+
26
+ # Local installation
27
+ npm install apidocly --save-dev
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Command Line
33
+
34
+ ```bash
35
+ # Basic usage
36
+ apidocly -i ./src -o ./docs
37
+
38
+ # With options
39
+ apidocly -i ./src -o ./docs --verbose --single-file
40
+
41
+ # Show help
42
+ apidocly --help
43
+ ```
44
+
45
+ ### Options
46
+
47
+ | Option | Description | Default |
48
+ |--------|-------------|---------|
49
+ | `-i, --input <path>` | Input source files path | `./` |
50
+ | `-o, --output <path>` | Output documentation path | `./docs` |
51
+ | `-c, --config <path>` | Path to config file | auto-detect |
52
+ | `--private` | Include @apiPrivate methods | `false` |
53
+ | `--single-file` | Generate single HTML file | `false` |
54
+ | `--verbose` | Show verbose output | `false` |
55
+
56
+ ### Programmatic Usage
57
+
58
+ ```javascript
59
+ const apidocly = require('apidocly');
60
+
61
+ apidocly.generate({
62
+ input: './src',
63
+ output: './docs',
64
+ singleFile: false,
65
+ verbose: true
66
+ }).then(result => {
67
+ console.log(`Generated ${result.endpoints} endpoints`);
68
+ });
69
+ ```
70
+
71
+ ## Configuration
72
+
73
+ Create `apidocly.json` in your project root:
74
+
75
+ ```json
76
+ {
77
+ "name": "My API",
78
+ "version": "1.0.0",
79
+ "description": "API Documentation",
80
+ "title": "My API Docs",
81
+ "url": "https://api.example.com",
82
+ "sampleUrl": "https://api.example.com",
83
+ "password": null,
84
+ "passwordMessage": "Enter password to view documentation",
85
+ "header": {
86
+ "title": "Introduction",
87
+ "filename": "header.md"
88
+ },
89
+ "footer": {
90
+ "title": "Footer",
91
+ "filename": "footer.md"
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Password Protection
97
+
98
+ To enable password protection, add a `password` field to your config:
99
+
100
+ ```json
101
+ {
102
+ "password": "your-password",
103
+ "passwordMessage": "Enter password to access API documentation"
104
+ }
105
+ ```
106
+
107
+ You can also use a pre-hashed password:
108
+
109
+ ```json
110
+ {
111
+ "password": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
112
+ }
113
+ ```
114
+
115
+ ## Annotation Syntax
116
+
117
+ apidocly uses the same annotation syntax as apidoc.js.
118
+
119
+ ### Basic Example
120
+
121
+ ```javascript
122
+ /**
123
+ * @api {get} /users/:id Get user by ID
124
+ * @apiName GetUser
125
+ * @apiGroup Users
126
+ * @apiVersion 1.0.0
127
+ *
128
+ * @apiParam {Number} id User's unique ID
129
+ *
130
+ * @apiSuccess {Number} id User ID
131
+ * @apiSuccess {String} name User name
132
+ * @apiSuccess {String} email User email
133
+ *
134
+ * @apiError (Error 404) {String} error User not found
135
+ */
136
+ ```
137
+
138
+ ### Supported Annotations
139
+
140
+ | Annotation | Description |
141
+ |------------|-------------|
142
+ | `@api {method} path [title]` | Required. Define HTTP method and path |
143
+ | `@apiName name` | Unique identifier for the endpoint |
144
+ | `@apiGroup name` | Group name for navigation |
145
+ | `@apiVersion version` | API version (semver) |
146
+ | `@apiDescription text` | Detailed description |
147
+ | `@apiParam [(group)] [{type}] field [description]` | URL parameters |
148
+ | `@apiQuery [(group)] [{type}] field [description]` | Query string parameters |
149
+ | `@apiBody [(group)] [{type}] field [description]` | Request body parameters |
150
+ | `@apiHeader [(group)] [{type}] field [description]` | Request headers |
151
+ | `@apiSuccess [(group)] [{type}] field [description]` | Success response fields |
152
+ | `@apiError [(group)] [{type}] field [description]` | Error response fields |
153
+ | `@apiExample [{type}] title` | Usage examples |
154
+ | `@apiSuccessExample [{type}] [title]` | Success response examples |
155
+ | `@apiErrorExample [{type}] [title]` | Error response examples |
156
+ | `@apiPermission name` | Required permission |
157
+ | `@apiDeprecated [text]` | Mark as deprecated |
158
+ | `@apiPrivate` | Mark as private |
159
+ | `@apiIgnore [hint]` | Ignore this block |
160
+ | `@apiDefine name [title]` | Define reusable block |
161
+ | `@apiUse name` | Include defined block |
162
+ | `@apiSampleRequest url` | Configure sample request URL |
163
+
164
+ ### Parameter Types
165
+
166
+ ```javascript
167
+ // Basic types
168
+ @apiParam {String} name User name
169
+ @apiParam {Number} age User age
170
+ @apiParam {Boolean} active Is active
171
+ @apiParam {Object} profile User profile
172
+
173
+ // Arrays
174
+ @apiParam {String[]} tags List of tags
175
+
176
+ // Optional parameters (use square brackets)
177
+ @apiParam {String} [nickname] Optional nickname
178
+
179
+ // Default values
180
+ @apiParam {Number} [page=1] Page number
181
+
182
+ // Size constraints
183
+ @apiParam {String{..100}} name Max 100 characters
184
+ @apiParam {String{5..}} name Min 5 characters
185
+ @apiParam {String{5..100}} name 5-100 characters
186
+
187
+ // Allowed values
188
+ @apiParam {String="admin","user"} role User role
189
+ ```
190
+
191
+ ## Supported Languages
192
+
193
+ | Language | File Extensions | Comment Style |
194
+ |----------|-----------------|---------------|
195
+ | JavaScript/TypeScript | `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` | `/** */` |
196
+ | PHP | `.php` | `/** */` |
197
+ | Python | `.py` | `"""` docstrings |
198
+
199
+ ## Output Structure
200
+
201
+ ### Multiple Files (default)
202
+
203
+ ```
204
+ docs/
205
+ ├── index.html
206
+ ├── api_data.js
207
+ ├── openapi.json # OpenAPI 3.0 specification
208
+ ├── css/
209
+ │ └── style.css
210
+ └── js/
211
+ ├── main.js
212
+ └── auth.js
213
+ ```
214
+
215
+ ### Single File (`--single-file`)
216
+
217
+ ```
218
+ docs/
219
+ ├── index.html # All-in-one HTML
220
+ └── openapi.json # OpenAPI 3.0 specification
221
+ ```
222
+
223
+ ## OpenAPI Export
224
+
225
+ Every generated documentation includes an `openapi.json` file that follows the OpenAPI 3.0 specification. This file can be used with:
226
+
227
+ - **AI Assistants**: Feed to ChatGPT, Claude, or other AI tools to generate API client code
228
+ - **Code Generators**: Use with OpenAPI Generator to create SDKs in any language
229
+ - **API Testing Tools**: Import into Postman, Insomnia, or similar tools
230
+ - **Type Generation**: Generate TypeScript interfaces, Zod schemas, or Pydantic models
231
+
232
+ ### AI Integration Example
233
+
234
+ ```
235
+ You: Here's my API spec (attach openapi.json). Generate a TypeScript client with Axios.
236
+
237
+ AI: Based on your OpenAPI specification, here's a complete TypeScript client...
238
+ ```
239
+
240
+ ## License
241
+
242
+ MIT
243
+
244
+ ---
245
+
246
+ **Website:** [apidocly.com](https://apidocly.com)
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const path = require('path');
5
+ const apidocly = require('../lib/index');
6
+ const pkg = require('../package.json');
7
+
8
+ program
9
+ .name('apidocly')
10
+ .description('API documentation generator')
11
+ .version(pkg.version);
12
+
13
+ program
14
+ .option('-i, --input <path>', 'Input source files path', './')
15
+ .option('-o, --output <path>', 'Output documentation path', './docs')
16
+ .option('-c, --config <path>', 'Path to config file')
17
+ .option('--private', 'Include @apiPrivate methods', false)
18
+ .option('--single-file', 'Generate single HTML file with embedded assets', false)
19
+ .option('--verbose', 'Show verbose output', false);
20
+
21
+ program.parse();
22
+
23
+ const options = program.opts();
24
+
25
+ async function run() {
26
+ console.log(`\n apidocly v${pkg.version}\n`);
27
+
28
+ try {
29
+ const inputPath = path.resolve(process.cwd(), options.input);
30
+ const outputPath = path.resolve(process.cwd(), options.output);
31
+ const configPath = options.config
32
+ ? path.resolve(process.cwd(), options.config)
33
+ : null;
34
+
35
+ if (options.verbose) {
36
+ console.log(' Input: ', inputPath);
37
+ console.log(' Output: ', outputPath);
38
+ if (configPath) console.log(' Config: ', configPath);
39
+ console.log('');
40
+ }
41
+
42
+ const result = await apidocly.generate({
43
+ input: inputPath,
44
+ output: outputPath,
45
+ config: configPath,
46
+ includePrivate: options.private,
47
+ singleFile: options.singleFile,
48
+ verbose: options.verbose
49
+ });
50
+
51
+ console.log(` Done! Generated documentation for ${result.endpoints} endpoints.`);
52
+ console.log(` Output: ${outputPath}\n`);
53
+ } catch (error) {
54
+ console.error(' Error:', error.message);
55
+ if (options.verbose) {
56
+ console.error(error.stack);
57
+ }
58
+ process.exit(1);
59
+ }
60
+ }
61
+
62
+ run();
@@ -0,0 +1,170 @@
1
+ function buildApiData(endpoints, config, options = {}) {
2
+ const { includePrivate } = options;
3
+
4
+ let filteredEndpoints = endpoints;
5
+ if (!includePrivate) {
6
+ filteredEndpoints = endpoints.filter(ep => !ep.private);
7
+ }
8
+
9
+ // Don't filter by version here - frontend will handle version filtering
10
+ // This allows users to switch between endpoint versions in the UI
11
+
12
+ const groups = {};
13
+
14
+ for (const endpoint of filteredEndpoints) {
15
+ const groupName = endpoint.group || 'Default';
16
+
17
+ if (!groups[groupName]) {
18
+ groups[groupName] = {
19
+ name: groupName,
20
+ endpoints: []
21
+ };
22
+ }
23
+
24
+ groups[groupName].endpoints.push({
25
+ id: generateId(endpoint),
26
+ method: endpoint.method,
27
+ path: endpoint.path,
28
+ title: endpoint.title || endpoint.name || `${endpoint.method} ${endpoint.path}`,
29
+ name: endpoint.name || generateName(endpoint),
30
+ version: endpoint.version,
31
+ description: endpoint.description,
32
+ permission: endpoint.permission,
33
+ deprecated: endpoint.deprecated,
34
+ deprecatedText: endpoint.deprecatedText,
35
+ sampleRequest: endpoint.sampleRequest,
36
+ parameters: groupParams(endpoint.params),
37
+ query: groupParams(endpoint.query),
38
+ body: groupParams(endpoint.body),
39
+ headers: groupParams(endpoint.headers),
40
+ success: groupResponses(endpoint.success),
41
+ error: groupResponses(endpoint.error),
42
+ examples: endpoint.examples,
43
+ successExamples: endpoint.successExamples,
44
+ errorExamples: endpoint.errorExamples,
45
+ paramExamples: endpoint.paramExamples,
46
+ headerExamples: endpoint.headerExamples
47
+ });
48
+ }
49
+
50
+ const sortedGroups = Object.values(groups).sort((a, b) =>
51
+ a.name.localeCompare(b.name)
52
+ );
53
+
54
+ for (const group of sortedGroups) {
55
+ group.endpoints.sort((a, b) => {
56
+ const methodOrder = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
57
+ const aOrder = methodOrder.indexOf(a.method);
58
+ const bOrder = methodOrder.indexOf(b.method);
59
+ if (aOrder !== bOrder) return aOrder - bOrder;
60
+ return a.path.localeCompare(b.path);
61
+ });
62
+ }
63
+
64
+ return {
65
+ project: {
66
+ name: config.name,
67
+ version: config.version,
68
+ description: config.description,
69
+ title: config.title,
70
+ url: config.url,
71
+ sampleUrl: config.sampleUrl,
72
+ environments: config.environments || []
73
+ },
74
+ header: config.header,
75
+ footer: config.footer,
76
+ groups: sortedGroups,
77
+ template: config.template,
78
+ generator: {
79
+ name: 'apidocly',
80
+ time: new Date().toISOString()
81
+ }
82
+ };
83
+ }
84
+
85
+ function generateId(endpoint) {
86
+ const name = endpoint.name || generateName(endpoint);
87
+ return name.toLowerCase().replace(/[^a-z0-9]/g, '-');
88
+ }
89
+
90
+ function generateName(endpoint) {
91
+ const method = endpoint.method.charAt(0) + endpoint.method.slice(1).toLowerCase();
92
+ const path = endpoint.path
93
+ .replace(/[/:{}]/g, '_')
94
+ .replace(/_+/g, '_')
95
+ .replace(/^_|_$/g, '');
96
+ return `${method}${path.charAt(0).toUpperCase()}${path.slice(1)}`;
97
+ }
98
+
99
+ function groupParams(params) {
100
+ const grouped = {};
101
+
102
+ for (const param of params) {
103
+ const groupName = param.group || 'Parameter';
104
+ if (!grouped[groupName]) {
105
+ grouped[groupName] = [];
106
+ }
107
+ grouped[groupName].push(param);
108
+ }
109
+
110
+ return grouped;
111
+ }
112
+
113
+ function groupResponses(responses) {
114
+ const grouped = {};
115
+
116
+ for (const response of responses) {
117
+ const groupName = response.group || 'Response';
118
+ if (!grouped[groupName]) {
119
+ grouped[groupName] = [];
120
+ }
121
+ grouped[groupName].push(response);
122
+ }
123
+
124
+ return grouped;
125
+ }
126
+
127
+ function filterLatestVersions(endpoints) {
128
+ // Group endpoints by their unique key (method + normalized path + name)
129
+ const endpointMap = new Map();
130
+
131
+ for (const endpoint of endpoints) {
132
+ // Create a unique key based on method, path pattern, and name
133
+ // Normalize the path by removing version prefix (e.g., /1.0.0/, /1.0.1/)
134
+ const normalizedPath = endpoint.path.replace(/^\/\d+\.\d+\.\d+/, '');
135
+ const key = `${endpoint.method}:${normalizedPath}:${endpoint.name || ''}`;
136
+
137
+ const existingEndpoint = endpointMap.get(key);
138
+
139
+ if (!existingEndpoint) {
140
+ endpointMap.set(key, endpoint);
141
+ } else {
142
+ // Compare versions and keep the higher one
143
+ const existingVersion = existingEndpoint.version || '0.0.0';
144
+ const newVersion = endpoint.version || '0.0.0';
145
+
146
+ if (compareVersions(newVersion, existingVersion) > 0) {
147
+ endpointMap.set(key, endpoint);
148
+ }
149
+ }
150
+ }
151
+
152
+ return Array.from(endpointMap.values());
153
+ }
154
+
155
+ function compareVersions(v1, v2) {
156
+ const parts1 = v1.split('.').map(Number);
157
+ const parts2 = v2.split('.').map(Number);
158
+
159
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
160
+ const num1 = parts1[i] || 0;
161
+ const num2 = parts2[i] || 0;
162
+
163
+ if (num1 > num2) return 1;
164
+ if (num1 < num2) return -1;
165
+ }
166
+
167
+ return 0;
168
+ }
169
+
170
+ module.exports = { buildApiData };