@naanlang/naan 1.1.0 → 1.2.1

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,171 +6,136 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2023 by Richard C. Zulch
9
+ * Copyright (c) 2017-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
13
13
 
14
14
  /*
15
- * MakeNideAPIclient
16
- *
17
- * Make a DAS API client object for browsers. This client API handles a single project at a
18
- * time, from load() to close().
15
+ * NideAPIclient
19
16
  *
17
+ * Make an API client object for browsers.
20
18
  *
21
19
  */
22
20
 
23
- closure MakeNideAPIclient(url, guid, local client) {
21
+ closure NideAPIclient(url, guid, apiRequester, local client) {
22
+ global()
24
23
  client = new(object, this)
24
+ if !url.match(RegExp("^https?:\/\/", "i"))
25
+ url = "http://${url}"
25
26
  client.url = url
26
27
  client.guid = guid
27
28
  client.notifyAfter = 0
28
29
  client.instanceID = UUID() // our client's unique ID
29
30
 
30
- // clientRequest() - general request routine
31
-
32
- closure clientRequest(method, path, options, cbReq, local request) {
33
- request = xnew(js.w.XMLHttpRequest)
34
- request.addEventListener("load", function (event) {
35
- cbReq(false, request, event)
36
- })
37
-
38
- request.addEventListener("error", function reqFailed(event, local errtext) {
39
- if request.status == 0
40
- errtext = "connectivity"
41
- else
42
- errtext = "request failed"
43
- error = Error(errtext, {
44
- status: request.status
45
- method: method
46
- path: path
47
- })
48
- cbReq(error, request, event)
49
- })
31
+ // clientRequest() - append our guid to the headers
50
32
 
51
- request.open(method, path)
52
- if options.responseType
53
- request.responseType = options.responseType
54
- if options.contentType
55
- request.setRequestHeader("Content-Type", options.contentType)
33
+ closure clientRequest(url, options) {
34
+ options = merge(options)
56
35
  if guid
57
- request.setRequestHeader("x-naanlang-api-guid", guid)
58
- request.send(options.putdata)
36
+ options.headers = [list("x-naanlang-api-guid", guid)]
37
+ apiRequester(url, options)
59
38
  }
60
39
 
61
40
  //
62
41
  // docs()
63
42
  //
64
43
 
65
- client.docs = closure docs() {
66
- clientRequest("GET", url.concat("/"), false, function (error, req, event) {
67
- printline(req.responseText)
68
- })
44
+ client.docs = closure docs(local error, content, extra) {
45
+ `(error, content, extra) = clientRequest(url.concat("/"))
46
+ printline(content)
69
47
  }
70
48
 
71
49
  //
72
50
  // status(callback, timeoutMS)
73
51
  //
74
52
 
75
- client.status = closure status(cbStatus, timeoutms, local query, options, queued) {
76
- options = new(dictionary)
77
- options.timeout = timeoutms
78
- options.after = client.notifyAfter
79
- query = EncodeQuery("/status/?", options)
80
- clientRequest("GET", url.concat(query), false, function (error, req, event) {
81
- if (error || req.status != 200)
82
- cbStatus()
83
- else if (length(req.responseText) == 0)
84
- cbStatus([])
85
- else {
86
- queued = new(JSONparse(req.responseText))
87
- queued.forEach(function(item,index,array) {
88
- if client.notifyAfter < item.stamp
89
- client.notifyAfter = item.stamp
90
- })
91
- cbStatus(queued)
92
- }
53
+ client.status = closure status(cbStatus, timeoutms, local query, error, content, extra, queued) {
54
+ query = EncodeQuery("/status/?", {
55
+ timeout: timeoutms
56
+ after: client.notifyAfter
93
57
  })
58
+ `(error, content, extra) = clientRequest(url.concat(query))
59
+ if (error || extra.status != 200)
60
+ cbStatus()
61
+ else if (length(content) == 0)
62
+ cbStatus([])
63
+ else {
64
+ queued = new(content)
65
+ queued.forEach(function(item, index, array) {
66
+ if client.notifyAfter < item.stamp
67
+ client.notifyAfter = item.stamp
68
+ })
69
+ cbStatus(queued)
70
+ }
94
71
  }
95
72
 
96
73
  //
97
74
  // get
98
75
  //
99
76
 
100
- client.get = closure get(path, cbRead) {
101
- options.path = filepath
102
- clientRequest("GET", url.concat("/", path), false, cbRead)
77
+ client.get = closure get(path, options, local error, content, extra) {
78
+ `(error, content, extra) = clientRequest(url.concat("/", path))
79
+ if !error && extra.status != 200
80
+ error = Error("readFile failed: ".concat(extra.status), {
81
+ status: extra.status
82
+ })
83
+ if error
84
+ list(error)
85
+ else
86
+ list(error, content, extra)
103
87
  }
104
88
 
105
89
  //
106
- // readFile(filepath, cbRead)
90
+ // readFile(filepath, options)
107
91
  //
108
92
 
109
- client.readFile = closure readFile(filepath, cbRead, local query, options) {
110
- options = new(dictionary)
111
- options.path = filepath
112
- query = EncodeQuery("/readfile/?", options)
113
- clientRequest("GET", url.concat(query), false, cbRead)
93
+ client.readFile = closure readFile(filepath, options, local query, error, content, extra) {
94
+ query = EncodeQuery("/readfile/?", {
95
+ path: filepath
96
+ })
97
+ `(error, content, extra) = clientRequest(url.concat(query), options)
98
+ if !error && extra.status != 200
99
+ error = Error("readFile failed: ".concat(extra.status), {
100
+ status: extra.status
101
+ })
102
+ if error
103
+ list(error)
104
+ else
105
+ list(error, content, extra)
114
106
  }
115
107
 
116
108
  //
117
109
  // psmRemote(options, data, callback)
118
110
  //
119
- // Perform an operation and call back when done. If the communication works then the result is:
120
- // callback(false, (remote_error, remote_data)).
121
- // Otherwise if communication failed then the result is:
122
- // callback(<error>).
111
+ // Perform an operation and call back when done.
123
112
 
124
- client.psmRemote = closure psmRemote(options, data, callback, local query, method, reqOptions, error) {
113
+ client.psmRemote = closure psmRemote(options, data, callback,
114
+ local query, reqOptions, error, content, extra) {
125
115
  query = EncodeQuery("/psm/?", options)
126
- if data
127
- method = "PUT"
128
- else
129
- method = "GET"
130
116
  reqOptions = {
131
117
  putdata: data
132
118
  }
133
- if options.encoding == "binary" {
134
- if data
135
- reqOptions.contentType = "application/octet-stream" // we are sending binary data
136
- else
137
- reqOptions.responseType = "arraybuffer" // we are requesting binary data
138
- } else if options.encoding == "json" {
139
- if data {
140
- reqOptions.contentType = "application/json;charset=UTF-8" // we are sending JSON
141
- `(error, data) = JsonStringify(data)
142
- if error
143
- return (callback(Error("psmRemote failed:", error)))
144
- reqOptions.putdata = data
145
- }
146
- else
147
- reqOptions.responseType = "application/json" // we are requesting JSON
148
- }
149
- clientRequest(method, url.concat(query), reqOptions, function (error, req, event, local data) {
150
- if !error && req.status != 200
151
- error = Error("NodeFS request failed: ".concat(req.status), {
152
- status: req.status
153
- method: method
154
- query: query
155
- })
119
+ if !data
120
+ reqOptions.encoding = options.encoding // expected response format
121
+ else if options.encoding == "binary"
122
+ reqOptions.contentType = "application/octet-stream" // we are sending binary data
123
+ else if options.encoding == "json" {
124
+ reqOptions.contentType = "application/json;charset=UTF-8" // we are sending JSON
125
+ `(error, data) = JsonStringify(data)
156
126
  if error
157
- callback(error)
158
- else {
159
- if req.getResponseHeader("content-type").startsWith("application/json")
160
- try { // attempt to decode JSON response
161
- data = new(JSONparse(req.responseText))
162
- if array(data) {
163
- data = totuple(data)
164
- error = data.0
165
- data = data.1 }
166
- } catch {
167
- if true
168
- data = req.responseText // just return text on failure
169
- }
170
- else
171
- data = req.response // e.g. when reading binary content
172
- callback(error, data) }
173
- })
127
+ return (callback(Error("psmRemote encoding failed:", error)))
128
+ reqOptions.putdata = data
129
+ }
130
+ `(error, content, extra) = clientRequest(url.concat(query), reqOptions)
131
+ if !error && extra.status != 200
132
+ error = Error("NodeFS request failed: ".concat(extra.status), {
133
+ status: extra.status
134
+ })
135
+ if error
136
+ callback(error)
137
+ else
138
+ callback(error, content)
174
139
  }
175
140
 
176
141
  client
@@ -186,12 +151,12 @@ closure MakeNideAPIclient(url, guid, local client) {
186
151
 
187
152
  function apicInit(local manifest) {
188
153
 
189
- manifest = `(MakeNideAPIclient, apicInit)
154
+ manifest = `(NideAPIclient, apicInit)
190
155
 
191
156
  Naan.module.build(module.id, "apiclient", function(modobj, compobj) {
192
157
  Naan.module.require("./client.nlg")
193
158
  compobj.manifest = manifest
194
- modobj.exports.MakeNideAPIclient = MakeNideAPIclient
159
+ modobj.exports.NideAPIclient = NideAPIclient
195
160
  })
196
161
 
197
162
  } ();
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2022 by Richard C. Zulch
9
+ * Copyright (c) 2019-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  closure psmcFsView(api, rootpath, local view, pathmod) {
22
+ global(JSpath)
22
23
  view = new(object, this)
23
24
  view.api = api
24
25
  view.rootpath = rootpath
@@ -154,6 +155,8 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
154
155
  // Read a file at the specified path
155
156
 
156
157
  view.readFile = function readFile(path, options, callback) {
158
+ if !path
159
+ throw("psmcFsView.readFile: path is false")
157
160
  psmRemote(path, "readFile", options, false, callback)
158
161
  }
159
162
 
@@ -354,7 +357,9 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
354
357
  // Tell the shell to open the specified file.
355
358
 
356
359
  view.shellOpen = function shellOpen(path, args, callback) {
357
- psmRemote(path, "shellOpen", { args: args }, false, callback)
360
+ if args
361
+ args = { args: args }
362
+ psmRemote(path, "shellOpen", args, false, callback)
358
363
  }
359
364
 
360
365
  // grepLines
@@ -424,11 +429,14 @@ closure psmcFsView(api, rootpath, local view, pathmod) {
424
429
  *
425
430
  */
426
431
 
427
- closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
432
+ closure psmcFsConnector(psm, api, local connClassID, connector, watch) {
433
+ global()
428
434
  connClassID = "NodeFS"
429
435
  connector = new(object, this)
430
436
  connector.psm = psm
431
- connector.api = nicc.api
437
+ connector.apis = {
438
+ HostFS: api
439
+ }
432
440
  connector.vault = psm.vault(connClassID)
433
441
  connector.label = "Naan-Server"
434
442
  watch = psm.watchable(connector)
@@ -563,7 +571,7 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
563
571
  // listResources
564
572
  //
565
573
  // List saved resources in our database
566
- connector.listResources = function listResources(local error, original) {
574
+ connector.listResources = function listResources(local error, output, original) {
567
575
  `(error, output) = connector.vault.listResources()
568
576
  if error
569
577
  return (list(error)) // can't access our saved resources
@@ -579,14 +587,14 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
579
587
  connector.info = function info(resID, local info, error, creds) {
580
588
  info = {
581
589
  classID: connClassID
582
- type: "remote filesystem"
583
- services: ["NideFS", "NideHostAPI"]
590
+ type: "remote server"
591
+ services: ["NideFS", "NideHostAPI", "NideTerminal"]
584
592
  }
585
593
  if resID == "Host" {
586
594
  info.name = "Host"
587
595
  info.type = "host filesystem"
588
596
  info.locked = true
589
- info.where = nicc.api.url
597
+ info.where = api.url
590
598
  info.access = "HostFS"
591
599
  } else {
592
600
  `(error, creds) = connector.vault.accessResource(resID)
@@ -620,14 +628,26 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
620
628
  `(error, creds) = connector.access(resID)
621
629
  if !error {
622
630
  if creds == "HostFS"
623
- api = connector.api
624
- else
625
- api = MakeNideAPIclient(creds.urlName, creds.authSecret) // API to remote NodeJS filesystem
631
+ api = connector.apis[creds]
632
+ else {
633
+ api = connector.apis[creds.urlName]
634
+ if !api // get API to remote server
635
+ connector.apis[creds.urlName] = api = NideAPIclient(creds.urlName, creds.authSecret)
636
+ }
626
637
  }
627
638
  if error
628
639
  list(error)
629
- else if service == "NideExecutor"
630
- require("../browser/terminals.nlg").TermHost(nicc.track, api, args.0, args.1) // host executor, args: (name, workerID)
640
+ else if service == "NideTerminal" {
641
+ if !App.nide.track
642
+ return (list(Error("NideTerminal not supported in this environment")))
643
+ list(false, App.nide.track.spawn(App.nide.registerRemote(api),
644
+ args.0, // terminal display name
645
+ args.1)) // options:
646
+ // {
647
+ // workerID: "NideServer" -- for main thread; other strings for worker threads
648
+ // type: "My Remotes" -- display name of terminal group; e.g. URL
649
+ // }
650
+ }
631
651
  else if service == "NideHostAPI"
632
652
  list(false, api) // connect to the Host API
633
653
  else if service == "NideFS" {
@@ -647,17 +667,18 @@ closure psmcFsConnector(psm, nicc, local connClassID, connector, watch) {
647
667
 
648
668
 
649
669
  /*
650
- * psmcMakeClientPSM
670
+ * MakeClientPSM
651
671
  *
652
672
  * Return a new client PSM with our known connectors.
653
673
  *
654
674
  */
655
675
 
656
- function psmcMakeClientPSM(nicc, dbname, local importPSM) {
676
+ function MakeClientPSM(api, dbname, local importPSM) {
677
+ global(App)
657
678
  importPSM = require("../storage/psm.nlg")
658
679
  App.psm = importPSM.MakePSM(dbname).1
659
680
  if App.psm {
660
- psmcFsConnector(App.psm, nicc) // HostFS access
681
+ psmcFsConnector(App.psm, api) // HostFS access
661
682
  require("../storage/dbt_pouch.nlg").DbConnector(App.psm) // Pouch databases as filesystems
662
683
  }
663
684
  App.psm
@@ -673,12 +694,12 @@ function psmcMakeClientPSM(nicc, dbname, local importPSM) {
673
694
 
674
695
  function psmcInit(local manifest) {
675
696
 
676
- manifest = `(psmcFsView, psmcFsConnector, psmcMakeClientPSM, psmcInit)
697
+ manifest = `(psmcFsView, psmcFsConnector, MakeClientPSM, psmcInit)
677
698
 
678
699
  Naan.module.build(module.id, "psm_client", function(modobj, compobj) {
679
700
  require("./client.nlg")
680
701
  compobj.manifest = manifest
681
- modobj.exports.MakeClientPSM = psmcMakeClientPSM
702
+ modobj.exports.MakeClientPSM = MakeClientPSM
682
703
  })
683
704
 
684
705
  } ();
@@ -35,6 +35,7 @@ wsMod;; // websocket module
35
35
  * workerID: <string> // worker on server (server destinations only)
36
36
  * }
37
37
  * startup: <dictionary> // worker spawn init (server destinations only)
38
+ * wss: <boolean> // true to use secure connection
38
39
  * debug: <boolean> // debug logging
39
40
  * }
40
41
  *
@@ -74,6 +75,7 @@ closure RelayCon(host, secret, options, local relay, nlregex)
74
75
  relay.guid = secret
75
76
  relay.host = host // e.g. "localhost:8009"
76
77
  relay.wscon = wsMod.WebSocket(host, {
78
+ wss: options.wss || undefined
77
79
  onopen: onopen
78
80
  onmessage: onmessage
79
81
  onerror: onerror
@@ -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-2024 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -193,8 +193,14 @@ closure JsLoadScript(libpath, globalID, local scriptTag, fpath) {
193
193
  else
194
194
  fpath = "/".concat(libpath)
195
195
  if js.s.importScripts && tostring(js.s.importScripts) == "[Function importScripts]" {
196
- js.s.importScripts(fpath) // load in worker context
197
- jsScripts[libpath] = js.s[globalID]
196
+ fpath = JSpath.dirname(js.w.location.href).concat(fpath)
197
+ try {
198
+ js.s.importScripts(fpath) // load in worker context
199
+ jsScripts[libpath] = js.s[globalID]
200
+ } catch {
201
+ debuglog("JsLoadScript:", libpath, "failed", exception)
202
+ js.s.console.log("JsLoadScript:", libpath, "failed", exception) // ensure visibility in JS console
203
+ }
198
204
  } else if js.w { // load in browser context
199
205
  pending = new(nonce)
200
206
  scriptTag = js.w.document.createElement("script")
@@ -205,7 +211,7 @@ closure JsLoadScript(libpath, globalID, local scriptTag, fpath) {
205
211
  true
206
212
  }
207
213
  scriptTag.onerror = function (event) {
208
- debuglog("jsScriptLoader:", libpath, "not found")
214
+ debuglog("JsLoadScript:", libpath, "not found")
209
215
  pending.signal(false)
210
216
  true
211
217
  }
@@ -105,7 +105,7 @@ closure httpsApiReqOnce(url, options,
105
105
  })
106
106
  response.on("end", function (local content, error, headers) {
107
107
  content = js.g.Buffer.concat(chunks)
108
- if response.statusCode < 200 || response.statusCode >= 300 {
108
+ if response.statusCode < 200 || response.statusCode >= 299 { // 299 is "cancelled"
109
109
  if options.debug
110
110
  debuglog("HttpsApiRequest status:", url, response.statusCode, "message:", content)
111
111
  pending.signal(list(Error("HttpsApiRequest statusCode:", url, response.statusCode, {
@@ -132,6 +132,7 @@ closure httpsApiReqOnce(url, options,
132
132
  pending.signal(list(error, content, {
133
133
  headers: response.headers
134
134
  status: response.statusCode
135
+ contentType: response.headers["content-type"]
135
136
  }))
136
137
  })
137
138
  })
@@ -450,8 +450,8 @@ closure psmsFsConnector(psm, api, local connClassID, connector, watch) {
450
450
  connector.info = function info(resID, local info, error, creds) {
451
451
  info = {
452
452
  classID: connClassID
453
- type: "remote filesystem"
454
- services: ["NideFS", "NideHostAPI"]
453
+ type: "remote server"
454
+ services: ["NideFS", "NideHostAPI", "NideTerminal"]
455
455
  }
456
456
  if resID == "Host" {
457
457
  info.name = "Host"
@@ -20,6 +20,7 @@
20
20
  *
21
21
  * Options:
22
22
  * {
23
+ * wss: <boolean> // true to use secure connection
23
24
  * onopen: <proc>(e) // connection has opened
24
25
  * onmessage: <proc>(e, message) // message received
25
26
  * onerror: <proc>(e) // error occurred
@@ -37,13 +38,17 @@ closure WebSocket(host, options, local wsock) {
37
38
  //
38
39
  // Connect with the host if not already connected, returning a result tuple.
39
40
  //
40
- wsock.connect = closure connect(local pending, error, ws) {
41
+ wsock.connect = closure connect(local error, ws, url, pending) {
41
42
  if wsock.ws
42
43
  return (list(false, { open: true }))
43
44
  `(error, ws) = await(js.i("ws"))
44
45
  if error
45
46
  return (list(error))
46
- wsock.ws = xnew(ws.default, "ws://${host}")
47
+ if options.wss
48
+ url = "wss://${host}"
49
+ else
50
+ url = "ws://${host}"
51
+ wsock.ws = xnew(ws.default, url)
47
52
  pending = new(nonce)
48
53
 
49
54
  // onopen
@@ -118,7 +118,7 @@
118
118
  * ]
119
119
  * }
120
120
  *
121
- * build = Builder(rulesDict, operation, fs, srcpath, destpath) // create a builder object
121
+ * build = Builder(rulesDict, operation, fs, srcpath, destpath, track) // create a builder object
122
122
  * build.increment() // increment the build counter
123
123
  * build.make() // run the build
124
124
  * build.clean() // clear all output folders
@@ -140,7 +140,7 @@
140
140
  *
141
141
  */
142
142
 
143
- closure Builder(rules, operation, fs, srcpath, destpath, local build, group, newgroup) {
143
+ closure Builder(rules, operation, fs, srcpath, destpath, track, local build, group, newgroup) {
144
144
  build = new(object, this)
145
145
  build.srcpath = srcpath
146
146
  build.destpath = destpath
@@ -393,7 +393,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
393
393
 
394
394
  closure zip(files, options, local result) {
395
395
  if !build.zipper
396
- build.zipper = ExecutorParallel({ // create worker for zipping
396
+ build.zipper = ExecutorParallel(track, { // create worker for zipping
397
397
  maxcount: 1 // stupid Zip lib can't do more than one
398
398
  basename: "zip"
399
399
  initeval: `(false, (zip1))
@@ -529,7 +529,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
529
529
 
530
530
  closure parseAndPack(text, fpath, options, local result) {
531
531
  if !build.parallel
532
- build.parallel = ExecutorParallel({ // create worker pool
532
+ build.parallel = ExecutorParallel(track, { // create worker pool
533
533
  basename: "build"
534
534
  initeval: `(false, (parseAndPack1))
535
535
  })
@@ -1190,12 +1190,13 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1190
1190
  //
1191
1191
  // Run the current build product by spawning a new worker.
1192
1192
  //
1193
- build.runSpawn = function runSpawn(track local error, maintext, executor, vpath, site) {
1193
+ build.runSpawn = function runSpawn(local error, maintext, executor, vpath, site) {
1194
1194
  `(error, maintext) = fs.readFile(JSpath.join(build.destpath, build.runway.main))
1195
1195
  if error
1196
1196
  error = Error("Builder: cannot read main file", error)
1197
1197
  else {
1198
- `(error, executor) = track.spawn(build.runway.spawn.0, "${rules.name} ${build.version}", "Run-${build.buildnum}", {
1198
+ `(error, executor) = track.spawn(build.runway.spawn.0, "${rules.name} ${build.version}", {
1199
+ workerID: "Run-${build.buildnum}"
1199
1200
  startup: {
1200
1201
  initcmds: maintext
1201
1202
  dirpath: fs.path.resolve(fs.path.sep, fs.rootpath, build.destpath)
@@ -1227,7 +1228,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1227
1228
  //
1228
1229
  // Run the current build product by opening a new window.
1229
1230
  //
1230
- build.runWindow = function runWindow(track
1231
+ build.runWindow = function runWindow(
1231
1232
  local title, main, url, features, vpath, error, window, site) {
1232
1233
  if !js.w
1233
1234
  return (list(Error('Run method "window" requires browser host')))
@@ -1259,13 +1260,13 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1259
1260
  // Run the current build product, which would be after the make call. Returns a standard result
1260
1261
  // tuple.
1261
1262
  //
1262
- build.run = function run(track) {
1263
+ build.run = function run() {
1263
1264
  if build.runway.open
1264
1265
  fs.shellOpen(JSpath.join(build.destpath, build.runway.open), build.runway.args)
1265
1266
  else if build.runway.spawn
1266
- build.runSpawn(track)
1267
+ build.runSpawn()
1267
1268
  else if build.runway.window
1268
- build.runWindow(track)
1269
+ build.runWindow()
1269
1270
  else
1270
1271
  list(false, { ok: true })
1271
1272
  }
@@ -1275,7 +1276,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1275
1276
  //
1276
1277
  // Refresh after updating the v-site, returning true iff an existing vsite was found.
1277
1278
  //
1278
- build.refresh = function refresh(track local url_origin, found) {
1279
+ build.refresh = function refresh(local url_origin, found) {
1279
1280
  if build.runway."v-site" {
1280
1281
  url_origin = js.w.location.origin.concat("/run", build.runway."v-site")
1281
1282
  for target in track.enumlist() {
@@ -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.1.0+1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.2.1+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
@@ -614,7 +614,7 @@ closure projConfig(projman, where, projID, local procon, fs, configpath) {
614
614
  rules.typeID = procon.where_typeID
615
615
  if !rules.operation
616
616
  rules.operation = "debug"
617
- `(error, builder) = Builder(rules, rules.operation, fs, "", buildpath)
617
+ `(error, builder) = Builder(rules, rules.operation, fs, "", buildpath, projman.track)
618
618
  if error
619
619
  return (list(error))
620
620
  procon.builder = builder
@@ -628,10 +628,10 @@ closure projConfig(projman, where, projID, local procon, fs, configpath) {
628
628
  buildnum: buildnum
629
629
  })
630
630
  if !error {
631
- if builder.refresh(projman.track)
631
+ if builder.refresh()
632
632
  buildonly = !buildonly // reverse if refresh
633
633
  if !buildonly
634
- `(error) = builder.run(projman.track)
634
+ `(error) = builder.run()
635
635
  }
636
636
  builder.destroy()
637
637
  procon.builder = false
@@ -662,7 +662,9 @@ closure projConfig(projman, where, projID, local procon, fs, configpath) {
662
662
  publish = rules.publish
663
663
  where = procon.localDict.publish[stage].where
664
664
  if !rules || !where
665
- return (list(Error("no project publish location configured")))
665
+ return (list(Error("no project publish location set")))
666
+ if !publish.input || !publish.sources || !publish.output
667
+ return (list(Error("project configuration has no publish instructions")))
666
668
  if publish.cacheControl
667
669
  outOptions = {
668
670
  cacheControl: publish.cacheControl