koa-classic-server 1.1.0 β†’ 2.0.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/jest.config.js ADDED
@@ -0,0 +1,18 @@
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ testMatch: [
4
+ '**/__tests__/**/*.test.js'
5
+ ],
6
+ testTimeout: 120000, // 2 minutes for performance tests
7
+ verbose: true,
8
+ collectCoverageFrom: [
9
+ 'index.cjs',
10
+ 'index.mjs'
11
+ ],
12
+ coveragePathIgnorePatterns: [
13
+ '/node_modules/',
14
+ '/customTest/',
15
+ '/benchmark-data/',
16
+ '/scripts/'
17
+ ]
18
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koa-classic-server",
3
- "version": "1.1.0",
4
- "description": "a server in style Apache 2",
3
+ "version": "2.0.0",
4
+ "description": "High-performance Koa middleware for serving static files with Apache-like directory listing, HTTP caching, template engine support, and comprehensive security fixes",
5
5
  "main": "index.cjs",
6
6
  "exports": {
7
7
  "import": "./index.mjs",
@@ -10,18 +10,31 @@
10
10
  "scripts": {
11
11
  "start": "node index.cjs",
12
12
  "test": "jest",
13
+ "test:security": "jest __tests__/security.test.js",
14
+ "test:performance": "jest __tests__/performance.test.js --runInBand",
15
+ "benchmark": "node benchmark.js",
16
+ "benchmark:save": "node benchmark.js --save",
17
+ "benchmark:setup": "node scripts/setup-benchmark.js",
13
18
  "loadConfig": "node ./customTest/loadConfig.util.js"
14
19
  },
15
20
  "keywords": [
16
- "file",
17
- "server"
21
+ "koa",
22
+ "middleware",
23
+ "static",
24
+ "file-server",
25
+ "directory-listing",
26
+ "apache",
27
+ "secure",
28
+ "template-engine"
18
29
  ],
19
30
  "author": "Italo Paesano",
20
31
  "license": "MIT",
21
32
  "dependencies": {
22
- "koa": "^2.13.4"
33
+ "koa": "^3.1.1",
34
+ "mime-types": "^2.1.35"
23
35
  },
24
36
  "devDependencies": {
37
+ "autocannon": "^7.15.0",
25
38
  "inquirer": "^12.4.1",
26
39
  "jest": "^29.7.0",
27
40
  "supertest": "^7.0.0"
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+ # Script per pubblicare koa-classic-server v1.2.0 su npm
3
+
4
+ set -e # Exit on error
5
+
6
+ echo "πŸ“¦ Publishing koa-classic-server v1.2.0 to npm"
7
+ echo ""
8
+
9
+ # Verifica login
10
+ echo "πŸ” Verifying npm login..."
11
+ if ! npm whoami > /dev/null 2>&1; then
12
+ echo "❌ Not logged in to npm. Please run: npm login"
13
+ exit 1
14
+ fi
15
+
16
+ echo "βœ… Logged in as: $(npm whoami)"
17
+ echo ""
18
+
19
+ # Verifica versione
20
+ echo "πŸ“‹ Package info:"
21
+ echo " Name: $(node -p "require('./package.json').name")"
22
+ echo " Version: $(node -p "require('./package.json').version")"
23
+ echo ""
24
+
25
+ # Verifica che non ci siano modifiche non committate
26
+ if [ -n "$(git status --porcelain)" ]; then
27
+ echo "⚠️ Warning: You have uncommitted changes"
28
+ git status --short
29
+ read -p "Continue anyway? (y/N) " -n 1 -r
30
+ echo
31
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
32
+ exit 1
33
+ fi
34
+ fi
35
+
36
+ # Dry run per vedere cosa verrΓ  pubblicato
37
+ echo "πŸ” Files that will be published:"
38
+ npm pack --dry-run | tail -20
39
+ echo ""
40
+
41
+ # Chiedi conferma
42
+ read -p "πŸš€ Publish to npm? (y/N) " -n 1 -r
43
+ echo
44
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
45
+ echo "❌ Publish cancelled"
46
+ exit 0
47
+ fi
48
+
49
+ # Pubblica
50
+ echo "πŸ“€ Publishing to npm..."
51
+ npm publish
52
+
53
+ if [ $? -eq 0 ]; then
54
+ echo ""
55
+ echo "βœ… Successfully published koa-classic-server@1.2.0!"
56
+ echo ""
57
+ echo "πŸ”— View on npm: https://www.npmjs.com/package/koa-classic-server"
58
+ echo ""
59
+ echo "πŸ“ Users can now install with:"
60
+ echo " npm install koa-classic-server@1.2.0"
61
+ echo " npm install koa-classic-server@latest"
62
+ else
63
+ echo "❌ Publish failed"
64
+ exit 1
65
+ fi
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Setup script for performance benchmarks
5
+ * Creates test files and directories for realistic performance testing
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const BENCHMARK_DIR = path.join(__dirname, '../benchmark-data');
12
+
13
+ console.log('πŸ”§ Setting up benchmark environment...\n');
14
+
15
+ // Clean up old benchmark data
16
+ if (fs.existsSync(BENCHMARK_DIR)) {
17
+ console.log('Cleaning old benchmark data...');
18
+ fs.rmSync(BENCHMARK_DIR, { recursive: true, force: true });
19
+ }
20
+
21
+ // Create benchmark directory
22
+ fs.mkdirSync(BENCHMARK_DIR, { recursive: true });
23
+
24
+ // Create small files (1KB each)
25
+ console.log('Creating small files (1KB each)...');
26
+ const smallDir = path.join(BENCHMARK_DIR, 'small-files');
27
+ fs.mkdirSync(smallDir);
28
+ for (let i = 1; i <= 100; i++) {
29
+ const content = 'X'.repeat(1024); // 1KB
30
+ fs.writeFileSync(path.join(smallDir, `file-${i}.txt`), content);
31
+ }
32
+ console.log(`βœ“ Created 100 small files (1KB each) = 100KB total`);
33
+
34
+ // Create medium files (100KB each)
35
+ console.log('Creating medium files (100KB each)...');
36
+ const mediumDir = path.join(BENCHMARK_DIR, 'medium-files');
37
+ fs.mkdirSync(mediumDir);
38
+ for (let i = 1; i <= 50; i++) {
39
+ const content = 'X'.repeat(100 * 1024); // 100KB
40
+ fs.writeFileSync(path.join(mediumDir, `file-${i}.txt`), content);
41
+ }
42
+ console.log(`βœ“ Created 50 medium files (100KB each) = 5MB total`);
43
+
44
+ // Create large files (1MB each)
45
+ console.log('Creating large files (1MB each)...');
46
+ const largeDir = path.join(BENCHMARK_DIR, 'large-files');
47
+ fs.mkdirSync(largeDir);
48
+ for (let i = 1; i <= 10; i++) {
49
+ const content = 'X'.repeat(1024 * 1024); // 1MB
50
+ fs.writeFileSync(path.join(largeDir, `file-${i}.txt`), content);
51
+ }
52
+ console.log(`βœ“ Created 10 large files (1MB each) = 10MB total`);
53
+
54
+ // Create directory with many files for listing test
55
+ console.log('Creating large directory (1000 files)...');
56
+ const largeDirListing = path.join(BENCHMARK_DIR, 'large-directory');
57
+ fs.mkdirSync(largeDirListing);
58
+ for (let i = 1; i <= 1000; i++) {
59
+ const content = `File number ${i}\n`;
60
+ fs.writeFileSync(path.join(largeDirListing, `item-${String(i).padStart(4, '0')}.txt`), content);
61
+ }
62
+ console.log(`βœ“ Created directory with 1000 files`);
63
+
64
+ // Create directory with very many files (10,000) for stress test
65
+ console.log('Creating very large directory (10,000 files) - this may take a while...');
66
+ const veryLargeDirListing = path.join(BENCHMARK_DIR, 'very-large-directory');
67
+ fs.mkdirSync(veryLargeDirListing);
68
+ for (let i = 1; i <= 10000; i++) {
69
+ const content = `File number ${i}\n`;
70
+ fs.writeFileSync(path.join(veryLargeDirListing, `item-${String(i).padStart(5, '0')}.txt`), content);
71
+ if (i % 1000 === 0) {
72
+ process.stdout.write(` Progress: ${i}/10000 files created...\r`);
73
+ }
74
+ }
75
+ console.log(`βœ“ Created directory with 10,000 files `);
76
+
77
+ // Create HTML file for caching test
78
+ console.log('Creating HTML files for caching test...');
79
+ const htmlContent = `<!DOCTYPE html>
80
+ <html>
81
+ <head>
82
+ <meta charset="UTF-8">
83
+ <title>Benchmark Test Page</title>
84
+ <style>
85
+ body { font-family: Arial, sans-serif; margin: 40px; }
86
+ h1 { color: #333; }
87
+ p { line-height: 1.6; }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <h1>Benchmark Test Page</h1>
92
+ <p>${'Lorem ipsum dolor sit amet. '.repeat(100)}</p>
93
+ </body>
94
+ </html>`;
95
+ fs.writeFileSync(path.join(BENCHMARK_DIR, 'test.html'), htmlContent);
96
+ console.log(`βœ“ Created HTML file for caching test`);
97
+
98
+ // Create CSS file
99
+ const cssContent = `
100
+ body {
101
+ margin: 0;
102
+ padding: 20px;
103
+ font-family: system-ui, -apple-system, sans-serif;
104
+ }
105
+ ${'h1 { color: #333; }\n'.repeat(50)}
106
+ `.trim();
107
+ fs.writeFileSync(path.join(BENCHMARK_DIR, 'style.css'), cssContent);
108
+ console.log(`βœ“ Created CSS file`);
109
+
110
+ // Create JS file
111
+ const jsContent = `
112
+ function benchmark() {
113
+ console.log('Benchmark test');
114
+ ${'console.log("test");\n'.repeat(100)}
115
+ }
116
+ `.trim();
117
+ fs.writeFileSync(path.join(BENCHMARK_DIR, 'script.js'), jsContent);
118
+ console.log(`βœ“ Created JS file`);
119
+
120
+ // Create nested directory structure
121
+ console.log('Creating nested directory structure...');
122
+ const nestedBase = path.join(BENCHMARK_DIR, 'nested');
123
+ fs.mkdirSync(nestedBase);
124
+ for (let depth = 1; depth <= 5; depth++) {
125
+ const dirPath = path.join(nestedBase, ...Array(depth).fill('level'));
126
+ fs.mkdirSync(dirPath, { recursive: true });
127
+ for (let i = 1; i <= 10; i++) {
128
+ fs.writeFileSync(
129
+ path.join(dirPath, `file-depth${depth}-${i}.txt`),
130
+ `Depth ${depth}, File ${i}\n`
131
+ );
132
+ }
133
+ }
134
+ console.log(`βœ“ Created nested directory structure (5 levels deep)`);
135
+
136
+ // Create .gitignore for benchmark-data
137
+ const gitignorePath = path.join(BENCHMARK_DIR, '.gitignore');
138
+ fs.writeFileSync(gitignorePath, '*\n');
139
+ console.log(`βœ“ Created .gitignore to exclude benchmark data from git`);
140
+
141
+ // Summary
142
+ console.log('\nβœ… Benchmark environment setup complete!\n');
143
+ console.log('Directory structure:');
144
+ console.log(' benchmark-data/');
145
+ console.log(' β”œβ”€β”€ small-files/ (100 files Γ— 1KB = 100KB)');
146
+ console.log(' β”œβ”€β”€ medium-files/ (50 files Γ— 100KB = 5MB)');
147
+ console.log(' β”œβ”€β”€ large-files/ (10 files Γ— 1MB = 10MB)');
148
+ console.log(' β”œβ”€β”€ large-directory/ (1,000 files for listing test)');
149
+ console.log(' β”œβ”€β”€ very-large-directory/ (10,000 files for stress test)');
150
+ console.log(' β”œβ”€β”€ nested/ (5 levels deep, 10 files each)');
151
+ console.log(' β”œβ”€β”€ test.html (HTML for caching test)');
152
+ console.log(' β”œβ”€β”€ style.css (CSS file)');
153
+ console.log(' └── script.js (JS file)');
154
+
155
+ const stats = getDirSize(BENCHMARK_DIR);
156
+ console.log(`\nTotal size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
157
+ console.log(`Total files: ${stats.files}`);
158
+
159
+ function getDirSize(dir) {
160
+ let totalSize = 0;
161
+ let totalFiles = 0;
162
+
163
+ function traverse(currentPath) {
164
+ const items = fs.readdirSync(currentPath, { withFileTypes: true });
165
+ for (const item of items) {
166
+ const fullPath = path.join(currentPath, item.name);
167
+ if (item.isDirectory()) {
168
+ traverse(fullPath);
169
+ } else {
170
+ totalSize += fs.statSync(fullPath).size;
171
+ totalFiles++;
172
+ }
173
+ }
174
+ }
175
+
176
+ traverse(dir);
177
+ return { size: totalSize, files: totalFiles };
178
+ }
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Quick test: RegExp index option
5
+ */
6
+
7
+ const Koa = require('koa');
8
+ const koaClassicServer = require('./index.cjs');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const http = require('http');
12
+
13
+ // Crea directory di test
14
+ const testDir = path.join(__dirname, 'test-regex-temp');
15
+ if (fs.existsSync(testDir)) {
16
+ fs.readdirSync(testDir).forEach(file => {
17
+ fs.unlinkSync(path.join(testDir, file));
18
+ });
19
+ fs.rmdirSync(testDir);
20
+ }
21
+ fs.mkdirSync(testDir);
22
+
23
+ // Test case 1: Case-insensitive matching
24
+ console.log('\nπŸ“‹ TEST 1: Case-insensitive /index\\.html/i\n');
25
+ console.log('File creati:');
26
+ console.log(' - INDEX.HTML (maiuscolo)');
27
+ console.log(' - other.txt\n');
28
+
29
+ fs.writeFileSync(path.join(testDir, 'INDEX.HTML'), '<h1>INDEX.HTML (MAIUSCOLO)</h1>');
30
+ fs.writeFileSync(path.join(testDir, 'other.txt'), 'altro file');
31
+
32
+ const app1 = new Koa();
33
+ app1.use(koaClassicServer(testDir, {
34
+ index: [/index\.html/i] // ← REGEXP CASE-INSENSITIVE!
35
+ }));
36
+
37
+ const server1 = app1.listen(9001);
38
+
39
+ // Test la richiesta
40
+ setTimeout(() => {
41
+ http.get('http://localhost:9001/', (res) => {
42
+ let data = '';
43
+ res.on('data', chunk => data += chunk);
44
+ res.on('end', () => {
45
+ if (data.includes('INDEX.HTML (MAIUSCOLO)')) {
46
+ console.log('βœ… SUCCESS: Il server ha trovato INDEX.HTML usando /index\\.html/i');
47
+ console.log(' Contenuto ricevuto: ' + data.trim());
48
+ } else {
49
+ console.log('❌ FAIL: Pattern non ha matchato');
50
+ console.log(' Contenuto ricevuto: ' + data);
51
+ }
52
+
53
+ server1.close();
54
+
55
+ // Test case 2
56
+ runTest2();
57
+ });
58
+ });
59
+ }, 100);
60
+
61
+ function runTest2() {
62
+ // Pulisci directory
63
+ fs.readdirSync(testDir).forEach(file => {
64
+ fs.unlinkSync(path.join(testDir, file));
65
+ });
66
+
67
+ console.log('\nπŸ“‹ TEST 2: Multi-extension /index\\.(html|htm)/i\n');
68
+ console.log('File creati:');
69
+ console.log(' - Index.HTM (mixed case, estensione .htm)');
70
+ console.log(' - other.html\n');
71
+
72
+ fs.writeFileSync(path.join(testDir, 'Index.HTM'), '<h1>Index.HTM (mixed case)</h1>');
73
+ fs.writeFileSync(path.join(testDir, 'other.html'), '<h1>altro</h1>');
74
+
75
+ const app2 = new Koa();
76
+ app2.use(koaClassicServer(testDir, {
77
+ index: [/index\.(html|htm)/i] // ← REGEXP con (html|htm)!
78
+ }));
79
+
80
+ const server2 = app2.listen(9002);
81
+
82
+ setTimeout(() => {
83
+ http.get('http://localhost:9002/', (res) => {
84
+ let data = '';
85
+ res.on('data', chunk => data += chunk);
86
+ res.on('end', () => {
87
+ if (data.includes('Index.HTM (mixed case)')) {
88
+ console.log('βœ… SUCCESS: Il server ha trovato Index.HTM usando /index\\.(html|htm)/i');
89
+ console.log(' Contenuto ricevuto: ' + data.trim());
90
+ } else {
91
+ console.log('❌ FAIL: Pattern non ha matchato');
92
+ console.log(' Contenuto ricevuto: ' + data);
93
+ }
94
+
95
+ server2.close();
96
+
97
+ // Test case 3
98
+ runTest3();
99
+ });
100
+ });
101
+ }, 100);
102
+ }
103
+
104
+ function runTest3() {
105
+ // Pulisci directory
106
+ fs.readdirSync(testDir).forEach(file => {
107
+ fs.unlinkSync(path.join(testDir, file));
108
+ });
109
+
110
+ console.log('\nπŸ“‹ TEST 3: Array con prioritΓ  [/index\\.html/i, /default\\.html/i]\n');
111
+ console.log('File creati:');
112
+ console.log(' - DEFAULT.HTML (maiuscolo)');
113
+ console.log(' - other.txt\n');
114
+ console.log('Nota: index.html NON esiste, dovrebbe trovare default.html\n');
115
+
116
+ fs.writeFileSync(path.join(testDir, 'DEFAULT.HTML'), '<h1>DEFAULT.HTML (fallback)</h1>');
117
+ fs.writeFileSync(path.join(testDir, 'other.txt'), 'altro');
118
+
119
+ const app3 = new Koa();
120
+ app3.use(koaClassicServer(testDir, {
121
+ index: [
122
+ /index\.html/i, // Prima cerca index.html (NON esiste)
123
+ /default\.html/i // Poi cerca default.html (ESISTE!)
124
+ ]
125
+ }));
126
+
127
+ const server3 = app3.listen(9003);
128
+
129
+ setTimeout(() => {
130
+ http.get('http://localhost:9003/', (res) => {
131
+ let data = '';
132
+ res.on('data', chunk => data += chunk);
133
+ res.on('end', () => {
134
+ if (data.includes('DEFAULT.HTML (fallback)')) {
135
+ console.log('βœ… SUCCESS: Il server ha fatto fallback a DEFAULT.HTML');
136
+ console.log(' Contenuto ricevuto: ' + data.trim());
137
+ } else {
138
+ console.log('❌ FAIL: Fallback non ha funzionato');
139
+ console.log(' Contenuto ricevuto: ' + data);
140
+ }
141
+
142
+ server3.close();
143
+
144
+ // Cleanup finale
145
+ cleanup();
146
+ });
147
+ });
148
+ }, 100);
149
+ }
150
+
151
+ function cleanup() {
152
+ console.log('\n🧹 Pulizia...\n');
153
+ fs.readdirSync(testDir).forEach(file => {
154
+ fs.unlinkSync(path.join(testDir, file));
155
+ });
156
+ fs.rmdirSync(testDir);
157
+ console.log('βœ… Tutti i test completati!\n');
158
+ }