@naanlang/naan 1.0.3 → 1.0.6

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 (44) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +8 -6
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +30 -6
  5. package/dist/naan.min.js +4 -4
  6. package/frameworks/browser/https_request.nlg +120 -0
  7. package/frameworks/browser/sworker.js +29 -39
  8. package/frameworks/browser/terminals.nlg +6 -4
  9. package/frameworks/browser/workers.nlg +13 -6
  10. package/frameworks/client/apiclient.nlg +1 -1
  11. package/frameworks/client/psm_client.nlg +80 -32
  12. package/frameworks/common/common.nlg +104 -26
  13. package/frameworks/common/preferences.nlg +1 -0
  14. package/frameworks/common/watching.nlg +1 -0
  15. package/frameworks/node/apiserver.nlg +9 -5
  16. package/frameworks/node/filesystem.nlg +89 -31
  17. package/frameworks/node/gitter.nlg +27 -6
  18. package/frameworks/node/https_request.nlg +119 -0
  19. package/frameworks/node/node.nlg +1 -0
  20. package/frameworks/node/psm_server.nlg +29 -7
  21. package/frameworks/project/build.nlg +13 -7
  22. package/frameworks/project/proj_cliser.nlg +11 -3
  23. package/frameworks/project/proj_console.nlg +13 -4
  24. package/frameworks/project/proj_folder.nlg +10 -1
  25. package/frameworks/project/proj_lambda.nlg +13 -4
  26. package/frameworks/project/proj_static.nlg +11 -3
  27. package/frameworks/project/projects.nlg +102 -20
  28. package/lib/browser/env_web.js +32 -8
  29. package/lib/core/naanlib.js +4 -4
  30. package/package.json +2 -1
  31. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
  32. package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
  33. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +7 -6
  34. package/plugins/serviceAws/aws_dynamo.nlg +128 -45
  35. package/plugins/serviceAws/aws_lambda.nlg +11 -4
  36. package/plugins/serviceAws/aws_s3.nlg +44 -14
  37. package/plugins/serviceAws/dbt_aws.nlg +29 -36
  38. package/plugins/serviceAws/psm_aws.nlg +21 -16
  39. package/plugins/serviceAws/serviceAws.nlg +6 -4
  40. package/test/harness.nlg +3 -1
  41. package/test/test_01_core.nlg +5 -5
  42. package/test/test_02_context.nlg +4 -4
  43. package/test/test_03_jsinterop.nlg +9 -3
  44. package/plugins/serviceAws/aws_cognito.nlg +0 -76
