@rip-lang/server 1.3.124 → 1.3.126

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/control/cli.rip CHANGED
@@ -63,6 +63,7 @@ export resolveAppEntry = (appPathInput, defaultEntryPath = null) ->
63
63
  export parseFlags = (argv, defaultEntryPath = null) ->
64
64
  rawFlags = new Set()
65
65
  appPathInput = null
66
+ appNameOverride = null
66
67
  appAliases = []
67
68
 
68
69
  isFlag = (tok) ->
@@ -82,13 +83,17 @@ export parseFlags = (argv, defaultEntryPath = null) ->
82
83
  unless appPathInput
83
84
  if tok.includes('@') and not tok.startsWith('-')
84
85
  [pathPart, aliasesPart] = tok.split('@')
86
+ aliases = aliasesPart.split(',').map((a) -> a.trim()).filter((a) -> a)
85
87
  maybe = tryResolveApp(pathPart)
86
88
  if maybe
87
89
  appPathInput = maybe
88
- appAliases = aliasesPart.split(',').map((a) -> a.trim()).filter((a) -> a)
90
+ if aliases.length > 0
91
+ appNameOverride = aliases[0]
92
+ appAliases = aliases
89
93
  continue
90
94
  else if not pathPart.includes('/') and not pathPart.startsWith('.')
91
- appAliases = [pathPart].concat(aliasesPart.split(',').map((a) -> a.trim()).filter((a) -> a))
95
+ appNameOverride = pathPart
96
+ appAliases = [pathPart].concat(aliases)
92
97
  continue
93
98
 
94
99
  maybe = tryResolveApp(tok)
@@ -97,7 +102,11 @@ export parseFlags = (argv, defaultEntryPath = null) ->
97
102
  continue
98
103
 
99
104
  unless isFlag(tok)
100
- appAliases.push(tok)
105
+ if appNameOverride
106
+ appAliases.push(tok)
107
+ else
108
+ appNameOverride = tok
109
+ appAliases = [tok]
101
110
  continue
102
111
 
103
112
  rawFlags.add(tok)
@@ -114,13 +123,18 @@ export parseFlags = (argv, defaultEntryPath = null) ->
114
123
  appPathInput = cwd
115
124
 
116
125
  getKV = (prefix) ->
117
- for f as rawFlags
118
- return f.slice(prefix.length) if f.startsWith(prefix)
126
+ tokens = Array.from(rawFlags)
127
+ bare = prefix.slice(0, -1)
128
+ for i in [0...tokens.length]
129
+ return tokens[i].slice(prefix.length) if tokens[i].startsWith(prefix)
130
+ if tokens[i] is bare and tokens[i + 1] and not tokens[i + 1].startsWith('-')
131
+ return tokens[i + 1]
119
132
  undefined
120
133
 
121
134
  has = (name) -> rawFlags.has(name)
122
135
 
123
136
  { baseDir, entryPath, appName } = resolveAppEntry(appPathInput, defaultEntryPath)
137
+ appName = appNameOverride if appNameOverride
124
138
  appAliases = [appName] if appAliases.length is 0
125
139
 
126
140
  tokens = Array.from(rawFlags)
@@ -211,8 +225,10 @@ export parseFlags = (argv, defaultEntryPath = null) ->
211
225
  rateLimit: coerceInt(getKV('--rate-limit='), 0)
212
226
  rateLimitWindow: coerceInt(getKV('--rate-limit-window='), 60000)
213
227
  watch: watch
214
- edgefilePath: getKV('--edgefile=')
215
- checkConfig: has('--check-config')
228
+ edgefilePath: getKV('--edgefile=') or getKV('--file=') or getKV('-f=')
229
+ checkConfig: has('--check-config') or has('--check') or has('-c')
230
+ reloadConfig: has('--reload') or has('-r')
231
+ stopServer: has('--stop') or has('-s')
216
232
  envOverride: envOverride
217
233
  debug: has('--debug')
218
234
  }
@@ -236,6 +252,16 @@ export findAppPathToken = (argv, existsSyncFn, isAbsoluteFn, resolveFn, cwdFn) -
236
252
  export computeSocketPrefix = (argv, resolveAppEntryFn, existsSyncFn, isAbsoluteFn, resolveFn, cwdFn) ->
