@naanlang/naan 1.4.4 → 1.6.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 (61) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +4 -4
  3. package/bin/index.js +25 -3
  4. package/dist/env_web.js +164 -22
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/browser.nlg +2 -2
  7. package/frameworks/browser/https_request.nlg +2 -2
  8. package/frameworks/browser/sworker.js +28 -26
  9. package/frameworks/browser/terminals.nlg +342 -700
  10. package/frameworks/browser/worker.nlg +255 -0
  11. package/frameworks/browser/workers.nlg +5 -5
  12. package/frameworks/browser/ws_client.nlg +15 -10
  13. package/frameworks/client/apiclient.nlg +204 -19
  14. package/frameworks/client/client.nlg +22 -2
  15. package/frameworks/client/psm_client.nlg +12 -3
  16. package/frameworks/client/relaycon.nlg +87 -161
  17. package/frameworks/common/events.nlg +101 -0
  18. package/frameworks/common/repltools.nlg +80 -1
  19. package/frameworks/node/apiserver.nlg +39 -246
  20. package/frameworks/node/filesystem.nlg +39 -12
  21. package/frameworks/node/gitter.nlg +9 -7
  22. package/frameworks/node/http_request.nlg +34 -13
  23. package/frameworks/node/https_request.nlg +8 -3
  24. package/frameworks/node/node.nlg +2 -0
  25. package/frameworks/node/pty.nlg +346 -0
  26. package/frameworks/node/shell.nlg +4 -4
  27. package/frameworks/node/worker.nlg +409 -539
  28. package/frameworks/node/ws_client.nlg +2 -2
  29. package/frameworks/project/build.nlg +1 -1
  30. package/frameworks/project/projects.nlg +2 -1
  31. package/frameworks/running/debugnub.nlg +17 -4
  32. package/frameworks/running/executors.nlg +27 -274
  33. package/frameworks/running/instances.nlg +393 -0
  34. package/frameworks/running/remote_req.nlg +143 -0
  35. package/frameworks/running/remote_res.nlg +89 -0
  36. package/frameworks/running/taskexec.nlg +2 -2
  37. package/frameworks/service/imp.nlg +736 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +105 -0
  40. package/frameworks/service/nubweb.nlg +106 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/dbt_pouch.nlg +26 -13
  43. package/frameworks/storage/psm.nlg +39 -5
  44. package/frameworks/storage/psm_dbtables.nlg +10 -2
  45. package/lib/browser/env_web.js +171 -29
  46. package/lib/browser/env_webworker.js +91 -5
  47. package/lib/core/naanlib.js +5 -5
  48. package/lib/env_node.js +184 -8
  49. package/lib/env_nodeworker.js +74 -1
  50. package/package.json +1 -1
  51. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
  52. package/plugins/serviceAws/aws_dynamo.nlg +1 -0
  53. package/plugins/serviceAws/aws_s3.nlg +3 -3
  54. package/plugins/serviceAws/aws_sqs.nlg +3 -1
  55. package/plugins/serviceAws/aws_utils.nlg +3 -1
  56. package/plugins/serviceAws/dbt_aws.nlg +22 -20
  57. package/plugins/serviceAws/psm_aws.nlg +2 -1
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_02_context.nlg +24 -1
  60. package/test/test_03_jsinterop.nlg +32 -3
  61. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -6,11 +6,16 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2024 by Richard C. Zulch
9
+ * Copyright (c) 2017-2026 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
13
13
 
14
+ // Globals
15
+
16
+ serviceMod;;
17
+
18
+
14
19
  /*
15
20
  * APIServer
16
21
  *
@@ -25,18 +30,21 @@
25
30
  * basePort: <number> // starting point for search
26
31
  * gateway: <string> // websocket gateway URL
27
32
  * statics: [<string>, ...] // search paths for static content
33
+ * debug: <boolean> // verbose logging
28
34
  * }
29
35
  *
30
36
  */
31
37
 
