koa-classic-server 2.0.0 → 2.1.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.
Files changed (45) hide show
  1. package/README.md +31 -17
  2. package/__tests__/directory-sorting-links.test.js +135 -0
  3. package/__tests__/ejs.test.js +299 -0
  4. package/__tests__/performance.test.js +75 -6
  5. package/__tests__/publicWwwTest/ejs-templates/complex.ejs +56 -0
  6. package/__tests__/publicWwwTest/ejs-templates/index.ejs +30 -0
  7. package/__tests__/publicWwwTest/ejs-templates/simple.ejs +13 -0
  8. package/__tests__/publicWwwTest/ejs-templates/with-conditional.ejs +28 -0
  9. package/__tests__/publicWwwTest/ejs-templates/with-escaping.ejs +26 -0
  10. package/__tests__/publicWwwTest/ejs-templates/with-loop.ejs +16 -0
  11. package/{scripts → __tests__}/setup-benchmark.js +1 -1
  12. package/docs/CODE_REVIEW.md +298 -0
  13. package/docs/FLOW_DIAGRAM.md +952 -0
  14. package/docs/template-engine/TEMPLATE_ENGINE_GUIDE.md +1734 -0
  15. package/docs/template-engine/esempi-incrementali.js +192 -0
  16. package/docs/template-engine/examples/esempio1-nessun-dato.ejs +12 -0
  17. package/docs/template-engine/examples/esempio2-una-variabile.ejs +11 -0
  18. package/docs/template-engine/examples/esempio3-piu-variabili.ejs +15 -0
  19. package/docs/template-engine/examples/esempio4-condizionale.ejs +18 -0
  20. package/docs/template-engine/examples/esempio5-loop.ejs +18 -0
  21. package/docs/template-engine/examples/index-esempi.html +181 -0
  22. package/docs/template-engine/examples/index.html +40 -0
  23. package/docs/template-engine/examples/test.ejs +64 -0
  24. package/index.cjs +186 -35
  25. package/package.json +9 -6
  26. package/CREATE_RELEASE.sh +0 -53
  27. package/publish-to-npm.sh +0 -65
  28. /package/{benchmark-results-baseline-v1.2.0.txt → __tests__/benchmark-results-baseline-v1.2.0.txt} +0 -0
  29. /package/{benchmark-results-optimized-v2.0.0.txt → __tests__/benchmark-results-optimized-v2.0.0.txt} +0 -0
  30. /package/{benchmark.js → __tests__/benchmark.js} +0 -0
  31. /package/{customTest → __tests__/customTest}/README.md +0 -0
  32. /package/{customTest → __tests__/customTest}/loadConfig.util.js +0 -0
  33. /package/{customTest → __tests__/customTest}/serversToLoad.util.js +0 -0
  34. /package/{demo-regex-index.js → __tests__/demo-regex-index.js} +0 -0
  35. /package/{test-regex-quick.js → __tests__/test-regex-quick.js} +0 -0
  36. /package/{BENCHMARKS.md → docs/BENCHMARKS.md} +0 -0
  37. /package/{CHANGELOG.md → docs/CHANGELOG.md} +0 -0
  38. /package/{DEBUG_REPORT.md → docs/DEBUG_REPORT.md} +0 -0
  39. /package/{DOCUMENTATION.md → docs/DOCUMENTATION.md} +0 -0
  40. /package/{EXAMPLES_INDEX_OPTION.md → docs/EXAMPLES_INDEX_OPTION.md} +0 -0
  41. /package/{INDEX_OPTION_PRIORITY.md → docs/INDEX_OPTION_PRIORITY.md} +0 -0
  42. /package/{OPTIMIZATION_HTTP_CACHING.md → docs/OPTIMIZATION_HTTP_CACHING.md} +0 -0
  43. /package/{PERFORMANCE_ANALYSIS.md → docs/PERFORMANCE_ANALYSIS.md} +0 -0
  44. /package/{PERFORMANCE_COMPARISON.md → docs/PERFORMANCE_COMPARISON.md} +0 -0
  45. /package/{noteExports.md → docs/noteExports.md} +0 -0