237
253
  override = getKV(argv, '--socket-prefix=')
238
254
  return override if override
255
+ for i in [2...argv.length]
256
+ tok = argv[i]
257
+ continue if tok.startsWith('-') or /^\d+$/.test(tok) or tok in ['http', 'https']
258
+ [pathPart, aliasesPart] = if tok.includes('@') then tok.split('@') else [tok, null]
259
+ aliases = aliasesPart?.split(',').map((a) -> a.trim()).filter((a) -> a) or []
260
+ looksLikePath = pathPart.includes('/') or pathPart.startsWith('.') or pathPart.endsWith('.rip') or pathPart.endsWith('.ts')
261
+ unless looksLikePath
262
+ return "rip_#{pathPart}"
263
+ return "rip_#{aliases[0]}" if aliases.length > 0
264
+ break
239
265
  appTok = findAppPathToken(argv, existsSyncFn, isAbsoluteFn, resolveFn, cwdFn)
240
266
  if appTok
241
267
  try
@@ -260,14 +286,16 @@ export runHelpOutput = (argv) ->
260
286
  rip-server - Pure Rip application server
261
287
 
262
288
  Usage:
263
- rip serve [options] [app-path] [app-name]
264
- rip serve [options] [app-path]@<alias1>,<alias2>,...
289
+ rip server [options] [app-path] [app-name]
290
+ rip server [options] [app-path]@<alias1>,<alias2>,...
265
291
 
266
292
  Options:
267
293
  -h, --help Show this help
268
294
  -v, --version Show version
269
- --edgefile=<path> Load Edgefile.rip from an explicit path
270
- --check-config Validate Edgefile.rip or config.rip and exit
295
+ -c, --check Validate Edgefile.rip or config.rip and exit
296
+ -r, --reload Reload config on a running server
297
+ -s, --stop Stop a running server
298
+ -f, --file=<path> Load Edgefile.rip from an explicit path
271
299
  --watch=<glob> Watch glob pattern (default: *.rip)
272
300
  --static Disable hot reload and file watching
273
301
  --env=<mode> Set environment (dev, production)
@@ -297,13 +325,15 @@ export runHelpOutput = (argv) ->
297
325
  r:<n>,<s>s Restart policy: max requests, max seconds
298
326
 
299
327
  Examples:
300
- rip serve Start with ./index.rip (watches *.rip)
301
- rip serve myapp Start with app name "myapp"
302
- rip serve http HTTP only (no TLS)
303
- rip serve --static w:8 Production: no reload, 8 workers
304
- rip serve --check-config
305
- rip serve --check-config --edgefile=./Edgefile.rip
306
- rip serve --edgefile=./Edgefile.rip
328
+ rip server Start with ./index.rip (watches *.rip)
329
+ rip server myapp Start with app name "myapp"
330
+ rip server http HTTP only (no TLS)
331
+ rip server --static w:8 Production: no reload, 8 workers
332
+ rip server -c Validate config and exit
333
+ rip server -c -f=./Edgefile.rip
334
+ rip server -r Reload running server config
335
+ rip server -s Stop running server
336
+ rip server -f=./Edgefile.rip
307
337
 
308
338
  If no index.rip or index.ts is found, a built-in static file server
309
339
  activates with directory indexes, auto-detected MIME types, and
@@ -313,8 +343,7 @@ export runHelpOutput = (argv) ->
313
343
 
314
344
  # --- Subcommands ---
315
345
 
316
- export runStopSubcommand = (argv, prefix, getPidFilePathFn, existsSyncFn, readFileSyncFn, killFn, spawnSyncFn, importMetaPath) ->
317
- return false unless 'stop' in argv
346
+ export stopServer = (prefix, getPidFilePathFn, existsSyncFn, readFileSyncFn, killFn, spawnSyncFn, importMetaPath) ->
318
347
  pidFile = getPidFilePathFn(prefix)
319
348
  try
320
349
  if existsSyncFn(pidFile)