32
38
  closure APIServer(options, local server, chroot) {
33
- global(App, JSpath, js, bodyParser, express, psmServerMod, workerMod)
39
+ global(App, serviceMod, JSpath, js, bodyParser, express, psmServerMod, workerMod)
34
40
  apisLoadLibs()
35
41
  server = new(object, this)
42
+ server.imp = App.imp
36
43
  server.app = express()
37
44
  server.guid = options.guid
38
45
  server.notifyq = []
39
46
  server.responseq = []
47
+ server.instanceID = UUID()
40
48
  options = merge({
41
49
  relpath: ""
42
50
  name: "NaanServer"
@@ -94,6 +102,7 @@ closure APIServer(options, local server, chroot) {
94
102
  return (result)
95
103
  }
96
104
  updatePort(port)
105
+
97
106
  if server.guid
98
107
  server.oururl = server.oururl.concat("?guid=", server.guid)
99
108
  App.api = server // used remotely by client/relaycon.nlg
@@ -107,6 +116,7 @@ closure APIServer(options, local server, chroot) {
107
116
  }
108
117
  } else // default is native websocket protocols
109
118
  server.ws = WebSocketServer(server)
119
+
110
120
  WesRelay(server) // registers as subsystem
111
121
  server.psm = psmServerMod.MakeServerPSM(server, options.name)
112
122
  `(error, hostfs) = server.psm.conns.NodeFS.connect("Host", "NideFS", list(""))
@@ -116,7 +126,9 @@ closure APIServer(options, local server, chroot) {
116
126
  return (list(error))
117
127
  }
118
128
  server.hostfs = hostfs
119
- server.workco = workerMod.WorkerController(server)
129
+ server.workco = workerMod.WorkerController()
130
+ server.Register(server.workco.serverID, server.workco)
131
+
120
132
  list(false, port)
121
133
  }
122
134
 
@@ -431,15 +443,8 @@ closure WebSocketConn(wsgate, connID, local wsconn) {
431
443
  wsconn.msgin = closure msgin(message, local error) {
432
444
  if wsgate.guid && message.guid != wsgate.guid
433
445
  error = Error("unauthorized")
434
- else if message.init {
435
- if message.init.destID {
436
- wsconn.relay = true
437
- wsgate.subsystems.Relay.wsChangeDestID(conn, wsconn.info.destID, message.init.destID)
438
- }
446
+ else if message.init
439
447
  wsconn.info = merge(wsconn.info, message.init)
440
- }
441
- else if message.respOp && wsgate.subsystems.Relay
442
- wsgate.subsystems.Relay.wsReceive(message, wsconn) // with respOp, must be a relay
443
448
  else if message.clientID && wsgate.subsystems[message.serverID]
444
449
  wsgate.subsystems[message.serverID].wsReceive(message, wsconn)
445
450
  else
@@ -510,11 +515,11 @@ closure WebSocketServer(server, local websocks) {
510
515
  websocks.subsystems[serverID].wsOpen(conn) // notify of our openings
511
516
  })
512
517
 
513
- server.wss.on("error", function(error) {
518
+ websocks.wss.on("error", function(error) {
514
519
  ErrorDebuglog(Error("WebSocketServer: connection error", error))
515
520
  })
516
521
 
517
- server.wss.on("close", function() {
522
+ websocks.wss.on("close", function() {
518
523
  debuglog("WebSocketServer has closed")
519
524
  websocks.closed = true // note that we are closed
520
525
  })
@@ -557,37 +562,31 @@ closure WesCon(server, websocks, socket, request, local wescon) {
557
562
  destID: UUID()
558
563
  address: new(socket._socket.address())
559
564
  }
565
+
566
+ wescon.impconn = server.imp.responder(function(data) {
567
+ if wescon.closed
568
+ return
569
+ socket.send(JSONstringify(data)),
570
+ list(false, { sent: true })
571
+ }, {
572
+ secret: server.guid
573
+ label: "api-client"
574
+ })
560
575
 
561
576
  // on message
562
577
  //
563
578
  // Incoming data.
564
579
  //
565
- socket.on("message", closure onmessage(text, local message, error) {
566
- try {
567
- message = new(js.g.JSON.parse(text))
568
- if server.guid && message.guid != server.guid
569
- error = Error("unauthorized")
570
- else if message.init {
571
- if message.init.destID {
572
- wescon.relay = true
573
- websocks.subsystems.Relay.wsChangeDestID(wescon, wescon.info.destID, message.init.destID)
574
- }
575
- wescon.info = merge(wescon.info, message.init)
576
- }
577
- else if message.respOp && websocks.subsystems.Relay
578
- websocks.subsystems.Relay.wsReceive(message, wescon) // with respOp, must be a relay
579
- else if message.clientID && websocks.subsystems[message.serverID]
580
- websocks.subsystems[message.serverID].wsReceive(message, wescon)
581
- else
582
- error = Error("can't route message")
583
- } catch {
584
- if true
585
- error = Error("can't decode message JSON", exception)
580
+ socket.on("message", closure(text, local error, message) {
581
+ `(error, message) = JsonParse(text)
582
+ if !error {
583
+ message = new(message)
584
+ if wescon.impconn.dispatch(message)
585
+ return
586
586
  }
587
- if error
588
- socket.send(JSONstringify([error]))
587
+ debuglog("socket message:", text)
589
588
  })
