@naanlang/naan 1.6.0 → 1.7.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/LICENSE.md +1 -1
- package/README.md +1 -1
- package/bin/index.js +2 -2
- package/dist/env_web.js +3 -31
- package/dist/naan.min.js +10 -10
- package/frameworks/browser/sworker.js +23 -23
- package/frameworks/browser/terminals.nlg +44 -122
- package/frameworks/browser/worker.nlg +64 -24
- package/frameworks/browser/workers.nlg +1 -1
- package/frameworks/client/apiclient.nlg +73 -43
- package/frameworks/client/psm_client.nlg +10 -14
- package/frameworks/common/common.nlg +2 -0
- package/frameworks/node/apiserver.nlg +0 -1
- package/frameworks/node/node.nlg +1 -0
- package/frameworks/node/pty.nlg +21 -21
- package/frameworks/node/shell.nlg +26 -3
- package/frameworks/node/worker.nlg +153 -79
- package/frameworks/project/build.nlg +55 -13
- package/frameworks/project/projects.nlg +1 -1
- package/frameworks/running/debugcom.nlg +20 -3
- package/frameworks/running/debugnub.nlg +16 -7
- package/frameworks/running/executors.nlg +14 -12
- package/frameworks/running/instances.nlg +1 -0
- package/frameworks/running/remote_req.nlg +4 -8
- package/frameworks/running/remote_res.nlg +1 -1
- package/frameworks/running/sourcecode.nlg +3 -2
- package/frameworks/running/taskexec.nlg +14 -57
- package/frameworks/service/imp.nlg +15 -0
- package/frameworks/service/nubnode.nlg +35 -4
- package/frameworks/service/nubweb.nlg +22 -4
- package/frameworks/storage/file_manager.nlg +3 -2
- package/lib/browser/env_web.js +10 -38
- package/lib/browser/env_webworker.js +1 -30
- package/lib/core/naanlib.js +10 -10
- package/lib/env_node.js +5 -22
- package/lib/env_nodeworker.js +8 -43
- package/lib/node_server_init.nlg +149 -0
- package/lib/node_worker.js +36 -0
- package/lib/node_worker_init.nlg +43 -0
- package/package.json +1 -1
- package/plugins/openSearch/openSearch.nlg +3 -2
- package/plugins/openSearch/os_dynamo.nlg +3 -2
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +44 -2
- package/plugins/serviceAws/aws_dynamo.nlg +144 -63
- package/plugins/serviceAws/aws_utils.nlg +6 -11
- package/plugins/serviceAws/serviceAws.nlg +3 -2
- package/test/test_01_core.nlg +2 -2
- package/test/test_02_context.nlg +8 -8
- package/test/test_03_jsinterop.nlg +3 -3
- package/test/test_07_lingo.nlg +1 -1
|
@@ -19,7 +19,7 @@ serviceMod;;
|
|
|
19
19
|
/*
|
|
20
20
|
* APIClient
|
|
21
21
|
*
|
|
22
|
-
* Make an API client object.
|
|
22
|
+
* Make an API client object. If you want websockets, and you do, call connect() on the object.
|
|
23
23
|
*
|
|
24
24
|
* Options:
|
|
25
25
|
* {
|
|
@@ -46,18 +46,20 @@ closure APIClient(imp, options, local client, url, guid) {
|
|
|
46
46
|
client.requester = clirRequester()
|
|
47
47
|
|
|
48
48
|
//
|
|
49
|
-
//
|
|
49
|
+
// connect
|
|
50
50
|
//
|
|
51
51
|
|
|
52
|
-
client.
|
|
53
|
-
if !client.wesc
|
|
52
|
+
client.connect = function connect(error) {
|
|
53
|
+
if !client.wesc
|
|
54
54
|
client.wesc = WebSocketClient(options, imp)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
`(error) = client.wesc.connect()
|
|
56
|
+
if error {
|
|
57
|
+
ErrorDebuglog("APIClient: cannot connect to", url, error)
|
|
58
|
+
return (list(error))
|
|
58
59
|
}
|
|
60
|
+
client.serverID = client.wesc.serverID // serverID
|
|
61
|
+
list(false, { connected: true })
|
|
59
62
|
}
|
|
60
|
-
wsopen()
|
|
61
63
|
|
|
62
64
|
// clientRequest() - append our guid to the headers
|
|
63
65
|
|
|
@@ -178,10 +180,12 @@ closure APIClient(imp, options, local client, url, guid) {
|
|
|
178
180
|
// Close down the client and release all resources.
|
|
179
181
|
//
|
|
180
182
|
client.destroy = closure destroy() {
|
|
181
|
-
debuglog("client
|
|
183
|
+
debuglog("API client destroyed") // ### not pretty, but uncommon
|
|
182
184
|
client.wesc.destroy()
|
|
183
185
|
client.wesc = false
|
|
184
186
|
}
|
|
187
|
+
|
|
188
|
+
// finis
|
|
185
189
|
|
|
186
190
|
client
|
|
187
191
|
};
|
|
@@ -197,25 +201,38 @@ closure APIClient(imp, options, local client, url, guid) {
|
|
|
197
201
|
* {
|
|
198
202
|
* url: <string> // hostname or URL of the remote server
|
|
199
203
|
* guid: <string> // shared secret for access to remote server
|
|
200
|
-
* instanceID: <string> // UUID for our client; can be persisted by caller
|
|
201
204
|
* }
|
|
202
205
|
*
|
|
203
206
|
*/
|
|
204
207
|
|
|
205
|
-
closure WebSocketClient(options, imp, local wesc, ws) {
|
|
208
|
+
closure WebSocketClient(options, imp, local wesc, ws, pending) {
|
|
206
209
|
global(js)
|
|
207
210
|
wesc = new(object, this)
|
|
208
211
|
|
|
212
|
+
// reconnect
|
|
213
|
+
//
|
|
214
|
+
// Attempt to reconnect if needed, returning false on success or an error tuple otherwise.
|
|
215
|
+
//
|
|
216
|
+
function reconnect() {
|
|
217
|
+
if !ws || wesc.fatal || pending {
|
|
218
|
+
connect() // try to connect
|
|
219
|
+
if wesc.fatal
|
|
220
|
+
return (list(wesc.fatal))
|
|
221
|
+
}
|
|
222
|
+
false
|
|
223
|
+
}
|
|
224
|
+
|
|
209
225
|
// connect
|
|
210
226
|
//
|
|
211
227
|
// Establish a websocket connection with the host.
|
|
212
228
|
//
|
|
213
|
-
wesc.connect = closure connect(local url,
|
|
214
|
-
if ws
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
wesc.connect = closure connect(local url, result) {
|
|
230
|
+
if ws {
|
|
231
|
+
if pending
|
|
232
|
+
return (pending.wait()) // connect in process
|
|
233
|
+
if !wesc.fatal
|
|
234
|
+
return(list(false, { connected: true })) // already open
|
|
235
|
+
}
|
|
219
236
|
try {
|
|
220
237
|
url = xnew(js.w.URL, options.url)
|
|
221
238
|
} catch {
|
|
@@ -226,27 +243,35 @@ closure WebSocketClient(options, imp, local wesc, ws) {
|
|
|
226
243
|
else
|
|
227
244
|
url.protocol = "ws:"
|
|
228
245
|
ws = xnew(js.w.WebSocket, url.href)
|
|
246
|
+
if wesc.fatal
|
|
247
|
+
debuglog("WebSocketClient reopen") // ### not pretty to see this
|
|
248
|
+
wesc.fatal = false
|
|
229
249
|
pending = new(nonce)
|
|
230
|
-
|
|
250
|
+
|
|
231
251
|
// onopen
|
|
232
252
|
// The WebSocket connection is now open.
|
|
233
|
-
ws.onopen = function onopen(e
|
|
253
|
+
ws.onopen = function onopen(e) {
|
|
254
|
+
if wesc.impconn {
|
|
255
|
+
result = wesc.impconn.reopen()
|
|
256
|
+
pending.signal(result)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
234
259
|
wesc.impconn = imp.requester(function(data) {
|
|
235
|
-
if
|
|
236
|
-
return
|
|
260
|
+
if !pending && reconnect() // !pending means not opening/reopening
|
|
261
|
+
return // failed to reconnect
|
|
237
262
|
ws.send(js.w.JSON.stringify(data))
|
|
238
263
|
list(false, { sent: true })
|
|
239
264
|
}, {
|
|
240
265
|
secret: options.guid
|
|
241
266
|
label: "api-server"
|
|
242
|
-
wesc: wesc
|
|
243
267
|
})
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
else
|
|
268
|
+
wesc.impconn.addEventListener("active", function (event, info) {
|
|
269
|
+
wesc.serverID = info.destID // serverID we connected to
|
|
247
270
|
pending.signal(list(false, { ok: true }))
|
|
271
|
+
pending = false
|
|
272
|
+
})
|
|
248
273
|
}
|
|
249
|
-
|
|
274
|
+
|
|
250
275
|
// onmessage
|
|
251
276
|
// A message was received via WebSockets.
|
|
252
277
|
ws.onmessage = function onmessage(e, local error, message) {
|
|
@@ -256,9 +281,11 @@ closure WebSocketClient(options, imp, local wesc, ws) {
|
|
|
256
281
|
if wesc.impconn.dispatch(message)
|
|
257
282
|
return
|
|
258
283
|
}
|
|
259
|
-
|
|
284
|
+
pending.signal(list(Error("incompatible responder")))
|
|
285
|
+
pending = false
|
|
286
|
+
debuglog("socket message:", e.data) // unexpected incoming data
|
|
260
287
|
}
|
|
261
|
-
|
|
288
|
+
|
|
262
289
|
// onerror
|
|
263
290
|
// An error occurred on the WebSocket connection.
|
|
264
291
|
// This can happen in normal operation when the connection times out.
|
|
@@ -266,35 +293,34 @@ closure WebSocketClient(options, imp, local wesc, ws) {
|
|
|
266
293
|
if !wesc.fatal
|
|
267
294
|
wesc.fatal = Error("websocket failed", e)
|
|
268
295
|
pending.signal(list(wesc.fatal))
|
|
296
|
+
pending = false
|
|
269
297
|
}
|
|
270
298
|
|
|
271
299
|
// onclose
|
|
272
300
|
// The WebSocket connection was closed.
|
|
273
301
|
ws.onclose = function onclose(e) {
|
|
274
302
|
// if e.code != 1000 && e.code != 1001 && e.code != 1005 && e.code != 1006
|
|
275
|
-
debuglog("WebSocketClient: closed:", e.code, e.reason)
|
|
303
|
+
debuglog("WebSocketClient: closed:", e.code, e.reason) // ### not pretty to see this
|
|
276
304
|
ws = false
|
|
277
305
|
if !wesc.fatal
|
|
278
306
|
wesc.fatal = Error("websocket closed", e.code, e.reason)
|
|
279
307
|
pending.signal(list(wesc.fatal))
|
|
280
|
-
|
|
281
|
-
code: e.code
|
|
282
|
-
reason: e.reason
|
|
283
|
-
})
|
|
284
|
-
wesc.impconn = false
|
|
308
|
+
pending = false
|
|
285
309
|
}
|
|
286
310
|
|
|
287
|
-
pending.wait()
|
|
311
|
+
result = pending.wait(10000, list(Error("IMP connection timeout")))
|
|
312
|
+
pending = false
|
|
313
|
+
result
|
|
288
314
|
}
|
|
289
|
-
|
|
315
|
+
|
|
290
316
|
// close
|
|
291
317
|
//
|
|
292
318
|
// Close the connection, but can be opened again with connect().
|
|
293
319
|
//
|
|
294
320
|
wesc.close = function close() {
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
ws
|
|
321
|
+
if ws
|
|
322
|
+
ws.close(1000) // 1000: normal close
|
|
323
|
+
ws = false
|
|
298
324
|
}
|
|
299
325
|
|
|
300
326
|
// destroy
|
|
@@ -303,23 +329,27 @@ closure WebSocketClient(options, imp, local wesc, ws) {
|
|
|
303
329
|
//
|
|
304
330
|
wesc.destroy = function destroy() {
|
|
305
331
|
close()
|
|
332
|
+
wesc.impconn.destroy()
|
|
333
|
+
wesc.impconn = false
|
|
306
334
|
}
|
|
307
335
|
|
|
308
336
|
// send
|
|
309
337
|
//
|
|
310
|
-
// Send a message.
|
|
338
|
+
// Send a message, returning result tuple.
|
|
311
339
|
//
|
|
312
340
|
wesc.send = closure send(data) {
|
|
313
|
-
if
|
|
314
|
-
return
|
|
341
|
+
if reconnect()
|
|
342
|
+
return // failed to reconnect
|
|
315
343
|
wesc.impconn.send(data)
|
|
316
344
|
}
|
|
317
345
|
|
|
318
346
|
// sendRaw
|
|
319
347
|
//
|
|
320
|
-
// Send a message with reconnect but no routing.
|
|
348
|
+
// Send a message with reconnect but no routing. Returns a result tuple.
|
|
321
349
|
//
|
|
322
350
|
wesc.sendRaw = closure sendRaw(data) {
|
|
351
|
+
if reconnect()
|
|
352
|
+
return // failed to reconnect
|
|
323
353
|
ws.send(data)
|
|
324
354
|
}
|
|
325
355
|
|
|
@@ -634,18 +634,14 @@ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
|
|
|
634
634
|
else {
|
|
635
635
|
api = connector.apis[creds.urlName]
|
|
636
636
|
if !api { // create API to remote server
|
|
637
|
-
|
|
637
|
+
api = APIClient(App.imp, {
|
|
638
638
|
url: creds.urlName
|
|
639
639
|
guid: creds.authSecret
|
|
640
|
-
instanceID: creds.instanceID
|
|
641
640
|
})
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
641
|
+
`(error) = api.connect()
|
|
642
|
+
if !error
|
|
643
|
+
connector.apis[creds.urlName] = api
|
|
644
|
+
} } }
|
|
649
645
|
if error
|
|
650
646
|
list(error)
|
|
651
647
|
else if service == "NideTerminal" {
|
|
@@ -653,11 +649,11 @@ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
|
|
|
653
649
|
return (list(Error("NideTerminal not supported in this environment")))
|
|
654
650
|
list(false, App.nide.track.spawn(App.nide.registerRemote(api),
|
|
655
651
|
args.0, // terminal display name
|
|
656
|
-
args.1
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
|
|
652
|
+
merge(args.1, {
|
|
653
|
+
serverID: api.serverID // server we found
|
|
654
|
+
// workerID: "NideServer" -- for main thread; other strings for worker threads
|
|
655
|
+
// type: "My Remotes" -- display name of terminal group; e.g. URL
|
|
656
|
+
}))) // options:
|
|
661
657
|
}
|
|
662
658
|
else if service == "NideHostAPI"
|
|
663
659
|
list(false, api) // connect to the Host API
|
|
@@ -247,6 +247,8 @@ macro LoadComponentOnDemand(functionKeys, componentPath, local modobj, key) {
|
|
|
247
247
|
macro LiveExport() {
|
|
248
248
|
global()
|
|
249
249
|
closure (exporterMod, local importList) {
|
|
250
|
+
if !exporterMod
|
|
251
|
+
return (false) // no module (e.g. build time)
|
|
250
252
|
importList = []
|
|
251
253
|
//
|
|
252
254
|
// LiveImport
|
package/frameworks/node/node.nlg
CHANGED
|
@@ -124,6 +124,7 @@ function nodrInit(local manifest) {
|
|
|
124
124
|
throw("module frameworks/node cannot be used from a browser")
|
|
125
125
|
Naan.module.build(module.id, "node", function(modobj, compobj) {
|
|
126
126
|
require("../common/").LiveImport()
|
|
127
|
+
require("../common/events.nlg")
|
|
127
128
|
commonWatching = require("../common/watching.nlg")
|
|
128
129
|
compobj.manifest = manifest
|
|
129
130
|
function nodrReload() {
|
package/frameworks/node/pty.nlg
CHANGED
|
@@ -102,8 +102,9 @@ closure ptyManagerMain(local ptym, pty) {
|
|
|
102
102
|
//
|
|
103
103
|
// Close the pty manager, killing any open spawned processes.
|
|
104
104
|
//
|
|
105
|
-
ptym.close = closure close() {
|
|
106
|
-
|
|
105
|
+
ptym.close = closure close(local worker) {
|
|
106
|
+
for worker in ptym.workers
|
|
107
|
+
worker.kill()
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
// spawn
|
|
@@ -111,14 +112,16 @@ closure ptyManagerMain(local ptym, pty) {
|
|
|
111
112
|
// Spawn a new pty process, returning a result tuple. If executable is omitted then a shell is
|
|
112
113
|
// spawned.
|
|
113
114
|
//
|
|
114
|
-
// options:
|
|
115
|
+
// options: (all optional)
|
|
115
116
|
// {
|
|
116
|
-
//
|
|
117
|
+
// cwd: <string> // current working directory
|
|
118
|
+
// env: <string> // environment
|
|
119
|
+
// raw: <boolean> // enable raw mode with ioctls
|
|
117
120
|
// }
|
|
118
121
|
//
|
|
119
122
|
//
|
|
120
123
|
ptym.spawn = closure spawn(executable, args, notify, options,
|
|
121
|
-
local ptySpawned, ptypro) {
|
|
124
|
+
local error, ptySpawned, ptypro) {
|
|
122
125
|
|
|
123
126
|
if !executable {
|
|
124
127
|
if os.platform() == "win32"
|
|
@@ -136,6 +139,10 @@ closure ptyManagerMain(local ptym, pty) {
|
|
|
136
139
|
env: js.g.process.env
|
|
137
140
|
}, options)
|
|
138
141
|
|
|
142
|
+
`(error) = open() // ensure we're open
|
|
143
|
+
if error
|
|
144
|
+
return (list(error))
|
|
145
|
+
|
|
139
146
|
ptySpawned = pty.spawn(executable, args, { // spawn the pty process
|
|
140
147
|
name: options.name
|
|
141
148
|
cols: options.cols
|
|
@@ -147,7 +154,6 @@ closure ptyManagerMain(local ptym, pty) {
|
|
|
147
154
|
ptypro = ptyProcess(ptySpawned, notify)
|
|
148
155
|
ptym.workers.push({ // track all pty processes
|
|
149
156
|
pid: ptySpawned.pid
|
|
150
|
-
workerGUID: options.workerGUID || undefined
|
|
151
157
|
ptySpawned: ptySpawned
|
|
152
158
|
})
|
|
153
159
|
|
|
@@ -160,19 +166,6 @@ closure ptyManagerMain(local ptym, pty) {
|
|
|
160
166
|
ptypro.killed(event)
|
|
161
167
|
})
|
|
162
168
|
|
|
163
|
-
if options.workerGUID
|
|
164
|
-
WorkerController().watcher.watch(
|
|
165
|
-
function(action, worker, local ptydex) { // worker-owner of pty process died
|
|
166
|
-
if action == "terminated" {
|
|
167
|
-
ptydex = ptym.workers.findIndex(function(item) {
|
|
168
|
-
options.workerGUID == item.workerGUID
|
|
169
|
-
})
|
|
170
|
-
if ptydex >= 0 {
|
|
171
|
-
ptySpawned.kill()
|
|
172
|
-
ptym.workers.splice(ptydex)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
169
|
list(false, ptypro)
|
|
177
170
|
}
|
|
178
171
|
|
|
@@ -238,7 +231,14 @@ closure ptyProcess(ptySpawned, notify, local ptyp) {
|
|
|
238
231
|
// Kill the spawned process.
|
|
239
232
|
//
|
|
240
233
|
ptyp.kill = closure kill(signame) {
|
|
241
|
-
|
|
234
|
+
if signame
|
|
235
|
+
ptySpawned.kill(signame) // use caller signal
|
|
236
|
+
else {
|
|
237
|
+
ptySpawned.kill("SIGTERM") // SIGTERM/SIGKILL sequence
|
|
238
|
+
sleep(1000)
|
|
239
|
+
if ptySpawned
|
|
240
|
+
ptySpawned.kill("SIGKILL")
|
|
241
|
+
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// finis
|
|
@@ -268,7 +268,7 @@ closure ptyManagerWorker(local ptyw, mainContext, mainPty) {
|
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
if !mainContext {
|
|
271
|
-
`(error, main) = require("naanlib:frameworks/running/
|
|
271
|
+
`(error, main) = require("naanlib:frameworks/running/remote_req.nlg").MainThread()
|
|
272
272
|
if !error
|
|
273
273
|
`(error, mainContext) = main.context()
|
|
274
274
|
if error
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* column positioning: // // !
|
|
8
8
|
*
|
|
9
|
-
* Copyright (c) 2024-
|
|
9
|
+
* Copyright (c) 2024-2026 by Richard C. Zulch
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
|
|
31
|
-
global(js, compress)
|
|
31
|
+
global(js, compress, App)
|
|
32
32
|
shob = new(object, this)
|
|
33
33
|
|
|
34
34
|
// readline
|
|
@@ -78,6 +78,26 @@ closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
|
|
|
78
78
|
pending.wait()
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// pty
|
|
82
|
+
//
|
|
83
|
+
// Execute a shell command in a PTY terminal with optional current-working-directory.
|
|
84
|
+
//
|
|
85
|
+
shob.pty = closure pty(cmd, args, cwd, local exec, error, options, ptyp) {
|
|
86
|
+
exec = App.exec
|
|
87
|
+
if !exec
|
|
88
|
+
`(error, exec) = App.workerNub.execProxy()
|
|
89
|
+
if !error {
|
|
90
|
+
options = { input:"keyboard", output:"display", raw:true }
|
|
91
|
+
if cwd
|
|
92
|
+
options.spawn = { cwd: cwd }
|
|
93
|
+
`(error, ptyp) = exec.subprocess(cmd, args, options)
|
|
94
|
+
}
|
|
95
|
+
if ptyp
|
|
96
|
+
list(false, ptyp)
|
|
97
|
+
else
|
|
98
|
+
exec(cmd, args, cwd)
|
|
99
|
+
}
|
|
100
|
+
|
|
81
101
|
// shellparser
|
|
82
102
|
// intercept command-line parsing and look for a !shell escape
|
|
83
103
|
function shellparser(source, grammar, operators, local token, text) {
|
|
@@ -89,7 +109,10 @@ closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
|
|
|
89
109
|
atom: `;;
|
|
90
110
|
loc: tuple(0, text.length)
|
|
91
111
|
})
|
|
92
|
-
|
|
112
|
+
if text == "\n"
|
|
113
|
+
pty()
|
|
114
|
+
else
|
|
115
|
+
exec("bash", ["-c", text], js.d) // default directory: js.d
|
|
93
116
|
}
|
|
94
117
|
else {
|
|
95
118
|
source.tokenpush(token)
|