@naanlang/naan 1.5.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.
Files changed (62) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +2 -2
  3. package/bin/index.js +7 -3
  4. package/dist/env_web.js +162 -48
  5. package/dist/naan.min.js +11 -11
  6. package/frameworks/browser/https_request.nlg +2 -2
  7. package/frameworks/browser/sworker.js +28 -26
  8. package/frameworks/browser/terminals.nlg +295 -737
  9. package/frameworks/browser/worker.nlg +295 -0
  10. package/frameworks/browser/workers.nlg +6 -6
  11. package/frameworks/browser/ws_client.nlg +15 -10
  12. package/frameworks/client/apiclient.nlg +211 -9
  13. package/frameworks/client/psm_client.nlg +10 -14
  14. package/frameworks/client/relaycon.nlg +87 -161
  15. package/frameworks/common/common.nlg +2 -0
  16. package/frameworks/common/events.nlg +101 -0
  17. package/frameworks/common/repltools.nlg +80 -1
  18. package/frameworks/node/apiserver.nlg +38 -246
  19. package/frameworks/node/filesystem.nlg +31 -6
  20. package/frameworks/node/http_request.nlg +34 -13
  21. package/frameworks/node/https_request.nlg +8 -3
  22. package/frameworks/node/node.nlg +3 -0
  23. package/frameworks/node/pty.nlg +346 -0
  24. package/frameworks/node/shell.nlg +26 -3
  25. package/frameworks/node/worker.nlg +504 -560
  26. package/frameworks/node/ws_client.nlg +2 -2
  27. package/frameworks/project/build.nlg +56 -14
  28. package/frameworks/project/projects.nlg +1 -1
  29. package/frameworks/running/debugcom.nlg +20 -3
  30. package/frameworks/running/debugnub.nlg +33 -11
  31. package/frameworks/running/executors.nlg +40 -285
  32. package/frameworks/running/instances.nlg +394 -0
  33. package/frameworks/running/remote_req.nlg +139 -0
  34. package/frameworks/running/remote_res.nlg +89 -0
  35. package/frameworks/running/sourcecode.nlg +3 -2
  36. package/frameworks/running/taskexec.nlg +16 -59
  37. package/frameworks/service/imp.nlg +751 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +136 -0
  40. package/frameworks/service/nubweb.nlg +124 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/file_manager.nlg +3 -2
  43. package/lib/browser/env_web.js +169 -55
  44. package/lib/browser/env_webworker.js +89 -31
  45. package/lib/core/naanlib.js +11 -11
  46. package/lib/env_node.js +186 -27
  47. package/lib/env_nodeworker.js +82 -44
  48. package/lib/node_server_init.nlg +149 -0
  49. package/lib/node_worker.js +36 -0
  50. package/lib/node_worker_init.nlg +43 -0
  51. package/package.json +1 -1
  52. package/plugins/openSearch/openSearch.nlg +3 -2
  53. package/plugins/openSearch/os_dynamo.nlg +3 -2
  54. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +44 -2
  55. package/plugins/serviceAws/aws_dynamo.nlg +144 -63
  56. package/plugins/serviceAws/aws_utils.nlg +6 -11
  57. package/plugins/serviceAws/serviceAws.nlg +3 -2
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_01_core.nlg +2 -2
  60. package/test/test_02_context.nlg +32 -9
  61. package/test/test_03_jsinterop.nlg +3 -3
  62. package/test/test_07_lingo.nlg +1 -1