590
-
589
+
591
590
  // on error
592
591
  //
593
592
  // Error has occurred on this connection.
@@ -607,222 +606,15 @@ closure WesCon(server, websocks, socket, request, local wescon) {
607
606
  code: code
608
607
  reason: reason
609
608
  }
609
+ wescon.impconn.destroy()
610
610
  websocks.closeConn(wescon)
611
611
  })
612
-
613
- // send
614
- //
615
- // Send a dictionary, which must have a clientID defined.
616
- //
617
- wescon.send = function send(message) {
618
- if !message.clientID || !message.serverID
619
- throw("Wescon.send: client and server IDs required")
620
- if wescon.closed
621
- list(Error("Socket connection closed", wescon.closed))
622
- socket.send(JSONstringify(message)),
623
- [false, {ok: true}]
624
- }
625
612
 
626
613
  // finis
627
614
  wescon
628
615
  };
629
616
 
630
617
 
631
- /*
632
- * WesRelay
633
- *
634
- * Web socket relay controller. This allows apiserver clients to discover each other and connect
635
- * for remote evaluation, etc. A source connection makes a request of a destination connection, and
636
- * the response is routed back to the original connection.
637
- *
638
- * This uses the following message fields for the requesting source:
639
- * guid: security token
640
- * clientID: defined by the source, this string defines where replies should be sent
641
- * serverID: "Relay"
642
- * destID: the destination connection
643
- * endServerID: the serverID to use at the destination
644
- * The message is modified with a virtual clientID to send to the destination, and then changed back
645
- * for return to the requester.
646
- *
647
- * To discover the available destination connections a source should use:
648
- * clientID: requester
649
- * serverID: "Relay"
650
- * relayOp: "getdests"
651
- * which returns:
652
- * relayOp: "destlist"
653
- * destinations: [
654
- * destID: guid for the connection
655
- * address: socket port / family / ip address
656
- * name: <string>
657
- * workerID: id of destination worker
658
- * ]
659
- * New and closed destinations are sent asynchronously to each source:
660
- * relayOp: "opened" / "closed"
661
- * info: {
662
- * destID: guid for the connection
663
- * address: socket port / family / ip address
664
- * name: <string>
665
- * workerID: id of destination worker
666
- * }
667
- *
668
- */
669
-
670
- closure WesRelay(server, local welay) {
671
- global()
672
- welay = new(object, this)
673
- welay.serverID = "Relay" // our serverID as a subsystem
674
- welay.destIDs = { } // destination conns index
675
- welay.virtualIDs = { } // virtual clientID index
676
- welay.links = new(weakmap) // source and dest connections
677
- server.Register(welay.serverID, welay)
678
-
679
- // getVirtualID
680
- // Return the virtual clientID for a requesting source/clientID and destination.
681
- function getVirtualID(sconn, clientID, dconn, local sdict, ddict, vid) {
682
- sdict = welay.links[sconn]
683
- if !sdict
684
- welay.links[sconn] = sdict = {
685
- virtualIDs: { }
686
- }
687
- ddict = welay.links[dconn]
688
- if !ddict
689
- welay.links[dconn] = ddict = {
690
- virtualIDs: { }
691
- }
692
- vid = ddict.virtualIDs[clientID]
693
- if !vid {
694
- sdict.virtualIDs[clientID] = vid = UUID()
695
- welay.virtualIDs[vid] = {
696
- sconn: sconn
697
- dconn: dconn
698
- clientID: clientID
699
- }
700
- }
701
- vid
702
- }
703
-
704
- // addConnection
705
- // Record a new connection as a possible destination.
706
- function addConnection(conn) {
707
- welay.destIDs[conn.info.destID] = conn
708
- }
709
-
710
- // removeConnection
711
- // Cleanup for a closed connection, be it source or destination or both.
712
- function removeConnection(conn, local vid, cdict, did, dconn) {
713
- welay.links[conn] = undefined
714
- for `(vid, cdict) in welay.virtualIDs
715
- if cdict.sconn === conn || cdict.dconn === conn
716
- welay.virtualIDs[vid] = undefined
717
- for `(did, dconn) in welay.destIDs
718
- if dconn === conn
719
- welay.destIDs[did] = undefined
720
- }
721
-
722
- // destList
723
- // Make an array of destination connections, excluding the specified one.
724
- //
725
- welay.destList = function destList(conn, local output, did, dconn) {
726
- output = []
727
- for `(did, dconn) in welay.destIDs
728
- if dconn !== conn && dconn.relay
729
- output.push(dconn.info)
730
- output
731
- }
732
-
733
- // wsReceive
734
- //
735
- // Process a message from the websocket API.
736
- //
737
- welay.wsReceive = function wsReceive(message, sconn, local dconn, error, cdict) {
738
- if message.relayOp == "getdests" { // get destination list
739
- message.relayOp = "destlist"
740
- message.destinations = destList(sconn)
741
- sconn.send(message)
742
- return
743
- }
744
- else if message.destID && message.endServerID { // request from a source
745
- dconn = welay.destIDs[message.destID]
746
- if !dconn
747
- error = Error("destID or endServerID invalid", message.relayOp)
748
- else {
749
- message.serverID = message.endServerID
750
- message.clientID = getVirtualID(sconn, message.clientID, dconn)
751
- message.endServerID = undefined
752
- message.destID = undefined
753
- dconn.send(message)
754
- return
755
- }
756
- }
757
- else { // response from a destination
758
- cdict = welay.virtualIDs[message.clientID]
759
- dconn = cdict.sconn // this is original source
760
- if !cdict || !dconn
761
- error = Error("cannot route reply", message.relayOp)
762
- else {
763
- message.endServerID = message.serverID
764
- message.serverID = welay.serverID
765
- message.clientID = cdict.clientID
766
- message.destID = dconn.info.destID
767
- dconn.send(message)
768
- return
769
- }
770
- }
771
- sconn.send({
772
- serverID: message.serverID
773
- clientID: message.clientID
774
- respOp: "error"
775
- payload: [error]
776
- })
777
- }
778
-
779
- // wsOpen
780
- //
781
- // Note that a new connection was opened.
782
- //
783
- welay.wsOpen = function wsOpen(conn) {
784
- addConnection(conn)
785
- }
786
-
787
- // wsClose
788
- //
789
- // Note that an existing connection was closed.
790
- //
791
- welay.wsClose = function wsClose(conn, local destID, sconn) {
792
- removeConnection(conn)
793
- if conn.relay
794
- for `(destID, sconn) in welay.destIDs
795
- sconn.send({
796
- serverID: welay.serverID
797
- clientID: "*"
798
- relayOp: "closed"
799
- info: conn.info
800
- })
801
- }
802
-
803
- // wsChangeDestID
804
- //
805
- // The destID has changed (via the client init message.)
806
- //
807
- welay.wsChangeDestID = function wsChangeDestID(conn, prevID, newID, local destID, sconn) {
808
- welay.destIDs[prevID] = undefined
809
- welay.destIDs[newID] = conn
810
- conn.info.destID = newID
811
- for `(destID, sconn) in welay.destIDs
812
- if destID != newID
813
- sconn.send({
814
- serverID: welay.serverID
815
- clientID: "*"
816
- relayOp: "opened"
817
- info: conn.info
818
- })
819
- }
820
-
821
- // finis
822
- welay
823
- };
824
-
825
-
826
618
  /*
827
619
  * ApisInit
828
620
  *
@@ -840,7 +632,7 @@ function apisLoadLibs() {
840
632
  };
841
633
 
842
634
  function ApisInit(local manifest) {
843
- manifest = `(APIServer, WebSocketGatewayServer, WebSocketConn, WebSocketServer, WesCon, WesRelay,
635
+ manifest = `(APIServer, WebSocketGatewayServer, WebSocketConn, WebSocketServer, WesCon,
844
636
  apisLoadLibs, ApisInit)
845
637
 
846
638
  Naan.module.build(module.id, "apiserver", function(modobj, compobj) {
@@ -851,4 +643,5 @@ function ApisInit(local manifest) {
851
643
  require("./filesystem.nlg")
852
644
  psmServerMod = require("./psm_server.nlg")
853
645
  workerMod = require("./worker.nlg")
646
+ serviceMod = require("naanlib:frameworks/service/imp.nlg")
854
647
  }();
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2022 by Richard C. Zulch
9
+ * Copyright (c) 2017-2026 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -977,16 +977,41 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
977
977
  //
978
978
  // shellOpen
979
979
  //
980
- // Execute the MacOS open utility, returning a standard tuple: `(error, result)
980
+ // Execute the platform's open utility, returning a standard tuple: `(error, result) which opens
981
+ // the specified path as a browser URL or invokes a shell script. This is used by the NaaN build
982
+ // system's "run" function.
983
+ // The args option has a dictionary that can override the "runner" utility and set arguments
984
+ // for specific platform(s):
985
+ // {
986
+ // darwin: {
987
+ // runner: "open" // default for platforms not mentioned
988
+ // // args: ["--args", <prog arg>] // program arguments if desired
989
+ // }
990
+ // linux / android: {
991
+ // runner: "bash" // for <path>.sh or <path>.tool
992
+ // runner: "xdg-open" // all other types
993
+ // }
994
+ // }
981
995
  //
982
- files.shellOpen = closure shellOpen(path, args, callback) {
996
+ files.shellOpen = closure shellOpen(path, args, callback, local platform, runner) {
983
997
  if !callback
984
998
  return (syncAdapter(shellOpen, path, args))
985
- if args
986
- args = [path].concat("--args", args) // only add target args if specified
999
+ platform = js.g.process.platform
1000
+ runner = args[platform].runner
1001
+ if runner
1002
+ { } // explicit runner
1003
+ else if platform == "linux" || platform == "android" {
1004
+ if RegExp("[.]((sh)|(tool))$").test(path)
1005
+ runner = "bash" // execute scripts
1006
+ else
1007
+ runner = "xdg-open" // "open" other things
1008
+ } else
1009
+ runner = "open" // default runner
1010
+ if args[platform]
1011
+ args = [path].concat(args[platform]) // has custom args
987
1012
  else
988
1013
  args = [path]
989
- nodecp.execFile("open",
1014
+ nodecp.execFile(runner,
990
1015
  args, // command line args to open
991
1016
  { }, // execution options
992
1017
  function(err, stdout, stderr) {
@@ -1041,6 +1066,8 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1041
1066
  cmdargs.push(options.pattern)
1042
1067
  cmdargs.push(JSpath.resolve(rootpath, localpath))
1043
1068
  execOptions = { cmdargs: cmdargs }
1069
+ if js.g.process.platform == "win32" // to make "-i" work on Windows
1070
+ execOptions.spawnops = { env: merge(js.g.process.env, { LC_ALL: 'en_US.UTF-8' }) }
1044
1071
  result = []
1045
1072
  lineRE = RegExp("([^:]*):([0-9]+):(.*)$") // <path>:<line-number>:<matching-text-line>
1046
1073
  if integer(options.maxCount)
@@ -1075,7 +1102,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1075
1102
  // has completed. Options are:
1076
1103
  // {
1077
1104
  // cmdargs: -- array of string arguments to the command (see below)
1078
- // execops: -- optional exec options like cwd
1105
+ // spawnops: -- optional spawn options like cwd
1079
1106
  // errproc: -- callback for stderr output lines, or false
1080
1107
  // errLE: -- string or RegExp for stderr line ending
1081
1108
  // outproc: -- optional callback for stdout output lines
@@ -1100,7 +1127,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1100
1127
  //
1101
1128
 
1102
1129
  files.execLines = closure execLines(cmdname, options, callback,
1103
- local output, execops, spawn, linerErr, linerOut) {
1130
+ local output, spawnops, spawn, linerErr, linerOut) {
1104
1131
  if !callback
1105
1132
  return (syncAdapter(execLines, cmdname, options))
1106
1133
  if !string(cmdname) || cmdname == ""
@@ -1109,11 +1136,11 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1109
1136
  output = options.output
1110
1137
  else
1111
1138
  output = {}
1112
- if options.execops
1113
- execops = options.execops
1139
+ if options.spawnops
1140
+ spawnops = options.spawnops
1114
1141
  else
1115
- execops = { }
1116
- spawn = nodecp.spawn(cmdname, options.cmdargs, execops)
1142
+ spawnops = { }
1143
+ spawn = nodecp.spawn(cmdname, options.cmdargs, spawnops)
1117
1144
 
1118
1145
  // completed
1119
1146
  // execute callback only once
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2021 by Richard C. Zulch
9
+ * Copyright (c) 2020-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -56,7 +56,7 @@ closure Gitter(fs, local gitt) {
56
56
  asyncResult(callback, false, gitt.roots[repopath])
57
57
  execOptions = {
58
58
  cmdargs: ["rev-parse", "--show-toplevel"]
59
- execops: {
59
+ spawnops: {
60
60
  cwd: repopath
61
61
  }
62
62
  outLE: "\x00" // lines delimited by nulls
@@ -117,7 +117,7 @@ closure Gitter(fs, local gitt) {
117
117
  repopath = fs.path.resolve(fs.rootpath, repopath)
118
118
  execOptions = {
119
119
  cmdargs: ["log", "-z", "--pretty=format:%H|%an|%ae|%aI|%B"]
120
- execops: {
120
+ spawnops: {
121
121
  cwd: repopath
122
122
  }
123
123
  outLE: "\x00" // lines delimited by nulls
@@ -168,10 +168,12 @@ closure Gitter(fs, local gitt) {
168
168
  if !string(repopath)
169
169
  return (asyncResult(callback, Error("invalid path:", repopath)))
170
170
  repopath = fs.path.resolve(fs.rootpath, repopath)
171
- bridge = bridgePath(repopath)
171
+ bridge = fs.path.normalize(bridgePath(repopath)) // ensure it works for our FS
172
+ if bridge == "."
173
+ bridge = "" // no bridge in this case
172
174
  execOptions = {
173
175
  cmdargs: ["status", "-z", "--porcelain"]
174
- execops: {
176
+ spawnops: {
175
177
  cwd: repopath
176
178
  }
177
179
  outLE: "\x00" // lines delimited by nulls
@@ -219,13 +221,13 @@ closure Gitter(fs, local gitt) {
219
221
  repopath = fs.path.resolve(fs.rootpath, repopath)
220
222
  execOptions = {
221
223
  cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), gitPath(options.pathspec))]
222
- execops: {
224
+ spawnops: {
223
225
  cwd: repopath
224
226
  }
225
227
  }
226
228
  nodecp.execFile("git",
227
229
  execOptions.cmdargs,
228
- execOptions.execops,
230
+ execOptions.spawnops,
229
231
  function(err, stdout, stderr) {
230
232
  if err
231
233
  callback(Error("git failed", err, stderr))
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2024-2025 by Richard C. Zulch
8
+ * Copyright (c) 2024-2026 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -19,8 +19,9 @@
19
19
  * {
20
20
  * method: <string> // HTTP method (defaults to GET)
21
21
  * headers: <dictionary> // key/data pairs
22
- * putdata: <data> // data to put
22
+ * putdata: <data> // data to put, either string or JSON
23
23
  * json: <boolean> // ignore content-type of return and decode JSON
24
+ * debug: <boolean> // log errors and status results
24
25
  * }
25
26
  *
26
27
  * The return is a result tuple comprising:
@@ -34,13 +35,31 @@
34
35
  */
35
36
 
36
37
  closure HttpRequest(url, options,
37
- local pending, chunks, params, request) {
38
+ local host, pending, chunks, params, request, headers, putdata) {
38
39
  global(js, nodeHttp)
40
+ try {
41
+ host = xnew(js.g.URL, url).host
42
+ if host.length < 3
43
+ return (Error("httpRequest: host empty"))
44
+ } catch {
45
+ return (Error("httpRequest:", exception))
46
+ }
39
47
  pending = new(nonce)
40
48
  chunks = []
49
+ headers = new(options.headers || []) // modifiable copy or false
50
+ headers.push(list("Host", host))
51
+ putdata = options.putdata
52
+ if putdata {
53
+ if !string(putdata) {
54
+ putdata = JSONstringify(putdata)
55
+ headers.push(list('Content-Type', 'application/json'))
56
+ }
57
+ // set explicit content-length to avoid default chunked encoding
58
+ headers.push(list('Content-Length', putdata.length))
59
+ }
41
60
  params = {
42
61
  method: options.method || undefined
43
- headers: options.headers || undefined
62
+ headers: headers || undefined
44
63
  }
45
64
  request = nodeHttp.request(url, params, closure (response) {
46
65
  response.on("data", function (chunk) {
@@ -49,7 +68,12 @@ closure HttpRequest(url, options,
49
68
  response.on("end", function (local content, error) {
50
69
  content = js.g.Buffer.concat(chunks)
51
70
  if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
52
- debuglog("httpRequest status:", url, response.statusCode, "message:", content)
71
+ if options.debug {
72
+ debuglog("httpRequest status:", url, response.statusCode, "message:", response.statusMessage)
73
+ if string(putdata)
74
+ putdata = putdata.substr(0,250)
75
+ debuglog("httpRequest putdata:", putdata)
76
+ }
53
77
  pending.signal(list(Error("httpRequest statusCode:", url, response.statusCode, {
54
78
  status: response.statusCode
55
79
  }), false, { status: response.statusCode }))
@@ -63,23 +87,20 @@ closure HttpRequest(url, options,
63
87
  else
64
88
  error = Error("httpRequest terminated prematurely:", url, response.statusCode)
65
89
  pending.signal(list(error, content, {
66
- headers: response.headers
90
+ headers: new(response.headers)
67
91
  status: response.statusCode
68
92
  contentType: response.headers["content-type"]
69
93
  }))
70
94
  })
71
95
  })
72
96
  request.on("error", function hre(error) {
73
- debuglog("httpRequest error:", url, "error:", error)
97
+ if options.debug
98
+ debuglog("httpRequest error:", url, "error:", error)
74
99
  error = Error("httpRequest failed:", url, error)
75
100
  pending.signal(list(error))
76
101
  })
77
- if options.putdata {
78
- if string(options.putdata)
79
- request.write(options.putdata)
80
- else
81
- request.write(JSONstringify(options.putdata))
82
- }
102
+ if putdata
103
+ request.write(putdata)
83
104
  request.end()
84
105
  pending.wait()
85
106
  };
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021-2024 by Richard C. Zulch
8
+ * Copyright (c) 2021-2026 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -123,12 +123,17 @@ closure httpsApiReqOnce(url, options,
123
123
  else
124
124
  content = js.g.Buffer.concat(chunks)
125
125
  if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
126
- if options.debug
126
+ if options.debug {
127
+ content = content.toString()
127
128
  debuglog("HttpsApiRequest status:", url, response.statusCode, "message:", content)
129
+ if string(putdata)
130
+ putdata = putdata.substr(0,250)
131
+ debuglog("HttpsApiRequest putdata:", putdata)
132
+ }
128
133
  pending.signal(list(Error("HttpsApiRequest statusCode:", url, response.statusCode, {
129
134
  status: response.statusCode
130
135
  }), false, {
131
- headers: response.headers
136
+ headers: new(response.headers)
132
137
  status: response.statusCode
133
138
  }))
134
139
  return
@@ -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
+ commonWatching = require("../common/watching.nlg")
127
128
  compobj.manifest = manifest
128
129
  function nodrReload() {
129
130
  nodecp = js.r("child_process")
@@ -139,5 +140,6 @@ function nodrInit(local manifest) {
139
140
  module.reload = nodrReload
140
141
  module.exports.NaanlangDir = NaanlangDir
141
142
  module.exports.NaanlangRC = NaanlangRC
143
+ module.exports.NodeStreamLiner = NodeStreamLiner
142
144
  })
143
145
  } ();