package/index.cjs CHANGED
@@ -65,10 +65,10 @@ module.exports = function koaClassicServer(
65
65
  options.template = opts.template || {};
66
66
 
67
67
  options.method = Array.isArray(options.method) ? options.method : ['GET'];
68
- options.showDirContents = typeof options.showDirContents == 'boolean' ? options.showDirContents : true;
68
+ options.showDirContents = typeof options.showDirContents === 'boolean' ? options.showDirContents : true;
69
69
 
70
70
  // Normalize index option to array format
71
- if (typeof options.index == 'string') {
71
+ if (typeof options.index === 'string') {
72
72
  // DEPRECATION WARNING: String format is deprecated
73
73
  if (options.index) {
74
74
  console.warn(
@@ -91,14 +91,14 @@ module.exports = function koaClassicServer(
91
91
  options.index = [];
92
92
  }
93
93
 
94
- options.urlPrefix = typeof options.urlPrefix == 'string' ? options.urlPrefix : "";
94
+ options.urlPrefix = typeof options.urlPrefix === 'string' ? options.urlPrefix : "";
95
95
  options.urlsReserved = Array.isArray(options.urlsReserved) ? options.urlsReserved : [];
96
- options.template.render = (options.template.render == undefined || typeof options.template.render == 'function') ? options.template.render : undefined;
96
+ options.template.render = (options.template.render === undefined || typeof options.template.render === 'function') ? options.template.render : undefined;
97
97
  options.template.ext = Array.isArray(options.template.ext) ? options.template.ext : [];
98
98
 
99
99
  // OPTIMIZATION: HTTP Caching options
100
- options.cacheMaxAge = typeof options.cacheMaxAge == 'number' && options.cacheMaxAge >= 0 ? options.cacheMaxAge : 3600;
101
- options.enableCaching = typeof options.enableCaching == 'boolean' ? options.enableCaching : true;
100
+ options.cacheMaxAge = typeof options.cacheMaxAge === 'number' && options.cacheMaxAge >= 0 ? options.cacheMaxAge : 3600;
101
+ options.enableCaching = typeof options.enableCaching === 'boolean' ? options.enableCaching : true;
102
102
 
103
103
  return async (ctx, next) => {
104
104
  // Check if method is allowed
@@ -109,7 +109,7 @@ module.exports = function koaClassicServer(
109
109
 
110
110
  // Normalize URL (remove trailing slash)
111
111
  let pageHref = '';
112
- if (ctx.href.charAt(ctx.href.length - 1) == '/') {
112
+ if (ctx.href.charAt(ctx.href.length - 1) === '/') {
113
113
  pageHref = new URL(ctx.href.slice(0, -1));
114
114
  } else {
115
115
  pageHref = new URL(ctx.href);
@@ -120,7 +120,7 @@ module.exports = function koaClassicServer(
120
120
  const a_urlPrefix = options.urlPrefix.split("/");
121
121
 
122
122
  for (const key in a_urlPrefix) {
123
- if (a_urlPrefix[key] != a_pathname[key]) {
123
+ if (a_urlPrefix[key] !== a_pathname[key]) {
124
124
  await next();
125
125
  return;
126
126
  }
@@ -128,7 +128,7 @@ module.exports = function koaClassicServer(
128
128
 
129
129
  // Create pageHrefOutPrefix without URL prefix
130
130
  let pageHrefOutPrefix = pageHref;
131
- if (options.urlPrefix != "") {
131
+ if (options.urlPrefix !== "") {
132
132
  let a_pathnameOutPrefix = a_pathname.slice(a_urlPrefix.length);
133
133
  let s_pathnameOutPrefix = a_pathnameOutPrefix.join("/");
134
134
  let hrefOutPrefix = pageHref.origin + '/' + s_pathnameOutPrefix;
@@ -139,7 +139,7 @@ module.exports = function koaClassicServer(
139
139
  if (Array.isArray(options.urlsReserved) && options.urlsReserved.length > 0) {
140
140
  const a_pathnameOutPrefix = pageHrefOutPrefix.pathname.split("/");
141
141
  for (const value of options.urlsReserved) {
142
- if (a_pathnameOutPrefix[1] == value.substring(1)) {
142
+ if (a_pathnameOutPrefix[1] === value.substring(1)) {
143
143
  await next();
144
144
  return;
145
145
  }
@@ -149,7 +149,7 @@ module.exports = function koaClassicServer(
149
149
  // Path Traversal Protection
150
150
  // Construct safe file path
151
151
  let requestedPath = "";
152
- if (pageHrefOutPrefix.pathname == "/") {
152
+ if (pageHrefOutPrefix.pathname === "/") {
153
153
  requestedPath = "";
154
154
  } else {
155
155
  requestedPath = decodeURIComponent(pageHrefOutPrefix.pathname);
@@ -193,7 +193,7 @@ module.exports = function koaClassicServer(
193
193
  }
194
194
 
195
195
  // No index file found, show directory listing
196
- ctx.body = await show_dir(toOpen);
196
+ ctx.body = await show_dir(toOpen, ctx);
197
197
  } else {
198
198
  // Directory listing disabled
199
199
  ctx.status = 404;
@@ -386,8 +386,20 @@ module.exports = function koaClassicServer(
386
386
  ctx.body = src;
387
387
  }
388
388
 
389
+ // Helper function to format file size in human-readable format
390
+ function formatSize(bytes) {
391
+ if (bytes === 0) return '0 B';
392
+ if (bytes === undefined || bytes === null) return '-';
393
+
394
+ const k = 1024;
395
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
396
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
397
+
398
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
399
+ }
400
+
389
401
  // OPTIMIZATION: show_dir is now async and uses array join instead of string concatenation
390
- async function show_dir(toOpen) {
402
+ async function show_dir(toOpen, ctx) {
391
403
  let dir;
392
404
  try {
393
405
  // OPTIMIZATION: Use async readdir (non-blocking)
@@ -409,61 +421,158 @@ module.exports = function koaClassicServer(
409
421
  `;
410
422
  }
411
423
 
424
+ // Get sorting parameters from query string
425
+ const sortBy = ctx.query.sort || 'name';
426
+ const sortOrder = ctx.query.order || 'asc';
427
+
428
+ // Build base URL for sorting links (without query params)
429
+ const baseUrl = pageHrefOutPrefix.pathname;
430
+
431
+ // Helper to create sorting URL
432
+ function getSortUrl(column) {
433
+ let newOrder = 'asc';
434
+ if (sortBy === column && sortOrder === 'asc') {
435
+ newOrder = 'desc';
436
+ }
437
+ return `${baseUrl}?sort=${column}&order=${newOrder}`;
438
+ }
439
+
440
+ // Helper to get sort indicator
441
+ function getSortIndicator(column) {
442
+ if (sortBy === column) {
443
+ return sortOrder === 'asc' ? ' ↑' : ' ↓';
444
+ }
445
+ return '';
446
+ }
447
+
412
448
  // OPTIMIZATION: Use array + join instead of string concatenation
413
449
  // This reduces memory allocation from O(n²) to O(n)
414
450
  const parts = [];
415
451
  parts.push("<table>");
452
+ parts.push("<thead>");
453
+ parts.push("<tr>");
454
+ parts.push(`<th><a href="${escapeHtml(getSortUrl('name'))}">Name${getSortIndicator('name')}</a></th>`);
455
+ parts.push(`<th><a href="${escapeHtml(getSortUrl('type'))}">Type${getSortIndicator('type')}</a></th>`);
456
+ parts.push(`<th><a href="${escapeHtml(getSortUrl('size'))}">Size${getSortIndicator('size')}</a></th>`);
457
+ parts.push("</tr>");
458
+ parts.push("</thead>");
459
+ parts.push("<tbody>");
416
460
 
417
461
  // Parent directory link
418
- if (pageHrefOutPrefix.origin + "/" != pageHrefOutPrefix.href) {
419
- const a_pD = pageHref.href.split("/");
462
+ const currentPath = pageHref.origin + pageHref.pathname;
463
+ if (currentPath !== pageHrefOutPrefix.origin + "/") {
464
+ // Build parent directory URL without query parameters
465
+ const a_pD = currentPath.split("/");
420
466
  a_pD.pop();
421
467
  const parentDirectory = a_pD.join("/");
422
468
  // Escape HTML to prevent XSS
423
- parts.push(`<tr><td><a href="${escapeHtml(parentDirectory)}"><b>.. Parent Directory</b></a></td><td>DIR</td></tr>`);
469
+ parts.push(`<tr><td><a href="${escapeHtml(parentDirectory)}"><b>.. Parent Directory</b></a></td><td>DIR</td><td>-</td></tr>`);
424
470
  }
425
471
 
426
- if (dir.length == 0) {
427
- parts.push(`<tr><td>empty folder</td><td></td></tr>`);
472
+ if (dir.length === 0) {
473
+ parts.push(`<tr><td>empty folder</td><td></td><td></td></tr>`);
428
474
  } else {
429
475
  let a_sy = Object.getOwnPropertySymbols(dir[0]);
430
476
  const sy_type = a_sy[0];
431
477
 
478
+ // Collect all items data first (for sorting)
479
+ const items = [];
432
480
  for (const item of dir) {
433
481
  const s_name = item.name.toString();
434
482
  const type = item[sy_type];
435
483
 
436
- let rowStart = '';
437
- if (type == 1) {
438
- // File
439
- rowStart = `<tr><td> FILE `;
440
- } else if (type == 2 || type == 3) {
441
- // Directory or symbolic link
442
- rowStart = `<tr><td>`;
443
- } else {
484
+ if (type !== 1 && type !== 2 && type !== 3) {
444
485
  console.error("Unknown file type:", type);
445
- continue; // Skip unknown types instead of throwing
486
+ continue;
446
487
  }
447
488
 
448
489
  const itemPath = path.join(toOpen, s_name);
449
490
  let itemUri = "";
450
- if (pageHref.href == pageHref.origin + options.urlPrefix + "/") {
491
+ // Build item URI without query parameters
492
+ const baseUrl = pageHref.origin + pageHref.pathname;
493
+ if (baseUrl === pageHref.origin + options.urlPrefix + "/" || baseUrl === pageHref.origin + options.urlPrefix) {
451
494
  itemUri = `${pageHref.origin + options.urlPrefix}/${encodeURIComponent(s_name)}`;
452
495
  } else {
453
- itemUri = `${pageHref.href}/${encodeURIComponent(s_name)}`;
496
+ itemUri = `${baseUrl}/${encodeURIComponent(s_name)}`;
454
497
  }
455
498
 
456
- // Check if this is a reserved directory
457
- if (pageHrefOutPrefix.pathname == '/' && options.urlsReserved.includes('/' + s_name) && (type == 2 || type == 3)) {
458
- parts.push(`${rowStart} ${escapeHtml(s_name)}</td> <td> DIR BUT RESERVED</td></tr>`);
499
+ // Get file size
500
+ let sizeStr = '-';
501
+ let sizeBytes = 0;
502
+ try {
503
+ const itemStat = await fs.promises.stat(itemPath);
504
+ if (type === 1) {
505
+ sizeBytes = itemStat.size;
506
+ sizeStr = formatSize(sizeBytes);
507
+ } else {
508
+ sizeStr = '-';
509
+ }
510
+ } catch (error) {
511
+ sizeStr = '-';
512
+ }
513
+
514
+ const mimeType = type === 2 ? "DIR" : (mime.lookup(itemPath) || 'unknown');
515
+ const isReserved = pageHrefOutPrefix.pathname === '/' && options.urlsReserved.includes('/' + s_name) && (type === 2 || type === 3);
516
+
517
+ items.push({
518
+ name: s_name,
519
+ type: type,
520
+ mimeType: mimeType,
521
+ sizeStr: sizeStr,
522
+ sizeBytes: sizeBytes,
523
+ itemUri: itemUri,
524
+ isReserved: isReserved
525
+ });
526
+ }
527
+
528
+ // Sort items based on query parameters
529
+ items.sort((a, b) => {
530
+ let comparison = 0;
531
+
532
+ if (sortBy === 'name') {
533
+ comparison = a.name.localeCompare(b.name);
534
+ } else if (sortBy === 'type') {
535
+ // Sort directories first, then by mime type
536
+ if (a.type === 2 && b.type !== 2) {
537
+ comparison = -1;
538
+ } else if (a.type !== 2 && b.type === 2) {
539
+ comparison = 1;
540
+ } else {
541
+ comparison = a.mimeType.localeCompare(b.mimeType);
542
+ }
543
+ } else if (sortBy === 'size') {
544
+ // Directories always at top when sorting by size
545
+ if (a.type === 2 && b.type !== 2) {
546
+ comparison = -1;
547
+ } else if (a.type !== 2 && b.type === 2) {
548
+ comparison = 1;
549
+ } else {
550
+ comparison = a.sizeBytes - b.sizeBytes;
551
+ }
552
+ }
553
+
554
+ // Apply sort order (asc/desc)
555
+ return sortOrder === 'desc' ? -comparison : comparison;
556
+ });
557
+
558
+ // Generate HTML for sorted items
559
+ for (const item of items) {
560
+ let rowStart = '';
561
+ if (item.type === 1) {
562
+ rowStart = `<tr><td> FILE `;
459
563
  } else {
460
- // Escape HTML to prevent XSS in filenames
461
- const mimeType = type == 2 ? "DIR" : (mime.lookup(itemPath) || 'unknown');
462
- parts.push(`${rowStart} <a href="${escapeHtml(itemUri)}">${escapeHtml(s_name)}</a> </td> <td> ${escapeHtml(mimeType)} </td></tr>`);
564
+ rowStart = `<tr><td>`;
565
+ }
566
+
567
+ if (item.isReserved) {
568
+ parts.push(`${rowStart} ${escapeHtml(item.name)}</td> <td> DIR BUT RESERVED</td><td>${item.sizeStr}</td></tr>`);
569
+ } else {
570
+ parts.push(`${rowStart} <a href="${escapeHtml(item.itemUri)}">${escapeHtml(item.name)}</a> </td> <td> ${escapeHtml(item.mimeType)} </td><td>${item.sizeStr}</td></tr>`);
463
571
  }
464
572
  }
465
573
  }
466
574
 
575
+ parts.push("</tbody>");
467
576
  parts.push("</table>");
468
577
 
469
578
  // OPTIMIZATION: Single join operation instead of multiple concatenations
@@ -477,6 +586,48 @@ module.exports = function koaClassicServer(
477
586
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
478
587
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
479
588
  <title>Index of ${escapeHtml(pageHrefOutPrefix.pathname)}</title>
589
+ <style>
590
+ body {
591
+ font-family: Arial, sans-serif;
592
+ margin: 20px;
593
+ }
594
+ h1 {
595
+ border-bottom: 1px solid #ddd;
596
+ padding-bottom: 10px;
597
+ }
598
+ table {
599
+ border-collapse: collapse;
600
+ width: 100%;
601
+ max-width: 800px;
602
+ }
603
+ thead {
604
+ background-color: #f5f5f5;
605
+ border-bottom: 2px solid #ddd;
606
+ }
607
+ th {
608
+ text-align: left;
609
+ padding: 10px;
610
+ font-weight: bold;
611
+ border-bottom: 2px solid #ddd;
612
+ }
613
+ td {
614
+ padding: 8px 10px;
615
+ border-bottom: 1px solid #eee;
616
+ }
617
+ tr:hover {
618
+ background-color: #f9f9f9;
619
+ }
620
+ a {
621
+ color: #0066cc;
622
+ text-decoration: none;
623
+ }
624
+ a:hover {
625
+ text-decoration: underline;
626
+ }
627
+ th:nth-child(1), td:nth-child(1) { width: 50%; }
628
+ th:nth-child(2), td:nth-child(2) { width: 30%; }
629
+ th:nth-child(3), td:nth-child(3) { width: 20%; text-align: right; }
630
+ </style>
480
631
  </head>
481
632
  <body>
482
633
  <h1>Index of ${escapeHtml(pageHrefOutPrefix.pathname)}</h1>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koa-classic-server",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
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": {
@@ -12,10 +12,10 @@
12
12
  "test": "jest",
13
13
  "test:security": "jest __tests__/security.test.js",
14
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",
18
- "loadConfig": "node ./customTest/loadConfig.util.js"
15
+ "benchmark": "node __tests__/benchmark.js",
16
+ "benchmark:save": "node __tests__/benchmark.js --save",
17
+ "benchmark:setup": "node __tests__/setup-benchmark.js",
18
+ "loadConfig": "node __tests__/customTest/loadConfig.util.js"
19
19
  },
20
20
  "keywords": [
21
21
  "koa",
@@ -30,11 +30,14 @@
30
30
  "author": "Italo Paesano",
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "koa": "^3.1.1",
34
33
  "mime-types": "^2.1.35"
35
34
  },
35
+ "peerDependencies": {
36
+ "koa": "^2.0.0 || ^3.0.0"
37
+ },
36
38
  "devDependencies": {
37
39
  "autocannon": "^7.15.0",
40
+ "ejs": "^3.1.10",
38
41
  "inquirer": "^12.4.1",
39
42
  "jest": "^29.7.0",
40
43
  "supertest": "^7.0.0"
package/CREATE_RELEASE.sh DELETED
@@ -1,53 +0,0 @@
1
- #!/bin/bash
2
- # Script per creare la release v1.2.0
3
-
4
- echo "🏷️ Creazione Release v1.2.0..."
5
-
6
- # Assicurati di essere sul branch principale
7
- echo "📥 Fetch delle ultime modifiche..."
8
- git fetch origin
9
-
10
- # Checkout al branch principale (prova main, poi master)
11
- if git show-ref --verify --quiet refs/remotes/origin/main; then
12
- echo "✅ Checkout a main..."
13
- git checkout main
14
- git pull origin main
15
- elif git show-ref --verify --quiet refs/remotes/origin/master; then
16
- echo "✅ Checkout a master..."
17
- git checkout master
18
- git pull origin master
19
- else
20
- echo "❌ Branch principale non trovato. Usa l'interfaccia GitHub."
21
- exit 1
22
- fi
23
-
24
- # Crea il tag
25
- echo "🏷️ Creazione tag v1.2.0..."
26
- git tag -a v1.2.0 -m "Release v1.2.0 - Critical Security Update
27
-
28
- ⚠️ CRITICAL SECURITY FIXES
29
-
30
- Security Fixes:
31
- - Fixed Path Traversal Vulnerability (CRITICAL)
32
- - Fixed Template Rendering Crash (CRITICAL)
33
-
34
- Bug Fixes:
35
- - HTTP Status Code 404 (HIGH)
36
- - Race Condition File Access (HIGH)
37
- - File Extension Extraction (HIGH)
38
- - Directory Read Errors (MEDIUM)
39
- - Content-Disposition Header (MEDIUM)
40
- - Code Quality & XSS Protection
41
-
42
- Statistics:
43
- - Security vulnerabilities: 2 critical fixed
44
- - Bugs fixed: 6
45
- - Tests: 71 passing
46
- - Documentation: 2000+ lines added"
47
-
48
- # Push del tag
49
- echo "📤 Push del tag a GitHub..."
50
- git push origin v1.2.0
51
-
52
- echo "✅ Tag creato! Ora vai su GitHub per creare la release:"
53
- echo " https://github.com/italopaesano/koa-classic-server/releases/new?tag=v1.2.0"
package/publish-to-npm.sh DELETED
@@ -1,65 +0,0 @@
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes