@naanlang/naan 1.4.4 → 1.5.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.
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2020-2023 by Richard C. Zulch
9
+ * Copyright (c) 2020-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -33,7 +33,7 @@ closure FileReader(file, progress, local pending, freader, result) {
33
33
  freader.addEventListener("error", function(error) {
34
34
  pending.signal(list(error))
35
35
  })
36
- freader.AddEventListener("progress", function(event) {
36
+ freader.addEventListener("progress", function(event) {
37
37
  if event.lengthComputable && event.total
38
38
  progress(toint((event.loaded * 100) / event.total))
39
39
  else
@@ -36,7 +36,7 @@
36
36
  *
37
37
  */
38
38
 
39
- var CurrentCacheName = "Naanlang-1.4.4+1";
39
+ var CurrentCacheName = "Naanlang-1.5.0+1";
40
40
 
41
41
 
42
42
  //
@@ -92,18 +92,18 @@ var terminated; // flag that this service worker
92
92
  var pubVersion = event.data.hereIsMyVersion;
93
93
  var sourceId = event.source.id; // client id of sender of message
94
94
  msgPorts[sourceId] = msgport;
95
- console.log("[1.4.4+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
- if (pubVersion != "1.4.4+1") {
95
+ console.log("[1.5.0+1] received new msgport for", sourceId, pubID, "-", pubVersion);
96
+ if (pubVersion != "1.5.0+1") {
97
97
  if (upgradeMsgPorts) { // if not activated
98
- console.log("[1.4.4+1] update pending for", sourceId);
98
+ console.log("[1.5.0+1] update pending for", sourceId);
99
99
  upgradeMsgPorts[sourceId] = msgport;
100
100
  }
101
101
  else {
102
102
  msgport.postMessage({ // notify new version available
103
103
  id: "upgrade",
104
- version: "1.4.4+1"
104
+ version: "1.5.0+1"
105
105
  });
106
- console.log("[1.4.4+1] update sent for:", sourceId);
106
+ console.log("[1.5.0+1] update sent for:", sourceId);
107
107
  }
108
108
  }
109
109
  Reaper(); // clean up obsolete info
@@ -134,10 +134,10 @@ var terminated; // flag that this service worker
134
134
  msgport.onmessage = function(msg) {
135
135
  msg = msg.data;
136
136
  if (msg.id == "skipWaiting") { // try to activate this SW now
137
- console.log("[1.4.4+1] attempting skipWaiting");
137
+ console.log("[1.5.0+1] attempting skipWaiting");
138
138
  self.skipWaiting();
139
139
  } else if (msg.id == "terminate") { // termiante this SW now
140
- console.log("[1.4.4+1] terminated");
140
+ console.log("[1.5.0+1] terminated");
141
141
  terminate();
142
142
  } else if (msg.id == "abortURL") { // abort an I/O transaction
143
143
  var href = new URL(msg.url).href;
@@ -150,7 +150,7 @@ var terminated; // flag that this service worker
150
150
  } else if (msg.id == "response") // response from fetch request
151
151
  processResponse(msg);
152
152
  else if (msg.id == "text") // just log some text
153
- console.log("[1.4.4+1] msg received:", msg.text);
153
+ console.log("[1.5.0+1] msg received:", msg.text);
154
154
  };
155
155
 
156
156
  // send text to IDE log
@@ -161,7 +161,7 @@ var terminated; // flag that this service worker
161
161
  // don't clutter the log
162
162
  msgport.postMessage({
163
163
  id: "text",
164
- text: "port received by 1.4.4+1",
164
+ text: "port received by 1.5.0+1",
165
165
  });
166
166
  */
167
167
  if (--pending === 0)
@@ -178,7 +178,7 @@ var terminated; // flag that this service worker
178
178
  includeUncontrolled: true
179
179
  }).then(function(clientList) {
180
180
  clientList.every(function(client) {
181
- console.log("[1.4.4+1] requesting new msgport for", client.id);
181
+ console.log("[1.5.0+1] requesting new msgport for", client.id);
182
182
  ++pending;
183
183
  client.postMessage({ // tell client(s) we need this fetch source
184
184
  msg: "Naan_need_fetch_port",
@@ -224,12 +224,12 @@ function Reaper() {
224
224
  clients[clientList[clidex].id] = clientList[clidex];
225
225
  for (var sourceId in msgPorts)
226
226
  if (!clients[sourceId]) {
227
- console.log("[1.4.4+1] source gone:", sourceId);
227
+ console.log("[1.5.0+1] source gone:", sourceId);
228
228
  delete msgPorts[sourceId]; // no longer a source
229
229
  }
230
230
  for (var clientId in fetchPorts)
231
231
  if (!clients[clientId]) {
232
- console.log("[1.4.4+1] client gone:", clientId);
232
+ console.log("[1.5.0+1] client gone:", clientId);
233
233
  delete fetchPorts[clientId]; // no longer a client
234
234
  }
235
235
  for (var fqdex = 0; fqdex < fetchQueue.length; ++fqdex) {
@@ -263,16 +263,16 @@ function ClearCaches() {
263
263
  return (Promise.all(
264
264
  cacheNames.map(function(cacheName) {
265
265
  if (cacheName != CurrentCacheName) {
266
- console.log('[1.4.4+1] deleting old cache:', cacheName);
266
+ console.log('[1.5.0+1] deleting old cache:', cacheName);
267
267
  return (caches.delete(cacheName));
268
268
  }
269
269
  })
270
270
  ));
271
271
  }).then(function() { // claim all clients
272
- console.log('[1.4.4+1] claiming clients');
272
+ console.log('[1.5.0+1] claiming clients');
273
273
  return (self.clients.claim());
274
274
  }).then(function() {
275
- console.log('[1.4.4+1] clients claimed');
275
+ console.log('[1.5.0+1] clients claimed');
276
276
  return (Promise.resolve(true));
277
277
  });
278
278
  return (promise);
@@ -315,7 +315,7 @@ function GetClientResponse(event, urlpath) {
315
315
  msgport.postMessage({
316
316
  id: "fetch",
317
317
  seq: seqno,
318
- version: "1.4.4+1",
318
+ version: "1.5.0+1",
319
319
  request: {
320
320
  method: event.request.method,
321
321
  url: event.request.url
@@ -363,7 +363,7 @@ function GetClientResponse(event, urlpath) {
363
363
  */
364
364
 
365
365
  self.addEventListener('install', function(event) {
366
- console.log("[1.4.4+1] install");
366
+ console.log("[1.5.0+1] install");
367
367
  self.skipWaiting();
368
368
  });
369
369
 
@@ -376,20 +376,20 @@ self.addEventListener('install', function(event) {
376
376
  */
377
377
 
378
378
  self.addEventListener('activate', function(event) {
379
- console.log("[1.4.4+1] activate");
379
+ console.log("[1.5.0+1] activate");
380
380
  self.clients.matchAll({ // for debugging, list controlled clients
381
381
  includeUncontrolled: true
382
382
  }).then(function(clientList) {
383
383
  var urls = clientList.map(function(client) {
384
384
  return (client.url);
385
385
  });
386
- console.log('[1.4.4+1] matching clients:', urls.join(', '));
386
+ console.log('[1.5.0+1] matching clients:', urls.join(', '));
387
387
  });
388
388
  var promise = ClearCaches().then(function() {
389
389
  for (var sourceId in upgradeMsgPorts) {
390
390
  upgradeMsgPorts[sourceId].postMessage({ // notify new version available
391
391
  id: "upgrade",
392
- version: "1.4.4+1"
392
+ version: "1.5.0+1"
393
393
  });
394
394
  }
395
395
  upgradeMsgPorts = false;
@@ -421,7 +421,7 @@ self.addEventListener('fetch', function(event) {
421
421
  var promise;
422
422
  var url = new URL(request.url);
423
423
  var nocache = request.method != "GET"
424
- || url.search.length !== 0 && url.searchParams.get("naanver") !== "3a2321ac98091606ee7cf5cc8919f70c"
424
+ || url.search.length !== 0 && url.searchParams.get("naanver") !== "1df9163d801364d0cae89b149cc24ef5"
425
425
  || request.headers.get('range');
426
426
  if (url.pathname.startsWith("/run/")) {
427
427
  nocache = true;
@@ -455,7 +455,7 @@ self.addEventListener('fetch', function(event) {
455
455
  statusText: "Request Cancelled"
456
456
  }));
457
457
  }
458
- console.log("[1.4.4+1] fetch failed", request.url, e);
458
+ console.log("[1.5.0+1] fetch failed", request.url, e);
459
459
  return (new Response(undefined, {
460
460
  status: 404,
461
461
  statusText: "Fetch Failed"
@@ -562,7 +562,7 @@ closure TermLocal(track, name, options, local target, nlregex, listener)
562
562
  msg.data._guid = target.guid
563
563
  js.t.DispatchMessage(msg.data)
564
564
  }
565
- }
565
+ }
566
566
 
567
567
  //
568
568
  // target.DebugCmd
@@ -764,14 +764,18 @@ closure WorkerToMain(local nideRunning, track) {
764
764
  /*
765
765
  * findIDEtarget
766
766
  *
767
- * Find a target for our IDE that matches the specified name, workerID, and naancont.
767
+ * Find a target for our IDE that matches the specified name, workerID, and naancont. Either the
768
+ * title or the naancont must match. The title will match but not the naancont if the window was
769
+ * closed/reloaded but we didn't get notified because browsers are annoying that way. Alternatively
770
+ * the naancont will match but not the title if we have updated the version.
768
771
  *
769
772
  */
770
773
 
771
- closure findIDEtarget(track, name, workerID, naancont, local target) {
774
+ closure findIDEtarget(track, name, workerID, title, naancont, local target) {
772
775
  global()
773
776
  for target in track.enumlist()
774
- if target.name == name && target.workerID == workerID && (!target.naancont || target.naancont === naancont)
777
+ if target.name == name && target.workerID == workerID
778
+ && (target.title == title || !target.naancont || target.naancont === naancont)
775
779
  return (target)
776
780
  false
777
781
  };
@@ -788,8 +792,10 @@ closure findIDEtarget(track, name, workerID, naancont, local target) {
788
792
 
789
793
  closure termIDE(track, name, workerID, naancont, title, local target, connected) {
790
794
  global(js, gWorkers)
791
- target = findIDEtarget(track, name, workerID, naancont)
795
+ target = findIDEtarget(track, name, workerID, title, naancont)
792
796
  if target { // new naancont on existing target
797
+ if target.naancont && naancont !== target.naancont
798
+ target.targetClose()
793
799
  target.updateController(naancont)
794
800
  target.title = title
795
801
  track.update(target)
@@ -934,7 +940,7 @@ closure termIDE(track, name, workerID, naancont, title, local target, connected)
934
940
  //
935
941
  // Mark the target window as closed.
936
942
  //
937
- function targetClose(local conn) {
943
+ target.targetClose = function targetClose(local conn) {
938
944
  for conn in connected {
939
945
  conn.db.detach(target)
940
946
  conn.WriteLn("\x1b[90m\x1b[3m".concat("\ntarget closed", "\x1b[0m"))
@@ -1012,12 +1018,12 @@ closure termInstallVirtualWatcher(track, naancont) {
1012
1018
  target.attention()
1013
1019
  }
1014
1020
  else if msg.op == "VsiteClose" {
1015
- target = findIDEtarget(track, msg.name, "NaanVsite", msg.naancont)
1021
+ target = findIDEtarget(track, msg.name, "NaanVsite", msg.title, msg.naancont)
1016
1022
  if target
1017
1023
  target.updateController()
1018
1024
  }
1019
1025
  else if msg.op == "VsiteVisibilityChange" {
1020
- target = findIDEtarget(track, msg.name, "NaanVsite", msg.naancont)
1026
+ target = findIDEtarget(track, msg.name, "NaanVsite", msg.title, msg.naancont)
1021
1027
  if target
1022
1028
  target.setCheckStatus(msg.hidden, msg.window)
1023
1029
  }
@@ -2,41 +2,50 @@
2
2
  * apiclient.nlg
3
3
  * Naanlib/frameworks/client
4
4
  *
5
- * Access to Nide API with browser HTTP client.
5
+ * Access to Nide API with specified HTTP client.
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2024 by Richard C. Zulch
9
+ * Copyright (c) 2017-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
13
13
 
14
14
  /*
15
- * NideAPIclient
15
+ * APIClient
16
16
  *
17
- * Make an API client object for browsers.
17
+ * Make an API client object.
18
+ *
19
+ * Options:
20
+ * {
21
+ * url: <string> // hostname or URL of the remote server
22
+ * guid: <string> // shared secret for access to remote server
23
+ * instanceID: <string> // optional UUID for our client; can be persisted by caller
24
+ * }
18
25
  *
19
26
  */
20
27
 
21
- closure NideAPIclient(url, guid, apiRequester, local client) {
22
- global()
28
+ closure APIClient(options, local client, url, guid) {
29
+ global(App)
23
30
  client = new(object, this)
31
+ url = options.url
24
32
  if !url.match(RegExp("^https?:\/\/", "i"))
25
33
  url = "http://${url}" // a protocol is required
26
34
  if url.slice(-1) != "/"
27
35
  url = url.concat("/") // must end in "/"
28
36
  client.url = url
29
- client.guid = guid
37
+ client.guid = options.guid
38
+ client.instanceID = options.instanceID || UUID() // our client's unique ID
30
39
  client.notifyAfter = 0
31
- client.instanceID = UUID() // our client's unique ID
40
+ client.requester = clirRequester()
32
41
 
33
42
  // clientRequest() - append our guid to the headers
34
43
 
35
- closure clientRequest(url, options) {
36
- options = merge(options)
37
- if guid
38
- options.headers = [list("x-naanlang-api-guid", guid)]
39
- apiRequester(url, options)
44
+ closure clientRequest(url, reqops) {
45
+ reqops = merge(reqops)
46
+ if options.guid
47
+ reqops.headers = [list("x-naanlang-api-guid", options.guid)]
48
+ client.requester(url, reqops)
40
49
  }
41
50
 
42
51
  //
@@ -57,7 +66,7 @@ closure NideAPIclient(url, guid, apiRequester, local client) {
57
66
  timeout: timeoutms
58
67
  after: client.notifyAfter
59
68
  })
60
- `(error, content, extra) = clientRequest(url.concat(query))
69
+ `(error, content, extra) = clientRequest(url.concat(query), { retrier: false }) // don't retry inside
61
70
  if (error || extra.status != 200)
62
71
  cbStatus()
63
72
  else if (length(content) == 0)
@@ -76,8 +85,12 @@ closure NideAPIclient(url, guid, apiRequester, local client) {
76
85
  // get
77
86
  //
78
87
 
79
- client.get = closure get(path, options, local error, content, extra) {
80
- `(error, content, extra) = clientRequest(url.concat(path))
88
+ client.get = closure get(path, options, local requrl, error, content, extra) {
89
+ if App.cache
90
+ requrl = url.concat(EncodeQuery(path.concat("?"), { naanver: App.cache }))
91
+ else
92
+ requrl = url.concat(path)
93
+ `(error, content, extra) = clientRequest(requrl)
81
94
  if !error && extra.status != 200
82
95
  error = Error("readFile failed: ".concat(extra.status), {
83
96
  status: extra.status
@@ -153,12 +166,12 @@ closure NideAPIclient(url, guid, apiRequester, local client) {
153
166
 
154
167
  function apicInit(local manifest) {
155
168
 
156
- manifest = `(NideAPIclient, apicInit)
169
+ manifest = `(APIClient, apicInit)
157
170
 
158
171
  Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
159
172
  Naan.module.require("./client.nlg")
160
173
  compobj.manifest = manifest
161
- modobj.exports.NideAPIclient = NideAPIclient
174
+ modobj.exports.APIClient = APIClient
162
175
  })
163
176
 
164
177
  } ();
@@ -6,11 +6,31 @@
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
 
13
13
 
14
+ /*
15
+ * clirRequester
16
+ *
17
+ * Return an appropriate HttpsRequestApi client based on our environment.
18
+ *
19
+ */
20
+
21
+ clirRequestApi = false;
22
+
23
+ function clirRequester() {
24
+ if !clirRequestApi {
25
+ if js.w
26
+ clirRequestApi = require("naanlib:frameworks/browser/https_request.nlg").HttpsApiRequest
27
+ else
28
+ clirRequestApi = require("naanlib:frameworks/node/https_request.nlg").HttpsApiRequest
29
+ }
30
+ clirRequestApi
31
+ };
32
+
33
+
14
34
  /*
15
35
  * clirInit
16
36
  *
@@ -20,7 +40,7 @@
20
40
  */
21
41
 
22
42
  function clirInit(local manifest) {
23
- manifest = `(clirInit)
43
+ manifest = `(clirRequester, clirInit)
24
44
 
25
45
  Naan.module.build(module.id, "client", function(modobj, compobj) {
26
46
  require("../common/").LiveImport()
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2024 by Richard C. Zulch
9
+ * Copyright (c) 2019-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -633,8 +633,17 @@ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
633
633
  api = connector.apis[creds]
634
634
  else {
635
635
  api = connector.apis[creds.urlName]
636
- if !api // get API to remote server
637
- connector.apis[creds.urlName] = api = NideAPIclient(creds.urlName, creds.authSecret)
636
+ if !api { // create API to remote server
637
+ connector.apis[creds.urlName] = api = APIClient({
638
+ url: creds.urlName
639
+ guid: creds.authSecret
640
+ instanceID: creds.instanceID
641
+ })
642
+ if creds.instanceID != api.instanceID { // update our client instanceID
643
+ creds.instanceID = api.instanceID
644
+ `(error) = connector.vault.updateResource(resID, creds)
645
+ }
646
+ }
638
647
  }
639
648
  }
640
649
  if error
@@ -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-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -1041,6 +1041,8 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1041
1041
  cmdargs.push(options.pattern)
1042
1042
  cmdargs.push(JSpath.resolve(rootpath, localpath))
1043
1043
  execOptions = { cmdargs: cmdargs }
1044
+ if js.g.process.platform == "win32" // to make "-i" work on Windows
1045
+ execOptions.spawnops = { env: merge(js.g.process.env, { LC_ALL: 'en_US.UTF-8' }) }
1044
1046
  result = []
1045
1047
  lineRE = RegExp("([^:]*):([0-9]+):(.*)$") // <path>:<line-number>:<matching-text-line>
1046
1048
  if integer(options.maxCount)
@@ -1075,7 +1077,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1075
1077
  // has completed. Options are:
1076
1078
  // {
1077
1079
  // cmdargs: -- array of string arguments to the command (see below)
1078
- // execops: -- optional exec options like cwd
1080
+ // spawnops: -- optional spawn options like cwd
1079
1081
  // errproc: -- callback for stderr output lines, or false
1080
1082
  // errLE: -- string or RegExp for stderr line ending
1081
1083
  // outproc: -- optional callback for stdout output lines
@@ -1100,7 +1102,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1100
1102
  //
1101
1103
 
1102
1104
  files.execLines = closure execLines(cmdname, options, callback,
1103
- local output, execops, spawn, linerErr, linerOut) {
1105
+ local output, spawnops, spawn, linerErr, linerOut) {
1104
1106
  if !callback
1105
1107
  return (syncAdapter(execLines, cmdname, options))
1106
1108
  if !string(cmdname) || cmdname == ""
@@ -1109,11 +1111,11 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
1109
1111
  output = options.output
1110
1112
  else
1111
1113
  output = {}
1112
- if options.execops
1113
- execops = options.execops
1114
+ if options.spawnops
1115
+ spawnops = options.spawnops
1114
1116
  else
1115
- execops = { }
1116
- spawn = nodecp.spawn(cmdname, options.cmdargs, execops)
1117
+ spawnops = { }
1118
+ spawn = nodecp.spawn(cmdname, options.cmdargs, spawnops)
1117
1119
 
1118
1120
  // completed
1119
1121
  // 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))
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2024 by Richard C. Zulch
9
+ * Copyright (c) 2024-2025 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -68,9 +68,9 @@ closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
68
68
  }
69
69
  }
70
70
  if cwd
71
- xlparams.execops = { cwd: cwd }
71
+ xlparams.spawnops = { cwd: cwd }
72
72
  else if options.cwd
73
- xlparams.execops = { cwd: options.cwd }
73
+ xlparams.spawnops = { cwd: options.cwd }
74
74
  fs.execLines(cmd, xlparams, function(error, result) {
75
75
  evalactive(list(`assign, `(compress, "$$"), report)) // assign to $$ in active namespace
76
76
  pending.signal(list(error, merge(result, report)))
@@ -80,7 +80,7 @@ closure ShellConnect(options, local shob, error, fs, xparser, shelly) {
80
80
 
81
81
  // shellparser
82
82
  // intercept command-line parsing and look for a !shell escape
83
- function shellparser(source, grammar, operators, local token) {
83
+ function shellparser(source, grammar, operators, local token, text) {
84
84
  token = source.tokenread()
85
85
  if token.atom == `! {
86
86
  source.tokenlast(true) // "consume" the ! token
@@ -296,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
296
296
  *
297
297
  * Nide.proj/
298
298
  * nide_cliser.cfg - JSON definition of the project (see below)
299
- * NaanIDE_version.txt - [default] substitution file defining 1.4.4+1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.5.0+1 etc.
300
300
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
301
301
  * build/ - [optional] default build output location
302
302
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -692,6 +692,7 @@ closure projConfig(projman, where, projID, local procon, fs, configpath) {
692
692
  erase: true
693
693
  overwrite: true
694
694
  follow: true
695
+ cacheControl: publish.cacheControlFiles
695
696
  })
696
697
  }
697
698
 
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * column positioning: // // !
9
9
  *
10
- * Copyright (c) 2017-2024 by Richard C. Zulch
10
+ * Copyright (c) 2017-2025 by Richard C. Zulch
11
11
  *
12
12
  */
13
13
 
@@ -76,21 +76,31 @@ closure dapoTable(database, rootpath, local table, prefix) {
76
76
 
77
77
  // add
78
78
  //
79
- // Add a new record to the table with optional metadata. This will fail if the key already
80
- // exists. Returns the new document record.
81
-
82
- table.add = closure add(key, content, metadata, callback, local id, doc) {
79
+ // Add a new record to the table with options. This will fail if the key already exists. Optional
80
+ // metadata is stored with the record and cannot be changed without calling update. Metadata keys
81
+ // cannot begin with underscore ("_").
82
+ //
83
+ // The returned document, which can be used with update, has the following keys:
84
+ // key - the key name of the record
85
+ // content - the body content stored under in the record
86
+ // _md5 - the MD5 hash of the content
87
+ // _length - the length of the content
88
+ // _id - the PouchDB ID
89
+ // _rev - the PouchDB revision
90
+ // <...> - various metadata keys
91
+
92
+ table.add = closure add(key, content, options, callback, local id, doc) {
83
93
  if !callback
84
- return (syncAdapter(add, key, content, metadata))
94
+ return (syncAdapter(add, key, content, options))
85
95
  id = makeID(key)
86
96
  if !string(id)
87
97
  return (asyncResult(callback, id)) // error from makeID
88
- if metadata && !dictionary(metadata)
98
+ if options.metadata && !dictionary(options.metadata)
89
99
  return (asyncResult(callback, Error("invalid arguments")))
90
100
  if !database.pouch
91
101
  return (asyncResult(callback, Error("database closed")))
92
- if metadata {
93
- doc = new(metadata) // start with metadata
102
+ if options.metadata {
103
+ doc = new(options.metadata) // start with metadata
94
104
  doc._rev = undefined } // _rev not allowed in metadata
95
105
  else
96
106
  doc = { }
@@ -115,12 +125,13 @@ closure dapoTable(database, rootpath, local table, prefix) {
115
125
 
116
126
  // update
117
127
  //
118
- // Update a record in the table using the document previously returned by add or get.
119
- // This will fail if the record has been modified by someone else in the meantime.
128
+ // Update a record in the table using the document previously returned by add or get. Specify
129
+ // options to update those values, otherwise existing/default values are used. This will fail
130
+ // if the record has been modified by someone else in the meantime.
120
131
 
121
- table.update = closure update(doc, callback) {
132
+ table.update = closure update(doc, options, callback) {
122
133
  if !callback
123
- return (syncAdapter(update, doc))
134
+ return (syncAdapter(update, doc, options))
124
135
  if !doc._id || !doc._id.startsWith(table.prefix) || !doc._rev
125
136
  return (asyncResult(callback, Error("invalid argument")))
126
137
  if makeID(doc.key) != doc._id
@@ -130,6 +141,8 @@ closure dapoTable(database, rootpath, local table, prefix) {
130
141
  doc = new(doc)
131
142
  doc._md5 = undefined
132
143
  doc._length = undefined
144
+ if options.metadata
145
+ doc = merge(doc, options.metadata)
133
146
  database.pouch.put(doc, function(error, resp) {
134
147
  if error
135
148
  error = Error("update failed", error)