@@ -0,0 +1,119 @@
1
+ /*
2
+ * https_node.nlg
3
+ *
4
+ * Https operations for NodeJS.
5
+ *
6
+ * column positioning: // // !
7
+ *
8
+ * Copyright (c) 2021-2022 by Richard C. Zulch
9
+ *
10
+ */
11
+
12
+
13
+ /*
14
+ * HttpsApiRequest
15
+ *
16
+ * Perform an API request from in NodeJS. This operates the same as HttpsApiRequest for browsers.
17
+ *
18
+ * Options:
19
+ * {
20
+ * method: <string> // HTTP method (defaults to GET/PUT)
21
+ * query: <dictionary> // query variables added to the URL
22
+ * putdata: <data> // data to put
23
+ * contentType: <string> // sets Content-Type header, otherwise auto-determined
24
+ * range: <string> // sets range header
25
+ * headers: [`(key, value)] // add header(s) to the request, overriding defaults
26
+ * }
27
+ *
28
+ */
29
+
30
+ closure HttpsApiRequest(url, options, local urlq, reqOptions, putdata, item, pending, chunks, request) {
31
+ if options.query
32
+ urlq = url.concat(EncodeQuery("?", options.query))
33
+ else
34
+ urlq = url
35
+ putdata = options.putdata
36
+ reqOptions = { }
37
+ if options.method
38
+ reqOptions.method = options.method
39
+ else if putdata
40
+ reqOptions.method = "POST"
41
+ else
42
+ reqOptions.method = "GET"
43
+ reqOptions.headers = { }
44
+ if putdata {
45
+ if options.contentType
46
+ reqOptions.headers['Content-Type'] = options.contentType
47
+ else if jsTypedArray(options.putdata)
48
+ reqOptions.headers['Content-Type'] = "application/octet-stream"
49
+ else if member(typeof(options.putdata), `(dictionary, array, xobject)) {
50
+ reqOptions.headers['Content-Type'] = "application/json"
51
+ `(error, putdata) = JsonStringify(options.putdata)
52
+ if error
53
+ return (list(Error("HttpsApiRequest encode:", url)))
54
+ }
55
+ fetchOptions.body = putdata
56
+ }
57
+ if array(options.headers)
58
+ for item in options.headers
59
+ reqOptions.headers[item.0] = item.1
60
+ pending = new(nonce)
61
+ chunks = []
62
+ request = nodeHttps.request(url, reqOptions, function (response) {
63
+ if response.statusCode < 200 || response.statusCode >= 300 {
64
+ pending.signal(list(Error("HttpsApiRequest statusCode:", url, response.statusCode, {
65
+ status: response.statusCode
66
+ })))
67
+ return
68
+ }
69
+ response.on("data", function (chunk) {
70
+ chunks.push(chunk)
71
+ })
72
+ response.on("end", function (local content, error) {
73
+ content = Buffer.concat(chunks)
74
+ if response.complete {
75
+ if response.headers["content-type"].startsWith("application/json") {
76
+ `(error, content) = JsonParse(content.toString())
77
+ if error
78
+ error = Error("HttpsApiRequest decode:", url, error)
79
+ else
80
+ content = new(content)
81
+ if array(content) { // deencapsulate to get API result
82
+ content = totuple(content)
83
+ error = content.0
84
+ content = content.1
85
+ }
86
+ }
87
+ }
88
+ else
89
+ error = Error("HttpsApiRequest terminated prematurely:", url)
90
+ pending.signal(list(error, content))
91
+ })
92
+ })
93
+ request.on("error", function(error) {
94
+ error = Error("HttpsApiRequest failed:", url, error)
95
+ pending.signal(list(error))
96
+ })
97
+ if data
98
+ request.write(data)
99
+ request.end()
100
+ pending.wait()
101
+ };
102
+
103
+
104
+ /*
105
+ * https_nodeInit
106
+ *
107
+ * Initialize HTTPS request operations for NodeJS.
108
+ *
109
+ */
110
+
111
+ function https_nodeInit(local manifest) {
112
+ manifest = `(HttpsGetJson, HttpsPutJson, https_nodeInit)
113
+
114
+ Naan.module.build(module.id, "https_request", function(modobj, compobj) {
115
+ require("./node.nlg")
116
+ compobj.manifest = manifest
117
+ modobj.exports.HttpsApiRequest = HttpsApiRequest
118
+ })
119
+ }();
@@ -75,6 +75,7 @@ function nodrInit(local manifest) {
75
75
  fs = js.r("fs")
76
76
  os = js.r("os")
77
77
  stream = js.r("stream")
78
+ nodeHttps = js.r("https")
78
79
  }()
79
80
  module.reload = nodrReload
80
81
  })
@@ -27,6 +27,7 @@ closure psmsFsView(rootpath, local view, gitter) {
27
27
  view = view.1
28
28
 
29
29
  // inherited
30
+ // -- view.folderPath(folderid, callback)
30
31
  // -- view.winDrives(callback)
31
32
  // -- view.testHidden(path, callback)
32
33
  // -- view.readLines(path, options, callback)
@@ -86,16 +87,27 @@ closure psmsFsView(rootpath, local view, gitter) {
86
87
  }
87
88
  res.send(new(array, result))
88
89
  }
89
-
90
+
91
+ // op: folderPath
92
+ function folderPathApi() {
93
+ res.send(new(array, view.folderPath(req.query.folderid)))
94
+ }
95
+
90
96
  // op: readLines
91
97
  function readLinesApi() {
92
98
  res.send(new(array, view.readLines(path, req.query)))
93
99
  }
94
100
 
95
101
  // op: readFile
96
- function readFileApi(local options) {
102
+ function readFileApi(local error, localpath, options) {
103
+ `(error, localpath) = view.localPath(path)
104
+ if error {
105
+ res.send(new(array, list(error)))
106
+ return
107
+ }
97
108
  options = {
98
- dotfiles: "allow" }
109
+ dotfiles: "allow"
110
+ }
99
111
  if req.query.encoding == "binary"
100
112
  options.headers = {
101
113
  "Content-Type": "application/octet-stream"
@@ -104,7 +116,7 @@ closure psmsFsView(rootpath, local view, gitter) {
104
116
  options.headers = { // ### is this always the right default?
105
117
  "Content-Type": "text/html; charset=UTF-8"
106
118
  }
107
- res.sendFile(path, options, function (err, local status) {
119
+ res.sendFile(localpath, options, function (err, local status) {
108
120
  if err {
109
121
  if err.code == "ENOENT"
110
122
  status = 404
@@ -187,11 +199,12 @@ closure psmsFsView(rootpath, local view, gitter) {
187
199
 
188
200
  // op: tree
189
201
  function treeApi() {
190
- res.send(new(array, view.tree(path, req.query.depthlimit)))
202
+ res.send(new(array, view.tree(path, toint(req.query.depthlimit))))
191
203
  }
192
204
 
193
205
  // op: dirSearch
194
206
  function dirSearchApi() {
207
+ req.query.excludeDirs = JSONparse(req.query.excludeDirs)
195
208
  res.send(new(array, view.dirSearch(path, req.query)))
196
209
  }
197
210
 
@@ -227,6 +240,7 @@ closure psmsFsView(rootpath, local view, gitter) {
227
240
 
228
241
  // op: grepLines
229
242
  function grepLinesApi() {
243
+ req.query.excludeDirs = JSONparse(req.query.excludeDirs)
230
244
  res.send(new(array, view.grepLines(path, req.query)))
231
245
  }
232
246
 
@@ -250,6 +264,7 @@ closure psmsFsView(rootpath, local view, gitter) {
250
264
 
251
265
  proc = {
252
266
  pathOp: pathOpApi
267
+ folderPath: folderPathApi
253
268
  readFile: readFileApi
254
269
  readLines: readLinesApi
255
270
  writeFile: writeFileApi
@@ -276,8 +291,15 @@ closure psmsFsView(rootpath, local view, gitter) {
276
291
  gitLog: gitLogApi
277
292
  gitShowFile: gitShowFileApi
278
293
  }[req.query.op]
279
- if proc
280
- proc()
294
+ if proc {
295
+ try {
296
+ proc()
297
+ } catch {
298
+ if true {
299
+ res.status(400).send("psm op failed: ".concat(req.query.op, ": ", exception))
300
+ }
301
+ }
302
+ }
281
303
  else
282
304
  res.status(400).send("invalid psm op: ".concat(req.query.op))
283
305
  }
@@ -1024,13 +1024,14 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1024
1024
  // } ... ]
1025
1025
  // }
1026
1026
  //
1027
- build.make = closure make(debugMake, local sink, xchan, entry, outext, error) {
1027
+ build.make = closure make(options, local sink, xchan, entry, outext, error) {
1028
1028
  sink = new(textstream, "", `string) // log all output
1029
1029
  xchan = textstreams(list(sink, sink))
1030
1030
  try {
1031
1031
  resetlist()
1032
- analyze(debugMake)
1033
- if debugMake {
1032
+ analyze(options.debugMake)
1033
+ if options.debugMake {
1034
+ printline("Debugging make for ", options.buildnum, "-", options.stage)
1034
1035
  if build.errorlist.length == 0 {
1035
1036
  printline("build operations (", build.filelist.length, ") for ", operation)
1036
1037
  for entry in build.filelist
@@ -1052,6 +1053,8 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1052
1053
  "\ndate: ", Date(),
1053
1054
  "\nfrom: ", JSpath.join(fs.rootpath, build.srcpath),
1054
1055
  "\n to: ", build.destpath,
1056
+ "\ntype: ", options.stage,
1057
+ "\nvers: ", options.buildnum,
1055
1058
  "\n", sink.taketext())
1056
1059
  `(error) = fs.writeFile(JSpath.join(build.destpath, "build_log.txt"), outext)
1057
1060
  if error
@@ -1153,14 +1156,17 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1153
1156
  //
1154
1157
  // build.refresh
1155
1158
  //
1156
- // Refresh after updating the v-site. This should probably update all types of executions. ###
1159
+ // Refresh after updating the v-site, returning true iff an existing vsite was found.
1157
1160
  //
1158
- build.refresh = function refresh(track local main, url_origin) {
1161
+ build.refresh = function refresh(track local main, url_origin, found) {
1159
1162
  if build.runway."v-site" {
1160
1163
  url_origin = js.w.location.origin.concat("/run", build.runway."v-site")
1161
- for target in track.enumlist()
1162
- target.vsiteRefresh(url_origin)
1164
+ for target in track.enumlist() {
1165
+ if target.vsiteRefresh(url_origin)
1166
+ found = true
1167
+ }
1163
1168
  }
1169
+ found
1164
1170
  }
1165
1171
 
1166
1172
  //
@@ -27,7 +27,7 @@ closure proj_cliserInstance(projman, where, projID, local project) {
27
27
  project.name = where.name
28
28
  project.where = where
29
29
  project.projID = projID
30
- project.config = projConfig(projman, where)
30
+ project.config = projConfig(projman, where, projID)
31
31
 
32
32
  // create
33
33
  //
@@ -63,8 +63,16 @@ closure proj_cliserInstance(projman, where, projID, local project) {
63
63
  //
64
64
  // Run the project by building and then launching.
65
65
  //
66
- project.run = closure run(buildonly) {
67
- project.config.run(buildonly)
66
+ project.run = closure run(stage, buildonly) {
67
+ project.config.run(stage, buildonly)
68
+ }
69
+
70
+ // publish
71
+ //
72
+ // Publish the project according to the stage.
73
+ //
74
+ project.publish = closure publish(stage) {
75
+ project.config.publish(stage)
68
76
  }
69
77
 
70
78
  // close()
@@ -27,7 +27,7 @@ closure proj_consoleInstance(projman, where, projID, local project) {
27
27
  project.name = where.name
28
28
  project.where = where
29
29
  project.projID = projID
30
- project.config = projConfig(projman, where)
30
+ project.config = projConfig(projman, where, projID)
31
31
  project.runPageDebug = true // switch to Debug page on run
32
32
 
33
33
  // create
@@ -60,9 +60,18 @@ closure proj_consoleInstance(projman, where, projID, local project) {
60
60
 
61
61
  // run
62
62
  //
63
- // Run the project by building and then launching.
64
- project.run = closure run(buildonly) {
65
- project.config.run(buildonly)
63
+ // Run the project by building and then launching.
64
+ //
65
+ project.run = closure run(stage, buildonly) {
66
+ project.config.run(stage, buildonly)
67
+ }
68
+
69
+ // publish
70
+ //
71
+ // Publish the project according to the stage.
72
+ //
73
+ project.publish = closure publish(stage) {
74
+ project.config.publish(stage)
66
75
  }
67
76
 
68
77
  // close
@@ -61,11 +61,20 @@ closure proj_folderInstance(projman, where, projID, local project) {
61
61
  // run
62
62
  //
63
63
  // Run the project by building and then launching.
64
- project.run = closure run(buildonly) {
64
+ project.run = closure run(stage, buildonly) {
65
65
  debuglog("folder run project stub")
66
66
  list(false, { ok: true })
67
67
  }
68
68
 
69
+ // publish
70
+ //
71
+ // Publish the project according to the stage.
72
+ //
73
+ project.publish = closure publish(stage) {
74
+ debuglog("folder publish project stub")
75
+ list(false, { ok: true })
76
+ }
77
+
69
78
  // close
70
79
  //
71
80
  // Close the project, releasing resources.
@@ -27,7 +27,7 @@ closure proj_lambdaInstance(projman, where, projID, local project) {
27
27
  project.name = where.name
28
28
  project.where = where
29
29
  project.projID = projID
30
- project.config = projConfig(projman, where)
30
+ project.config = projConfig(projman, where, projID)
31
31
  project.runPageDebug = true // switch to Debug page on run
32
32
 
33
33
  // create
@@ -60,9 +60,18 @@ closure proj_lambdaInstance(projman, where, projID, local project) {
60
60
 
61
61
  // run
62
62
  //
63
- // Run the project by building and then launching.
64
- project.run = closure run(buildonly) {
65
- project.config.run(buildonly)
63
+ // Run the project by building and then launching.
64
+ //
65
+ project.run = closure run(stage, buildonly) {
66
+ project.config.run(stage, buildonly)
67
+ }
68
+
69
+ // publish
70
+ //
71
+ // Publish the project according to the stage.
72
+ //
73
+ project.publish = closure publish(stage) {
74
+ project.config.publish(stage)
66
75
  }
67
76
 
68
77
  // close
@@ -26,7 +26,7 @@ closure proj_staticInstance(projman, where, projID, local project) {
26
26
  project.name = where.name
27
27
  project.where = where
28
28
  project.projID = projID
29
- project.config = projConfig(projman, where)
29
+ project.config = projConfig(projman, where, projID)
30
30
 
31
31
  // create
32
32
  //
@@ -62,8 +62,16 @@ closure proj_staticInstance(projman, where, projID, local project) {
62
62
  //
63
63
  // Run the project by building and then launching.
64
64
  //
65
- project.run = closure run(buildonly) {
66
- project.config.run(buildonly)
65
+ project.run = closure run(stage, buildonly) {
66
+ project.config.run(stage, buildonly)
67
+ }
68
+
69
+ // publish
70
+ //
71
+ // Publish the project according to the stage.
72
+ //
73
+ project.publish = closure publish(stage) {
74
+ project.config.publish(stage)
67
75
  }
68
76
 
69
77
  // close()
@@ -23,6 +23,7 @@ closure ProjectManager(locate, track, dbname, local error, proj) {
23
23
  `(error, proj) = storageResources.ResourceManager(dbname, "Projects/")
24
24
  if error
25
25
  return(list(error))
26
+ proj.configLocal = proj.vault("configlocal") // local config storage
26
27
  proj.locate = locate // storage locations
27
28
  proj.track = track // execution tracking
28
29
  proj.types = []
@@ -226,7 +227,9 @@ closure projConnector(projman, projtype, local connector, watch) {
226
227
  // deleteResource
227
228
  //
228
229
  // Delete information for a project. The return value is (error, result) tuple.
229
- connector.deleteResource = function deleteResource(resID) {
230
+ connector.deleteResource = function deleteResource(resID, local localID) {
231
+ localID = resID.concat("-data")
232
+ projman.configLocal.deleteResource(localID) // remove local storage if there is any
230
233
  connector.vault.deleteResource(resID)
231
234
  watch.notify(projtype.classID, { deleted: [resID] })
232
235
  list(false, { ok: true})
@@ -293,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
293
296
  *
294
297
  * Nide.proj/
295
298
  * nide_cliser.cfg - JSON definition of the project (see below)
296
- * NaanIDE_version.txt - [default] substitution file defining 1.0.3-1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.0.6-1 etc.
297
300
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
298
301
  * build/ - [optional] default build output location
299
302
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -306,6 +309,10 @@ closure projConnector(projman, projtype, local connector, watch) {
306
309
  * build: <build instructions dictionary>
307
310
  * }
308
311
  *
312
+ * A project configuration also stores local data that is not in any file, which is useful for
313
+ * items like accounts & locations, or credentials that should not be "on disk". Project data from
314
+ * the .cfg file is available in config.configDict, while local data is in config.localDict.
315
+ *
309
316
  * Given a new projConfig instance, you can either load (which fails of the project is not valid)
310
317
  * or create (which fails if the project already exists.) The load operation will update the where
311
318
  * argument with the actual name and type found. Unless other noted the methods all return standard
@@ -316,9 +323,12 @@ closure projConnector(projman, projtype, local connector, watch) {
316
323
  proj_configDataFolder = "Nide.proj/";
317
324
  proj_cliserConfigFile = "nide_cliser.cfg";
318
325
 
319
- closure projConfig(projman, where, local procon, fs, configpath) {
326
+ closure projConfig(projman, where, projID, local procon, fs, configpath) {
320
327
  procon = new(object, this)
321
328
  procon.name = where.name
329
+ procon.dirName = proj_configDataFolder.slice(0,-1) // remove terminal "/"
330
+ if projID
331
+ procon.localID = projID.concat("-data")
322
332
 
323
333
  // validate
324
334
  //
@@ -376,6 +386,15 @@ closure projConfig(projman, where, local procon, fs, configpath) {
376
386
  return (list(Error("can't parse project configuration", where.path, exception))) }
377
387
  procon.where_name = where.name // copy name to config
378
388
  procon.where_typeID = where.typeID // copy typeID to config
389
+ if procon.localID {
390
+ `(error, data) = projman.configLocal.accessResource(procon.localID)
391
+ if data
392
+ procon.localDict = data
393
+ else {
394
+ procon.localDict = { }
395
+ projman.configLocal.addResource(procon.localID, procon.localDict)
396
+ }
397
+ }
379
398
  list(false, procon.configDict)
380
399
  }
381
400
 
@@ -384,6 +403,11 @@ closure projConfig(projman, where, local procon, fs, configpath) {
384
403
  // Write procon.configDict to our configuration file, returning a result tuple.
385
404
  //
386
405
  closure writeConfig(local error, info, data) {
406
+ if procon.localID {
407
+ `(error, data) = projman.configLocal.updateResource(procon.localID, procon.localDict)
408
+ if error
409
+ debuglog("projConfig.writeConfig cannot write local storage:", ErrorString(error))
410
+ }
387
411
  `(error, data) = fs.writeFile(configpath, JSONstringify(procon.configDict))
388
412
  if !error {
389
413
  procon.configMD5 = data
@@ -402,6 +426,8 @@ closure projConfig(projman, where, local procon, fs, configpath) {
402
426
  // not already be loaded. The provided name overrides any name in the where argument above, but
403
427
  // path and locationID must be specified. Returns a standard result tuple.
404
428
  //
429
+ // ### we need to update this for optional multiple-configuraiton builds
430
+ //
405
431
  procon.create = closure create(name, local error, data, configpath) {
406
432
  if procon.loaded
407
433
  return (list(Error("Project.create: project already loaded")))
@@ -457,7 +483,9 @@ closure projConfig(projman, where, local procon, fs, configpath) {
457
483
  error = Error("project configuration not a directory", where.path)
458
484
  else
459
485
  `(error, data) = readConfig() }
460
- if error
486
+ if error.code == "ENOENT"
487
+ return (list(Error("Project <b>".concat(where.name, "</b>:<br>not found at ", where.path))))
488
+ else if error
461
489
  return (list(Error("Project.load failed", error)))
462
490
  procon.loaded = true
463
491
  list(false, procon.configDict)
@@ -496,15 +524,63 @@ closure projConfig(projman, where, local procon, fs, configpath) {
496
524
  procon.configDict = newconfig
497
525
  `(error, data) = writeConfig()
498
526
  }
499
-
527
+
528
+ // stages
529
+ //
530
+ // Return a tuple of stages in the project configuration.
531
+ //
532
+ procon.stages = function stages() {
533
+ procon.configDict."build-rules".*
534
+ }
535
+
536
+ // currentStage
537
+ //
538
+ // Validate and return the current stage of the project, or the first one if there isn't a
539
+ // previous selection, or false if no valid stages exist.
540
+ //
541
+ procon.currentStage = function currentStage(local stage, stagelist) {
542
+ stage = procon.localDict.currentStage
543
+ stagelist = stages()
544
+ if stage && !member(stage, stagelist)
545
+ stage = false
546
+ if stagelist && !stage
547
+ stage = stagelist.0
548
+ stage
549
+ }
550
+
551
+ // setCurrentStage
552
+ //
553
+ // Set the current stage.
554
+ //
555
+ procon.setCurrentStage = function setCurrentStage(stage) {
556
+ if member(stage, stages()) {
557
+ procon.localDict.currentStage = stage
558
+ update() // ### errors here are never reported
559
+ }
560
+ }
561
+
500
562
  // buildrules
501
563
  //
502
- // Get the current build rules.
564
+ // Get the build rules for the named stage.
503
565
  //
504
- function buildrules(local rules) {
505
- rules = procon.configDict."build-rules".default // "default" will be switchable later
566
+ function buildrules(stage, local rules, common, rule, key) {
567
+ rules = procon.configDict."build-rules"[stage]
506
568
  if !rules
507
569
  rules = procon.configDict.build // fallback to old config structure
570
+ rules = new(rules)
571
+ common = procon.configDict."build-common"
572
+ if common {
573
+ for rule in common
574
+ if !rules[rule]
575
+ rules[rule] = common[rule] // common rule was not overridden
576
+ else if array(common[rule], rules[rule])
577
+ rules[rule] = common[rule].concat(rules[rule]) // merge common array with rule
578
+ else if dictionary(common[rule], rules[rule]) {
579
+ for key in common[rule] // merge common dictionary with rule
580
+ if !rules[rule][key]
581
+ rules[rule][key] = common[rule][key]
582
+ }
583
+ }
508
584
  rules
509
585
  }
510
586
 
@@ -512,16 +588,18 @@ closure projConfig(projman, where, local procon, fs, configpath) {
512
588
  //
513
589
  // Run the project by building and then launching.
514
590
  //
515
- procon.run = closure run(buildonly, local rules, builder, buildpath, error, buildnum) {
591
+ procon.run = closure run(stage, buildonly, local rules, builder, buildpath, error, buildnum) {
516
592
  if !procon.loaded
517
593
  return (list(Error("project not loaded")))
518
- rules = buildrules()
594
+ rules = buildrules(stage)
519
595
  if !rules || !rules.products
520
596
  return (list(Error("build rules missing or incomplete")))
521
597
  buildpath = fs.path.join(proj_configDataFolder, rules.products)
522
598
  rules.name = procon.where_name
523
599
  rules.typeID = procon.where_typeID
524
- `(error, builder) = Builder(rules, "debug", fs, "", buildpath)
600
+ if !rules.operation
601
+ rules.operation = "debug"
602
+ `(error, builder) = Builder(rules, rules.operation, fs, "", buildpath)
525
603
  if error
526
604
  return (list(error))
527
605
  `(error, buildnum) = builder.increment()
@@ -529,11 +607,14 @@ closure projConfig(projman, where, local procon, fs, configpath) {
529
607
  ErrorDebuglog("build number increment failed", error)
530
608
  return (list(error))
531
609
  }
532
- `(error) = builder.make()
610
+ `(error) = builder.make({
611
+ stage: stage
612
+ buildnum: buildnum
613
+ })
533
614
  if !error {
534
- if buildonly
535
- builder.refresh(projman.track)
536
- else
615
+ if builder.refresh(projman.track)
616
+ buildonly = !buildonly // reverse if refresh
617
+ if !buildonly
537
618
  `(error) = builder.run(projman.track)
538
619
  }
539
620
  builder.destroy()
@@ -547,12 +628,13 @@ closure projConfig(projman, where, local procon, fs, configpath) {
547
628
  //
548
629
  // Publish the project to its configured location.
549
630
  //
550
- procon.publish = closure publish(local rules, publish, error, outfs, inpath) {
551
- rules = buildrules()
552
- publish = procon.configDict.publish
553
- if !rules || !publish.where
631
+ procon.publish = closure publish(stage, local rules, where, publish, error, outfs, inpath) {
632
+ rules = buildrules(stage)
633
+ publish = rules.publish
634
+ where = procon.localDict.publish[stage].where
635
+ if !rules || !where
554
636
  return (list(Error("no project publish location configured")))
555
- `(error, outfs) = projman.locate.connect(publish.where.locationID, "NideFS", publish.where.path)
637
+ `(error, outfs) = projman.locate.connect(where.locationID, "NideFS", where.path)
556
638
  if error
557
639
  return (list(Error("can't access publishing filesystem", error)))
558
640
  inpath = fs.path.join(proj_configDataFolder, rules.products, publish.input)