@awesomeness-js/utils 1.0.11 β 1.0.13
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/README.md +141 -8
- package/build.js +6 -1
- package/index.js +8 -8
- package/package.json +1 -1
- package/src/build.js +141 -89
- package/src/convertBytes.js +8 -0
- package/src/ignoreFolder/ignoreMe.js +3 -0
- package/src/ignoreMe.js +3 -0
- package/src/namespaceExample/deep/deep.js +10 -0
- package/src/namespaceExample/example.js +3 -0
- package/test.js +3 -0
package/README.md
CHANGED
|
@@ -1,16 +1,149 @@
|
|
|
1
1
|
# Nothing Special
|
|
2
2
|
|
|
3
|
-
Just some utils...
|
|
3
|
+
Just some <u>zero dependency</u> utils ...
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# π Auto-Generate API Exports for Your Node.js Project
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## π Why "build" Exists
|
|
12
|
+
|
|
13
|
+
When working on a Node.js project, you often need to import multiple functions from a directory and structure them in a clean, accessible way. Webpack is overkill for thisβitβs designed for browser bundling, not for generating structured API exports in a Node.js environment.
|
|
14
|
+
|
|
15
|
+
This script **automates** that process. It dynamically scans your source directory (`./src`), imports functions, and generates an `index.js` file that:
|
|
16
|
+
|
|
17
|
+
β
**Consolidates all exports** into a structured API object.
|
|
18
|
+
β
**Preserves function names and namespaces** based on folder structure.
|
|
19
|
+
β
**Extracts JSDoc comments** for better documentation.
|
|
20
|
+
β
**Works in plain Node.js**βno need for Webpack or extra dependencies.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π Why Use "build" Instead of Webpack?
|
|
25
|
+
|
|
26
|
+
**Webpack is great for frontend bundling, but itβs not ideal for this use case.** Hereβs why this script is a better choice:
|
|
27
|
+
|
|
28
|
+
β **Zero dependencies**βRuns in plain Node.js, no Webpack or config files required.
|
|
29
|
+
β **Automatic function export generation**βNo need to manually update an `index.js`.
|
|
30
|
+
β **JSDoc extraction**βIncludes comments directly in the generated file.
|
|
31
|
+
β **Simple and predictable**βYou control how exports are structured.
|
|
32
|
+
β **Namespace support**βUses folder structure to organize functions logically.
|
|
33
|
+
|
|
34
|
+
With this script, you can stop wasting time managing exports and focus on writing code.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## β‘ How It Works
|
|
39
|
+
|
|
40
|
+
1. **Scans** the `./src` directory for `.js` files.
|
|
41
|
+
2. **Generates** import statements dynamically.
|
|
42
|
+
3. **Creates** an API object that mirrors your folder structure.
|
|
43
|
+
4. **Extracts JSDoc comments** from each file and attaches them to the exports.
|
|
44
|
+
5. **Outputs** a clean, structured `index.js` file, ready to use.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## π§ Usage
|
|
49
|
+
|
|
50
|
+
Simply run:
|
|
51
|
+
|
|
52
|
+
By default, this will:
|
|
53
|
+
- Scan `./src` for JavaScript files
|
|
54
|
+
- Generate an `index.js` file
|
|
55
|
+
- Structure exports based on your folder hierarchy
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
|
|
59
|
+
import { build } from '@awesomeness-js/utils';
|
|
60
|
+
|
|
61
|
+
build();
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If you need custom paths, modify the `src` and `dest` options in the script:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
4
68
|
|
|
5
|
-
example usage:
|
|
6
|
-
```javascript
|
|
7
69
|
import { build } from '@awesomeness-js/utils';
|
|
8
70
|
|
|
9
|
-
|
|
10
|
-
src: './
|
|
11
|
-
dest:
|
|
71
|
+
build({
|
|
72
|
+
src: './my-functions',
|
|
73
|
+
dest: './api.js'
|
|
12
74
|
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## π Example Output
|
|
80
|
+
|
|
81
|
+
If your folder structure looks like this:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
src/
|
|
85
|
+
βββ utils/
|
|
86
|
+
β βββ formatDate.js
|
|
87
|
+
β βββ generateId.js
|
|
88
|
+
βββ services/
|
|
89
|
+
β βββ fetchData.js
|
|
90
|
+
βββ calculate.js
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Your generated `index.js` will look like this:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
/**
|
|
97
|
+
* This file is auto-generated by the build script.
|
|
98
|
+
* It consolidates API functions for use in the application.
|
|
99
|
+
* Do not edit manually.
|
|
100
|
+
*/
|
|
101
|
+
|
|
102
|
+
import utils_formatDate from './src/utils/formatDate.js';
|
|
103
|
+
import utils_generateId from './src/utils/generateId.js';
|
|
104
|
+
import services_fetchData from './src/services/fetchData.js';
|
|
105
|
+
import calculate from './src/calculate.js';
|
|
106
|
+
|
|
107
|
+
export { calculate };
|
|
108
|
+
export default {
|
|
109
|
+
utils: {
|
|
110
|
+
formatDate: utils_formatDate,
|
|
111
|
+
generateId: utils_generateId
|
|
112
|
+
},
|
|
113
|
+
services: {
|
|
114
|
+
fetchData: services_fetchData
|
|
115
|
+
},
|
|
116
|
+
calculate
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Your api is now neatly organized and ready to use!
|
|
121
|
+
and will look like this
|
|
122
|
+
```javascript
|
|
123
|
+
import { calculate } from './api.js';
|
|
124
|
+
const result = calculate(5, 10);
|
|
125
|
+
console.log(result);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
or
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
import { utils } from './api.js';
|
|
132
|
+
const id = utils.generateId();
|
|
133
|
+
console.log(id);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## π Who Should Use This?
|
|
141
|
+
|
|
142
|
+
- **Node.js developers** who want automatic API exports.
|
|
143
|
+
- **Backend teams** managing large function directories.
|
|
144
|
+
- **Anyone tired of manually updating `index.js` files.**
|
|
145
|
+
|
|
146
|
+
If you donβt need Webpackβs **complexity** but want **automatic structured exports**, this script is for you.
|
|
13
147
|
|
|
14
|
-
|
|
148
|
+
π **Try it out and let automation handle your exports!** π
|
|
15
149
|
|
|
16
|
-
```
|
package/build.js
CHANGED
package/index.js
CHANGED
|
@@ -28,16 +28,16 @@ export { _toPennies as toPennies };
|
|
|
28
28
|
export { _uuid as uuid };
|
|
29
29
|
|
|
30
30
|
export default {
|
|
31
|
+
/**${match[1]}*/
|
|
32
|
+
build: _build,
|
|
33
|
+
combineFiles: _combineFiles,
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
35
|
+
* Converts a given number of bytes into a more readable string format with appropriate units.
|
|
33
36
|
*
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
36
|
-
* @
|
|
37
|
-
* @returns {bool} - Returns true if the output file is generated successfully.
|
|
37
|
+
* @param {number} bytes - The number of bytes to convert.
|
|
38
|
+
* @param {number} [precision=2] - The number of decimal places to include in the result.
|
|
39
|
+
* @returns {string} The converted bytes in a string format with appropriate units.
|
|
38
40
|
*/
|
|
39
|
-
build: _build,
|
|
40
|
-
combineFiles: _combineFiles,
|
|
41
41
|
convertBytes: _convertBytes,
|
|
42
42
|
each: _each,
|
|
43
43
|
eachAsync: _eachAsync,
|
|
@@ -46,5 +46,5 @@ export default {
|
|
|
46
46
|
md5: _md5,
|
|
47
47
|
setLocalEnvs: _setLocalEnvs,
|
|
48
48
|
toPennies: _toPennies,
|
|
49
|
-
uuid: _uuid
|
|
49
|
+
uuid: _uuid,
|
|
50
50
|
};
|
package/package.json
CHANGED
package/src/build.js
CHANGED
|
@@ -1,115 +1,161 @@
|
|
|
1
1
|
import { readdirSync, statSync, writeFileSync, readFileSync } from 'fs';
|
|
2
2
|
import { join, sep } from 'path';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
function shouldIgnore(filePath, ignorePatterns) {
|
|
5
|
+
const ignore = ignorePatterns.some(pattern => {
|
|
6
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
7
|
+
const normalizedPattern = pattern.replace(/\\/g, '/');
|
|
8
|
+
if (normalizedPath === normalizedPattern) return true;
|
|
9
|
+
if (normalizedPattern.endsWith('/*')) {
|
|
10
|
+
const baseDir = normalizedPattern.slice(0, -2);
|
|
11
|
+
return normalizedPath.startsWith(baseDir + '/');
|
|
12
|
+
}
|
|
13
|
+
if (normalizedPattern.endsWith('/')) {
|
|
14
|
+
return normalizedPath === normalizedPattern.slice(0, -1) ||
|
|
15
|
+
normalizedPath.startsWith(normalizedPattern);
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
});
|
|
19
|
+
console.log('shouldIgnore', filePath, ignore);
|
|
20
|
+
return ignore;
|
|
21
|
+
}
|
|
13
22
|
|
|
14
|
-
function getAllFiles(base, dir, files = []) {
|
|
23
|
+
function getAllFiles(base, dir, files = [], ignore = []) {
|
|
15
24
|
const directory = join(base, dir);
|
|
16
|
-
|
|
25
|
+
const normalizedDir = dir.replace(/\\/g, '/');
|
|
26
|
+
if (ignore.some(pattern => normalizedDir.startsWith(pattern.replace(/\/\*$/, '')))) {
|
|
27
|
+
console.log('Ignoring folder:', normalizedDir);
|
|
28
|
+
return files;
|
|
29
|
+
}
|
|
30
|
+
let sortedFiles = readdirSync(directory).sort();
|
|
17
31
|
sortedFiles.forEach(file => {
|
|
18
32
|
const fullPath = join(directory, file);
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
) {
|
|
24
|
-
files = getAllFiles(base, join(dir, file), files);
|
|
25
|
-
} else if (file.endsWith('.js') && !file.match(/\..*\./)) { // Exclude files with two or more dots
|
|
26
|
-
files.push(join(dir, file));
|
|
33
|
+
const relativePath = join(dir, file).replace(/\\/g, '/');
|
|
34
|
+
if (shouldIgnore(relativePath, ignore)) {
|
|
35
|
+
console.log('Ignoring file:', relativePath);
|
|
36
|
+
return;
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
function objectToString(obj, allComments, indent = 4, level = 1) {
|
|
34
|
-
const spaces = ' '.repeat(indent * level);
|
|
35
|
-
const entries = Object.entries(obj).map(([key, value]) => {
|
|
36
|
-
|
|
37
|
-
if (typeof value === 'object') {
|
|
38
|
-
return `${spaces}${key}: ${objectToString(value, allComments, indent, level + 1)}`;
|
|
38
|
+
if (statSync(fullPath).isDirectory() && file !== '_template') {
|
|
39
|
+
getAllFiles(base, join(dir, file), files, ignore);
|
|
40
|
+
} else if (file.endsWith('.js') && !file.match(/\..*\./)) {
|
|
41
|
+
files.push(relativePath);
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
if(key.startsWith('___')){
|
|
42
|
-
let realKey = key.replace('___', '');
|
|
43
|
-
|
|
44
|
-
// split comment based on lines and insert spaces
|
|
45
|
-
let comment = allComments[value];
|
|
46
|
-
comment = comment.split('\n').map((line) => `${spaces.replace(' ', '')} ${line}`).join('\n');
|
|
47
|
-
|
|
48
|
-
return `${comment}\n${spaces}${realKey}: ${value}`;
|
|
49
|
-
} else {
|
|
50
|
-
return `${spaces}${key}: ${value}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
43
|
});
|
|
54
|
-
|
|
55
|
-
return `{\n${entries.join(',\n')}\n${' '.repeat(indent * (level - 1))}}`;
|
|
56
|
-
|
|
44
|
+
return files;
|
|
57
45
|
}
|
|
58
46
|
|
|
59
|
-
|
|
60
47
|
function extractJSDocComment(filePath) {
|
|
61
48
|
const fileContent = readFileSync(filePath, 'utf8');
|
|
62
49
|
const match = fileContent.match(/\/\*\*([\s\S]*?)\*\//);
|
|
63
50
|
return match ? `/**${match[1]}*/` : '';
|
|
64
51
|
}
|
|
65
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Generates the export file contents.
|
|
55
|
+
*
|
|
56
|
+
* For each file, if includeComments is true, its JSDoc comment is placed
|
|
57
|
+
* immediately above its key in the default export object.
|
|
58
|
+
*/
|
|
59
|
+
function generateExports(src, exportRoots, ignore, includeComments = false) {
|
|
60
|
+
const allFiles = getAllFiles(src, '.', [], ignore);
|
|
61
|
+
let importStatements = '';
|
|
62
|
+
let flatExports = [];
|
|
63
|
+
let nestedExports = {}; // Build a tree for nested exports
|
|
64
|
+
|
|
65
|
+
// Create file info objects
|
|
66
|
+
const fileDataList = allFiles.map(file => {
|
|
67
|
+
const normalizedFile = file.replace(/\\/g, '/');
|
|
68
|
+
const parts = normalizedFile.split('/');
|
|
69
|
+
const fileName = parts.pop();
|
|
70
|
+
const functionName = fileName.replace(/\.js$/, '');
|
|
71
|
+
const namespaceParts = parts;
|
|
72
|
+
const importVarName = namespaceParts.length > 0
|
|
73
|
+
? '_' + [...namespaceParts, functionName].join('_')
|
|
74
|
+
: '_' + functionName;
|
|
75
|
+
const importPath = src + '/' + normalizedFile.replace(/\.js$/, '');
|
|
76
|
+
const jsDocComment = includeComments ? extractJSDocComment(join(src, normalizedFile)) : '';
|
|
77
|
+
return { normalizedFile, parts: namespaceParts, functionName, importVarName, importPath, jsDocComment };
|
|
78
|
+
});
|
|
66
79
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let imports = '';
|
|
73
|
-
let allExports = '';
|
|
74
|
-
let apiObject = {};
|
|
75
|
-
|
|
76
|
-
let allComments = {};
|
|
77
|
-
|
|
80
|
+
// Generate import statements (without comments)
|
|
81
|
+
fileDataList.forEach(({ importVarName, importPath }) => {
|
|
82
|
+
importStatements += `import ${importVarName} from '${importPath}.js';\n`;
|
|
83
|
+
});
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
// Build flat exports and nested export tree.
|
|
86
|
+
fileDataList.forEach(({ parts, functionName, importVarName, jsDocComment }) => {
|
|
87
|
+
if (parts.length === 0) {
|
|
88
|
+
flatExports.push({ functionName, importVarName, jsDocComment });
|
|
89
|
+
} else {
|
|
90
|
+
let current = nestedExports;
|
|
91
|
+
parts.forEach(part => {
|
|
92
|
+
if (!current[part]) {
|
|
93
|
+
current[part] = {};
|
|
94
|
+
}
|
|
95
|
+
current = current[part];
|
|
96
|
+
});
|
|
97
|
+
// Store the leaf export along with its JSDoc comment.
|
|
98
|
+
current[functionName] = { importVarName, jsDocComment };
|
|
99
|
+
}
|
|
100
|
+
});
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
102
|
+
// Generate flat export statements for default export object.
|
|
103
|
+
let flatExportLines = '';
|
|
104
|
+
flatExports.forEach(({ functionName, importVarName, jsDocComment }) => {
|
|
105
|
+
if (exportRoots) {
|
|
106
|
+
if (includeComments && jsDocComment) {
|
|
107
|
+
// Indent each comment line by 4 spaces.
|
|
108
|
+
const indentedComment = jsDocComment.split('\n').map(line => ' ' + line).join('\n');
|
|
109
|
+
flatExportLines += indentedComment + '\n';
|
|
110
|
+
}
|
|
111
|
+
flatExportLines += ` ${functionName}: ${importVarName},\n`;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
93
114
|
|
|
115
|
+
// Recursively generate code for nested namespaces,
|
|
116
|
+
// placing JSDoc comments above each leaf key.
|
|
117
|
+
function generateNamespaceCode(nsObj, indentLevel) {
|
|
118
|
+
const indent = ' '.repeat(indentLevel);
|
|
119
|
+
let lines = ['{'];
|
|
120
|
+
for (const key in nsObj) {
|
|
121
|
+
const value = nsObj[key];
|
|
122
|
+
if (typeof value === 'object' && value.hasOwnProperty('importVarName')) {
|
|
123
|
+
// Leaf node.
|
|
124
|
+
if (includeComments && value.jsDocComment) {
|
|
125
|
+
const indentedComment = value.jsDocComment.split('\n').map(line => indent + ' ' + line).join('\n');
|
|
126
|
+
lines.push(indentedComment);
|
|
127
|
+
}
|
|
128
|
+
lines.push(`${indent} ${key}: ${value.importVarName},`);
|
|
129
|
+
} else {
|
|
130
|
+
// Nested namespace.
|
|
131
|
+
const nestedCode = generateNamespaceCode(value, indentLevel + 1);
|
|
132
|
+
lines.push(`${indent} ${key}: ${nestedCode},`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
lines.push(indent + '}');
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
94
138
|
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
// Generate the default export object.
|
|
140
|
+
let namespaceExportLines = '';
|
|
141
|
+
for (const ns in nestedExports) {
|
|
142
|
+
const nsCode = generateNamespaceCode(nestedExports[ns], 1);
|
|
143
|
+
namespaceExportLines += ` ${ns}: ${nsCode},\n`;
|
|
144
|
+
}
|
|
97
145
|
|
|
98
|
-
|
|
99
|
-
|
|
146
|
+
const defaultExportCode = 'export default {\n' +
|
|
147
|
+
flatExportLines +
|
|
148
|
+
namespaceExportLines +
|
|
149
|
+
'};';
|
|
100
150
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
151
|
+
// Generate individual flat export statements.
|
|
152
|
+
let flatExportStatements = '';
|
|
153
|
+
flatExports.forEach(({ functionName, importVarName }) => {
|
|
154
|
+
if (exportRoots) {
|
|
155
|
+
flatExportStatements += `export { ${importVarName} as ${functionName} };\n`;
|
|
106
156
|
}
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const apiContent = 'export default ' + objectToString(apiObject, allComments) + ';';
|
|
157
|
+
});
|
|
111
158
|
|
|
112
|
-
// Add a header comment
|
|
113
159
|
const headerComment = `/**
|
|
114
160
|
* This file is auto-generated by the build script.
|
|
115
161
|
* It consolidates API functions for use in the application.
|
|
@@ -117,15 +163,21 @@ function generateExports(src) {
|
|
|
117
163
|
*/
|
|
118
164
|
`;
|
|
119
165
|
|
|
120
|
-
return headerComment +
|
|
166
|
+
return headerComment +
|
|
167
|
+
importStatements + '\n' +
|
|
168
|
+
flatExportStatements +
|
|
169
|
+
'\n' +
|
|
170
|
+
defaultExportCode;
|
|
121
171
|
}
|
|
122
172
|
|
|
123
|
-
|
|
124
173
|
async function build({
|
|
125
174
|
src = './src',
|
|
126
|
-
dest = './index.js'
|
|
175
|
+
dest = './index.js',
|
|
176
|
+
exportRoots = true,
|
|
177
|
+
ignore = [],
|
|
178
|
+
includeComments = true
|
|
127
179
|
} = {}) {
|
|
128
|
-
const indexContent = generateExports(src);
|
|
180
|
+
const indexContent = generateExports(src, exportRoots, ignore, includeComments);
|
|
129
181
|
writeFileSync(dest, indexContent);
|
|
130
182
|
return true;
|
|
131
183
|
}
|
package/src/convertBytes.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Converts a given number of bytes into a more readable string format with appropriate units.
|
|
4
|
+
*
|
|
5
|
+
* @param {number} bytes - The number of bytes to convert.
|
|
6
|
+
* @param {number} [precision=2] - The number of decimal places to include in the result.
|
|
7
|
+
* @returns {string} The converted bytes in a string format with appropriate units.
|
|
8
|
+
*/
|
|
1
9
|
export default function convertBytes(bytes, precision = 2) {
|
|
2
10
|
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
3
11
|
bytes = Math.max(bytes, 0);
|
package/src/ignoreMe.js
ADDED
package/test.js
CHANGED