@@ -0,0 +1,346 @@
1
+ /*
2
+ * pty.nlg
3
+ * Naanlib/frameworks/node
4
+ *
5
+ * Pseudo-terminal access for Linux, Windows, and MacOS.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * PtyManager
16
+ *
17
+ * Fork a process with a pseudo-terminal (pty). This returns a pty-process manager object that can be
18
+ * used to manage these processes. On the NodeJS main thread this instantiates the (not thread safe)
19
+ * node-pty library. On worker threads it creates a connection to the main thread to actually perform
20
+ * the work, including data transfers. More than one process can be created simultaneously. If this
21
+ * worker dies then its processes are killed.
22
+ * There is one global PtyManager per thread, including the main thread. Spawn processes from
23
+ * your PtyManager instance and then kill them when done.
24
+ *
25
+ * This uses node-pty, with the API defined here:
26
+ * https://github.com/microsoft/node-pty/blob/main/typings/node-pty.d.ts
27
+ *
28
+ */
29
+
30
+ pty_instance = false;
31
+
32
+ closure PtyManager() {
33
+ global(js, pty_instance)
34
+ if pty_instance
35
+ return
36
+
37
+ if js.k.isMainThread
38
+ pty_instance = ptyManagerMain()
39
+ else
40
+ pty_instance = ptyManagerWorker()
41
+ };
42
+
43
+
44
+ /*
45
+ * ptyManagerMain
46
+ *
47
+ * Manager of pty processes on the NodeJS main thread.
48
+ *
49
+ */
50
+
51
+ closure ptyManagerMain(local ptym, pty) {
52
+ global(fs, os, js)
53
+ ptym = new(object, this)
54
+ ptym.workers = []
55
+
56
+ // install
57
+ //
58
+ // Install and/or update the node-pty package.
59
+ //
60
+ ptym.install = closure install(rootpath, local error, fs, output) {
61
+ if !rootpath
62
+ rootpath = js.d
63
+ debuglog("node-pty install at", rootpath)
64
+ `(error, fs) = Filesystem(rootpath)
65
+ `(error, output) = fs.execLines("npm", {
66
+ cmdargs: ["install", "--save", "@lydell/node-pty", "--prefix", js.d ]
67
+ execops: {
68
+ cwd: rootpath
69
+ }
70
+ })
71
+ if !error {
72
+ if output.exitCode != 0
73
+ error = Error("npm install exit code: ${output.exitCode}", {
74
+ stderr: output.stderr.join("\n")
75
+ })
76
+ }
77
+ if error
78
+ list(error)
79
+ else
80
+ list(false, output.stdout.join("\n"))
81
+ }
82
+
83
+ // open
84
+ //
85
+ // Open the pty manager, installing if needed, returning an error.
86
+ //
87
+ ptym.open = closure open(local error, stdout) {
88
+ if !pty {
89
+ `(error, pty) = await(js.i("@lydell/node-pty"))
90
+ if error {
91
+ `(error, stdout) = install()
92
+ if !error
93
+ `(error, pty) = await(js.i("@lydell/node-pty"))
94
+ if error
95
+ return (list(Error("can't use node-pty", error)))
96
+ }
97
+ }
98
+ list(false, { ok: true })
99
+ }
100
+
101
+ // close
102
+ //
103
+ // Close the pty manager, killing any open spawned processes.
104
+ //
105
+ ptym.close = closure close(local worker) {
106
+ for worker in ptym.workers
107
+ worker.kill()
108
+ }
109
+
110
+ // spawn
111
+ //
112
+ // Spawn a new pty process, returning a result tuple. If executable is omitted then a shell is
113
+ // spawned.
114
+ //
115
+ // options: (all optional)
116
+ // {
117
+ // cwd: <string> // current working directory
118
+ // env: <string> // environment
119
+ // raw: <boolean> // enable raw mode with ioctls
120
+ // }
121
+ //
122
+ //
123
+ ptym.spawn = closure spawn(executable, args, notify, options,
124
+ local error, ptySpawned, ptypro) {
125
+
126
+ if !executable {
127
+ if os.platform() == "win32"
128
+ executable = "powershell.exe"
129
+ else
130
+ executable = js.g.process.env.SHELL || "/bin/bash" // default shell with safe fallback
131
+ }
132
+ if !array(args)
133
+ args = []
134
+ options = merge({
135
+ name: "xterm-color"
136
+ cols: 80
137
+ rows: 25
138
+ cwd: js.g.process.env.HOME
139
+ env: js.g.process.env
140
+ }, options)
141
+
142
+ `(error) = open() // ensure we're open
143
+ if error
144
+ return (list(error))
145
+
146
+ ptySpawned = pty.spawn(executable, args, { // spawn the pty process
147
+ name: options.name
148
+ cols: options.cols
149
+ rows: options.rows
150
+ cwd: options.cwd
151
+ env: options.env
152
+ })
153
+
154
+ ptypro = ptyProcess(ptySpawned, notify)
155
+ ptym.workers.push({ // track all pty processes
156
+ pid: ptySpawned.pid
157
+ ptySpawned: ptySpawned
158
+ })
159
+
160
+ ptySpawned.onExit(function(event, local ptydex) {
161
+ ptydex = ptym.workers.findIndex(function(item) { // pty process died
162
+ ptySpawned.pid == item.pid
163
+ })
164
+ if ptydex >= 0
165
+ ptym.workers.splice(ptydex)
166
+ ptypro.killed(event)
167
+ })
168
+
169
+ list(false, ptypro)
170
+ }
171
+
172
+ // finis
173
+ ptym
174
+ };
175
+
176
+
177
+ /*
178
+ * ptyProcess
179
+ *
180
+ * A spawned pty process.
181
+ *
182
+ */
183
+
184
+ closure ptyProcess(ptySpawned, notify, local ptyp) {
185
+ global(js)
186
+ ptyp = new(object, this)
187
+
188
+ // onData
189
+ //
190
+ // Data received.
191
+ //
192
+ ptySpawned.onData(function(data) {
193
+ notify.ptyData(data)
194
+ })
195
+
196
+ // killed
197
+ //
198
+ // Process was killed.
199
+ //
200
+ ptyp.killed = function killed(event) {
201
+ notify.ptyKilled(event)
202
+ ptySpawned = false
203
+ }
204
+
205
+ // write
206
+ //
207
+ // Write text or a buffer to the spawned process.
208
+ //
209
+ ptyp.write = closure write(textbuf) {
210
+ ptySpawned.write(textbuf)
211
+ }
212
+
213
+ // resize
214
+ //
215
+ // Tell pty that terminal size changed.
216
+ //
217
+ ptyp.resize = closure resize(cols, rows) {
218
+ ptySpawned.resize(cols, rows)
219
+ }
220
+
221
+ // clear
222
+ //
223
+ // Tell pty that terminal was cleared.
224
+ //
225
+ ptyp.clear = closure clear() {
226
+ ptySpawned.clear()
227
+ }
228
+
229
+ // kill
230
+ //
231
+ // Kill the spawned process.
232
+ //
233
+ ptyp.kill = closure kill(signame) {
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
+ }
243
+
244
+ // finis
245
+ ptyp
246
+ };
247
+
248
+
249
+ /*
250
+ * ptyManagerWorker
251
+ *
252
+ * Manager of pty processes on NodeJS worker threads.
253
+ *
254
+ */
255
+
256
+ closure ptyManagerWorker(local ptyw, mainContext, mainPty) {
257
+ global(PtyManager)
258
+ ptyw = new(object, this)
259
+
260
+ // mainPtyManager
261
+ //
262
+ // Return (a proxy to) the main thread PtyManager.
263
+ //
264
+ closure mainPtyManager(local result, error, main) {
265
+ // evalPtyManager - executed remotely on the main thread
266
+ function evalPtyManager() {
267
+ require("naanlib:frameworks/node/pty.nlg").PtyManager()
268
+ }
269
+
270
+ if !mainContext {
271
+ `(error, main) = require("naanlib:frameworks/running/remote_req.nlg").MainThread()
272
+ if !error
273
+ `(error, mainContext) = main.context()
274
+ if error
275
+ return (list(error))
276
+ }
277
+ result = mainContext.evalq(evalPtyManager(), `(evalPtyManager))
278
+ while tuple(result) && !result.0
279
+ result = result.1 // peel off the wrappers
280
+ if !result
281
+ return (list(Error("PtyManager failed")))
282
+ list(false, result)
283
+ }
284
+
285
+ // open
286
+ //
287
+ // Open the pty manager.
288
+ //
289
+ ptyw.open = closure open(local error) {
290
+ `(error, mainPty) = mainPtyManager()
291
+ if mainPty
292
+ `(error) = mainPty.open()
293
+ if error
294
+ list(error)
295
+ else
296
+ list(false, { ok: true })
297
+ }
298
+
299
+ // close
300
+ //
301
+ // Close our pty manager and kill off our spawned processes.
302
+ //
303
+ ptyw.close = closure close() {
304
+ if !mainContext
305
+ return
306
+
307
+ // ### kill our spawned processes
308
+
309
+ mainContext = false
310
+ }
311
+
312
+ // spawn
313
+ //
314
+ // Spawn a new pty process, returning a result tuple. If executable is omitted then a shell is
315
+ // spawned.
316
+ //
317
+ ptyw.spawn = closure spawn(executable, args, notify, options) {
318
+ if mainPty
319
+ mainPty.spawn(executable, args, notify, options)
320
+ else
321
+ list(Error("ptyManagerWorker:spawn: not open"))
322
+ }
323
+
324
+ // finis
325
+ ptyw
326
+ };
327
+
328
+
329
+ /*
330
+ * ptyInit
331
+ *
332
+ * Initialize the node module.
333
+ *
334
+ *
335
+ */
336
+
337
+ function ptyInit(local manifest) {
338
+ manifest = `(PtyManager, ptyManagerMain, ptyProcess, ptyManagerWorker, ptyInit)
339
+
340
+ Naan.module.build(module.id, "pty", function(modobj, compobj) {
341
+ require("./filesystem.nlg")
342
+ require("./worker.nlg")
343
+ compobj.manifest = manifest
344
+ modobj.exports.PtyManager = PtyManager
345
+ })
346
+ } ();
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2024-2025 by Richard C. Zulch
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
- exec("bash", ["-c", text], js.d) // default directory: js.d
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)