@@ -326,7 +355,6 @@ export runStopSubcommand = (argv, prefix, getPidFilePathFn, existsSyncFn, readFi
326
355
  spawnSyncFn(['pkill', '-f', importMetaPath])
327
356
  catch e
328
357
  console.error "rip-server: stop failed: #{e.message}"
329
- true
330
358
 
331
359
  export runListSubcommand = (argv, prefix, getControlSocketPathFn, fetchFn, exitFn) ->
332
360
  return false unless 'list' in argv
@@ -341,3 +369,18 @@ export runListSubcommand = (argv, prefix, getControlSocketPathFn, fetchFn, exitF
341
369
  console.error "list command failed: #{e?.message or e}"
342
370
  exitFn(1)
343
371
  true
372
+
373
+ export runReloadSubcommand = (prefix, getControlSocketPathFn, fetchFn, exitFn) ->
374
+ controlUnix = getControlSocketPathFn(prefix)
375
+ try
376
+ res = fetchFn!('http://localhost/reload', { unix: controlUnix, method: 'POST' })
377
+ j = res.json!
378
+ if j?.ok
379
+ p "rip-server: config reload applied"
380
+ else
381
+ reason = j?.reason or 'unknown'
382
+ console.error "rip-server: config reload rejected: #{reason}"
383
+ exitFn(1)
384
+ catch e
385
+ console.error "rip-server: reload failed: #{e?.message or e}"
386
+ exitFn(1)
@@ -3,6 +3,7 @@
3
3
  # ==============================================================================
4
4
 
5
5
  import { unlinkSync } from 'node:fs'
6
+ import { dirname, join } from 'node:path'
6
7
  import { getWorkerSocketPath, getControlSocketPath } from './control.rip'
7
8
 
8
9
  # --- Spawn ---
@@ -28,7 +29,8 @@ export spawnTrackedWorker = (flags, workerId, version, nowMsFn, importMetaPath,
28
29
  RIP_LOG_JSON: if flags.jsonLogging then '1' else '0'
29
30
  RIP_VERSION: String(version)
30
31
 
31
- proc = Bun.spawn ['rip', importMetaPath],
32
+ loaderPath = join(dirname(importMetaPath), '..', '..', 'rip-loader.js')
33
+ proc = Bun.spawn ['bun', '--preload', loaderPath, importMetaPath],
32
34
  stdout: 'inherit'
33
35
  stderr: 'inherit'
34
36
  stdin: 'ignore'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rip-lang/server",
3
- "version": "1.3.124",
3
+ "version": "1.3.126",
4
4
  "description": "Pure Rip web framework and application server",
5
5
  "type": "module",
6
6
  "main": "api.rip",
@@ -45,7 +45,7 @@
45
45
  "author": "Steve Shreeve <steve.shreeve@gmail.com>",
46
46
  "license": "MIT",
47
47
  "dependencies": {
48
- "rip-lang": ">=3.13.129"
48
+ "rip-lang": ">=3.13.131"
49
49
  },
50
50
  "files": [
51
51
  "api.rip",
package/server.rip CHANGED
@@ -26,7 +26,7 @@ import { handleWorkerControl, handleWatchControl, handleRegistryControl, handleR
26
26
  import { getLanIP as controlGetLanIP, startMdnsAdvertisement as controlStartMdnsAdvertisement, stopMdnsAdvertisements as controlStopMdnsAdvertisements } from './control/mdns.rip'
27
27
  import { registerWatchGroup, handleWatchGroup, broadcastWatchChange, registerAppWatchDirs, shouldTriggerCodeReload } from './control/watchers.rip'
28
28
  import { computeHideUrls, logStartupSummary, createCleanup, installShutdownHandlers } from './control/lifecycle.rip'
29
- import { computeSocketPrefix as cliComputeSocketPrefix, runVersionOutput, runHelpOutput, runStopSubcommand, runListSubcommand, resolveAppEntry, parseFlags, coerceInt } from './control/cli.rip'
29
+ import { runVersionOutput, runHelpOutput, stopServer, runReloadSubcommand, resolveAppEntry, parseFlags, coerceInt } from './control/cli.rip'
30
30
  import { runSetupMode, runWorkerMode } from './control/worker.rip'
31
31
  import { spawnTrackedWorker, postWorkerQuit, waitForWorkerReady } from './control/workers.rip'
32
32
  import { createChallengeStore, handleChallengeRequest } from './acme/store.rip'
@@ -1476,21 +1476,17 @@ main = ->
1476
1476
  if runHelpOutput(process.argv)
1477
1477
  return
1478
1478
 
1479
- # Subcommands: stop/list
1480
- if 'stop' in process.argv or 'list' in process.argv
1481
- prefix = cliComputeSocketPrefix(process.argv, resolveAppEntry, existsSync, isAbsolute, resolve, (-> process.cwd()))
1482
- if runStopSubcommand(process.argv, prefix, getPidFilePath, existsSync, readFileSync, process.kill.bind(process), Bun.spawnSync, import.meta.path)
1483
- return
1484
-
1485
- if runListSubcommand(process.argv, prefix, getControlSocketPath, fetch, exit)
1486
- return
1487
-
1488
- # Normal startup
1489
1479
  flags = parseFlags(process.argv, defaultEntry)
1490
1480
  applyFlagSideEffects(flags)
1491
1481
  if flags.checkConfig
1492
1482
  runCheckConfig!(flags)
1493
1483
  return
1484
+ if flags.reloadConfig
1485
+ runReloadSubcommand!(flags.socketPrefix, getControlSocketPath, fetch, exit)
1486
+ return
1487
+ if flags.stopServer
1488
+ stopServer(flags.socketPrefix, getPidFilePath, existsSync, readFileSync, process.kill.bind(process), Bun.spawnSync, import.meta.path)
1489
+ return
1494
1490
  setEventJsonMode(flags.jsonLogging)
1495
1491
  pidFile = getPidFilePath(flags.socketPrefix)
1496
1492
  writeFileSync(pidFile, String(process.pid))
@@ -1504,13 +1500,7 @@ main = ->
1504
1500
  exit 1 unless startupReload.ok
1505
1501
 
1506
1502
  cleanup = createCleanup(pidFile, svr, mgr, unlinkSync, fetch, process)
1507
- installShutdownHandlers(cleanup, process, ->
1508
- p 'rip-server: received SIGHUP, reloading config...'
1509
- if svr.reloadRuntimeConfig!(flags, 'sighup', true).ok
1510
- p 'rip-server: config reload applied'
1511
- else
1512
- p 'rip-server: config reload rejected, keeping previous config'
1513
- )
1503
+ installShutdownHandlers(cleanup, process)
1514
1504
 
1515
1505
  # Suppress top URL lines if setup.rip will print them at the bottom
1516
1506
  flags.hideUrls = computeHideUrls(flags.appEntry, join, dirname, existsSync)
@@ -1,5 +1,5 @@
1
1
  import { test, eq, ok } from '../test.rip'
2
- import { coerceInt, parseFlags } from '../control/cli.rip'
2
+ import { coerceInt, computeSocketPrefix, parseFlags } from '../control/cli.rip'
3
3
  import { createEdgeRuntime, createReloadHistoryEntry } from '../edge/runtime.rip'
4
4
 
5
5
  test "control cli helpers load and parse basic flags", ->
@@ -8,6 +8,28 @@ test "control cli helpers load and parse basic flags", ->
8
8
  eq flags.httpsPort, 9445
9
9
  ok flags.edgefilePath.endsWith('Edgefile.rip')
10
10
 
11
+ test "control cli uses bare app token as true app name override", ->
12
+ flags = parseFlags(['bun', 'server', 'ola'])
13
+ eq flags.appName, 'ola'
14
+ eq flags.appAliases[0], 'ola'
15
+ eq flags.socketPrefix, 'rip_ola'
16
+
17
+ socketPrefix = computeSocketPrefix(
18
+ ['bun', 'server', 'ola'],
19
+ (appTok) -> { appName: appTok }
20
+ -> false
21
+ -> false
22
+ (_, tok) -> tok
23
+ -> '/tmp'
24
+ )
25
+ eq socketPrefix, 'rip_ola'
26
+
27
+ test "control cli supports explicit path with custom app name", ->
28
+ flags = parseFlags(['bun', 'server', 'packages/server/default.rip@ola'])
29
+ eq flags.appName, 'ola'
30
+ eq flags.appAliases[0], 'ola'
31
+ eq flags.socketPrefix, 'rip_ola'
32
+
11
33
  test "edge runtime helpers load and create summary entries", ->
12
34
  runtime = createEdgeRuntime()
13
35
  entry = createReloadHistoryEntry('reload-1', 'test', null, 1, 'applied')