@rip-lang/server 1.3.76 → 1.3.78
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/default.rip +137 -0
- package/package.json +3 -2
- package/server.rip +10 -6
package/default.rip
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# ==============================================================================
|
|
2
|
+
# @rip-lang/server — Default Static File Server
|
|
3
|
+
# ==============================================================================
|
|
4
|
+
#
|
|
5
|
+
# Built-in fallback when no index.rip or index.ts exists in the target directory.
|
|
6
|
+
# Activated automatically by `rip server` when no app entry is found.
|
|
7
|
+
#
|
|
8
|
+
# Behavior:
|
|
9
|
+
# - Serves static files with auto-detected MIME types
|
|
10
|
+
# - Directories with index.html serve the index.html
|
|
11
|
+
# - Directories without index.html show a browsable file listing
|
|
12
|
+
# - Rip browser bundle available at /rip/rip.min.js
|
|
13
|
+
# - Hot-reload via SSE for .rip, .html, and .css changes
|
|
14
|
+
#
|
|
15
|
+
# ==============================================================================
|
|
16
|
+
|
|
17
|
+
import { use, start, notFound } from '@rip-lang/server'
|
|
18
|
+
import { serve } from '@rip-lang/server/middleware'
|
|
19
|
+
import { statSync, readdirSync } from 'node:fs'
|
|
20
|
+
import { join, resolve } from 'node:path'
|
|
21
|
+
|
|
22
|
+
root = resolve(process.env.APP_BASE_DIR or process.cwd())
|
|
23
|
+
rootSlash = root + '/'
|
|
24
|
+
|
|
25
|
+
use serve dir: root, bundle: [], watch: true
|
|
26
|
+
|
|
27
|
+
# --- Helpers ---
|
|
28
|
+
|
|
29
|
+
esc = (s) ->
|
|
30
|
+
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
|
31
|
+
|
|
32
|
+
fmtSize = (n) ->
|
|
33
|
+
for u in ['B', 'KB', 'MB', 'GB']
|
|
34
|
+
if n < 1024
|
|
35
|
+
return if u is 'B' then "#{n} B" else "#{n.toFixed(1)} #{u}"
|
|
36
|
+
n /= 1024
|
|
37
|
+
"#{n.toFixed(1)} TB"
|
|
38
|
+
|
|
39
|
+
fmtDate = (ms) ->
|
|
40
|
+
new Date(ms).toLocaleString undefined,
|
|
41
|
+
year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit'
|
|
42
|
+
|
|
43
|
+
ICONS =
|
|
44
|
+
html: '🌐', htm: '🌐', css: '🎨', js: '📜', mjs: '📜', ts: '📜'
|
|
45
|
+
json: '📋', rip: '⚡', md: '📝', txt: '📝', pdf: '📕'
|
|
46
|
+
png: '🖼️', jpg: '🖼️', jpeg: '🖼️', gif: '🖼️', svg: '🖼️', webp: '🖼️', ico: '🖼️'
|
|
47
|
+
zip: '📦', gz: '📦', tar: '📦'
|
|
48
|
+
mp3: '🎵', ogg: '🎵', wav: '🎵', mp4: '🎬', webm: '🎬'
|
|
49
|
+
woff: '🔤', woff2: '🔤', ttf: '🔤', otf: '🔤'
|
|
50
|
+
|
|
51
|
+
icon = (name) ->
|
|
52
|
+
ICONS[name.slice(name.lastIndexOf('.') + 1).toLowerCase()] or '📄'
|
|
53
|
+
|
|
54
|
+
tr = (ic, href, label, size = '', date = '') ->
|
|
55
|
+
"<tr><td class=\"i\">#{ic}</td><td><a href=\"#{esc href}\">#{esc label}</a></td><td class=\"s\">#{size}</td><td class=\"d\">#{date}</td></tr>"
|
|
56
|
+
|
|
57
|
+
# --- Directory Index ---
|
|
58
|
+
|
|
59
|
+
renderIndex = (reqPath, fsPath) ->
|
|
60
|
+
entries = try readdirSync(fsPath, { withFileTypes: true }) catch then []
|
|
61
|
+
dirs = []
|
|
62
|
+
files = []
|
|
63
|
+
for e in entries
|
|
64
|
+
continue if e.name.startsWith('.')
|
|
65
|
+
if e.isDirectory() then dirs.push(e.name) else files.push(e.name)
|
|
66
|
+
dirs.sort()
|
|
67
|
+
files.sort()
|
|
68
|
+
|
|
69
|
+
rows = []
|
|
70
|
+
rows.push tr('📁', '../', 'Parent Directory') if reqPath isnt '/'
|
|
71
|
+
for d in dirs
|
|
72
|
+
rows.push tr('📁', "#{d}/", "#{d}/")
|
|
73
|
+
for f in files
|
|
74
|
+
try
|
|
75
|
+
s = statSync(join(fsPath, f))
|
|
76
|
+
rows.push tr(icon(f), f, f, fmtSize(s.size), fmtDate(s.mtimeMs))
|
|
77
|
+
catch
|
|
78
|
+
rows.push tr(icon(f), f, f)
|
|
79
|
+
|
|
80
|
+
title = esc(reqPath)
|
|
81
|
+
"""
|
|
82
|
+
<!DOCTYPE html>
|
|
83
|
+
<html lang="en">
|
|
84
|
+
<head>
|
|
85
|
+
<meta charset="UTF-8">
|
|
86
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
87
|
+
<title>Index of #{title}</title>
|
|
88
|
+
<style>
|
|
89
|
+
*{margin:0;padding:0;box-sizing:border-box}
|
|
90
|
+
body{font-family:ui-monospace,'SF Mono',Menlo,Monaco,monospace;background:#fafafa;color:#333;padding:24px 40px}
|
|
91
|
+
h1{font-size:16px;font-weight:600;color:#111;padding:12px 0;border-bottom:2px solid #e1e4e8;margin-bottom:4px}
|
|
92
|
+
table{width:100%;border-collapse:collapse}
|
|
93
|
+
th{text-align:left;padding:8px 12px;font-size:11px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:.5px;border-bottom:1px solid #e1e4e8}
|
|
94
|
+
td{padding:5px 12px;border-bottom:1px solid #f0f0f0;font-size:13px}
|
|
95
|
+
a{color:#0969da;text-decoration:none}a:hover{text-decoration:underline}
|
|
96
|
+
tr:hover td{background:#f0f7ff}
|
|
97
|
+
.i{width:28px;text-align:center}.s{width:100px;text-align:right;color:#666}.d{width:200px;color:#666}th.s{text-align:right}
|
|
98
|
+
@media(max-width:600px){body{padding:16px}.d,th.d{display:none}}
|
|
99
|
+
</style>
|
|
100
|
+
</head>
|
|
101
|
+
<body>
|
|
102
|
+
<h1>Index of #{title}</h1>
|
|
103
|
+
<table>
|
|
104
|
+
<thead><tr><th></th><th>Name</th><th class="s">Size</th><th class="d">Modified</th></tr></thead>
|
|
105
|
+
<tbody>
|
|
106
|
+
#{rows.join('\n')}
|
|
107
|
+
</tbody>
|
|
108
|
+
</table>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# --- Request Handler ---
|
|
114
|
+
|
|
115
|
+
notFound ->
|
|
116
|
+
reqPath = @req.path
|
|
117
|
+
path = resolve(root, reqPath.slice(1))
|
|
118
|
+
|
|
119
|
+
unless path is root or path.startsWith(rootSlash)
|
|
120
|
+
return new Response 'Not Found', { status: 404 }
|
|
121
|
+
|
|
122
|
+
try
|
|
123
|
+
stat = statSync(path)
|
|
124
|
+
catch
|
|
125
|
+
return new Response 'Not Found', { status: 404 }
|
|
126
|
+
|
|
127
|
+
if stat.isFile()
|
|
128
|
+
return @send path
|
|
129
|
+
|
|
130
|
+
if stat.isDirectory()
|
|
131
|
+
return new Response(null, { status: 301, headers: { Location: "#{reqPath}/" } }) unless reqPath.endsWith('/')
|
|
132
|
+
try return @send join(path, 'index.html'), 'text/html; charset=UTF-8' if statSync(join(path, 'index.html')).isFile()
|
|
133
|
+
return new Response renderIndex(reqPath, path), { headers: { 'Content-Type': 'text/html; charset=UTF-8' } }
|
|
134
|
+
|
|
135
|
+
new Response 'Not Found', { status: 404 }
|
|
136
|
+
|
|
137
|
+
start()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rip-lang/server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.78",
|
|
4
4
|
"description": "Pure Rip web framework and application server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "api.rip",
|
|
@@ -45,10 +45,11 @@
|
|
|
45
45
|
"author": "Steve Shreeve <steve.shreeve@gmail.com>",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"rip-lang": ">=3.13.
|
|
48
|
+
"rip-lang": ">=3.13.92"
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"api.rip",
|
|
52
|
+
"default.rip",
|
|
52
53
|
"middleware.rip",
|
|
53
54
|
"server.rip",
|
|
54
55
|
"server.html",
|
package/server.rip
CHANGED
|
@@ -165,6 +165,8 @@ parseRestartPolicy = (token, defReqs, defSecs) ->
|
|
|
165
165
|
|
|
166
166
|
{ maxRequests, maxSeconds }
|
|
167
167
|
|
|
168
|
+
defaultEntry = join(import.meta.dir, 'default.rip')
|
|
169
|
+
|
|
168
170
|
resolveAppEntry = (appPathInput) ->
|
|
169
171
|
abs = if isAbsolute(appPathInput) then appPathInput else resolve(process.cwd(), appPathInput)
|
|
170
172
|
|
|
@@ -177,8 +179,8 @@ resolveAppEntry = (appPathInput) ->
|
|
|
177
179
|
else if existsSync(two)
|
|
178
180
|
entryPath = two
|
|
179
181
|
else
|
|
180
|
-
console.
|
|
181
|
-
|
|
182
|
+
console.log "rip-server: no index.rip found, serving static files from #{abs}\n Create an index.rip to customize, or run 'rip server --help' for options."
|
|
183
|
+
entryPath = defaultEntry
|
|
182
184
|
else
|
|
183
185
|
unless existsSync(abs)
|
|
184
186
|
console.error "App path not found: #{abs}"
|
|
@@ -247,10 +249,7 @@ parseFlags = (argv) ->
|
|
|
247
249
|
else if existsSync(indexTs)
|
|
248
250
|
appPathInput = indexTs
|
|
249
251
|
else
|
|
250
|
-
|
|
251
|
-
console.error 'Usage: rip-server [flags] [app-path] [app-name]'
|
|
252
|
-
console.error ' rip-server [flags] [app-path]@<alias1>,<alias2>,...'
|
|
253
|
-
process.exit(2)
|
|
252
|
+
appPathInput = cwd
|
|
254
253
|
|
|
255
254
|
getKV = (prefix) ->
|
|
256
255
|
for f as rawFlags
|
|
@@ -620,6 +619,7 @@ class Manager
|
|
|
620
619
|
SOCKET_PATH: socketPath
|
|
621
620
|
SOCKET_PREFIX: @flags.socketPrefix
|
|
622
621
|
APP_ENTRY: @flags.appEntry
|
|
622
|
+
APP_BASE_DIR: @flags.appBaseDir
|
|
623
623
|
MAX_REQUESTS: String(@flags.maxRequestsPerWorker)
|
|
624
624
|
MAX_SECONDS: String(@flags.maxSecondsPerWorker)
|
|
625
625
|
RIP_LOG_JSON: if @flags.jsonLogging then '1' else '0'
|
|
@@ -1212,6 +1212,10 @@ main = ->
|
|
|
1212
1212
|
rip serve myapp Start with app name "myapp"
|
|
1213
1213
|
rip serve http HTTP only (no TLS)
|
|
1214
1214
|
rip serve --static w:8 Production: no reload, 8 workers
|
|
1215
|
+
|
|
1216
|
+
If no index.rip or index.ts is found, a built-in static file server
|
|
1217
|
+
activates with directory indexes, auto-detected MIME types, and
|
|
1218
|
+
hot-reload. Create an index.rip to customize server behavior.
|
|
1215
1219
|
"""
|
|
1216
1220
|
return
|
|
1217
1221
|
|