@naanlang/naan 1.0.2 → 1.0.5

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 (56) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +10 -8
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +172 -22
  5. package/dist/naan.min.js +6 -6
  6. package/frameworks/browser/https_request.nlg +120 -0
  7. package/frameworks/browser/sworker.js +299 -91
  8. package/frameworks/browser/terminals.nlg +28 -13
  9. package/frameworks/browser/workers.nlg +27 -12
  10. package/frameworks/client/apiclient.nlg +17 -4
  11. package/frameworks/client/psm_client.nlg +184 -81
  12. package/frameworks/common/common.nlg +105 -25
  13. package/frameworks/common/preferences.nlg +1 -0
  14. package/frameworks/common/watching.nlg +1 -0
  15. package/frameworks/node/apiserver.nlg +31 -15
  16. package/frameworks/node/filesystem.nlg +230 -46
  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 +113 -37
  21. package/frameworks/project/build.nlg +40 -23
  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 +128 -40
  28. package/frameworks/running/debugnub.nlg +2 -5
  29. package/frameworks/running/executors.nlg +1 -0
  30. package/frameworks/storage/csv.nlg +120 -0
  31. package/frameworks/storage/dbt_pouch.nlg +41 -25
  32. package/frameworks/storage/psm.nlg +108 -28
  33. package/frameworks/storage/psm_dbtables.nlg +7 -3
  34. package/frameworks/storage/resources.nlg +30 -2
  35. package/lib/browser/env_web.js +170 -20
  36. package/lib/browser/env_webworker.js +1 -1
  37. package/lib/browser/require.js +1 -1
  38. package/lib/core/naanlib.js +6 -6
  39. package/package.json +2 -1
  40. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
  41. package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
  42. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +8 -7
  43. package/plugins/serviceAws/aws_dynamo.nlg +715 -148
  44. package/plugins/serviceAws/aws_dynextra.nlg +294 -0
  45. package/plugins/serviceAws/aws_lambda.nlg +11 -4
  46. package/plugins/serviceAws/aws_s3.nlg +44 -14
  47. package/plugins/serviceAws/dbt_aws.nlg +58 -45
  48. package/plugins/serviceAws/psm_aws.nlg +61 -40
  49. package/plugins/serviceAws/serviceAws.nlg +7 -4
  50. package/plugins/serviceGitHub/psm_github.nlg +41 -25
  51. package/plugins/serviceGitLab/psm_gitlab.nlg +41 -25
  52. package/test/harness.nlg +3 -1
  53. package/test/test_01_core.nlg +5 -5
  54. package/test/test_02_context.nlg +4 -4
  55. package/test/test_03_jsinterop.nlg +9 -3
  56. package/plugins/serviceAws/aws_cognito.nlg +0 -76
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -36,6 +36,7 @@ closure MakeAPI(port, options, callback, local chroot, server) {
36
36
  server.psm = require("./psm_server.nlg").MakeServerPSM(server)
37
37
  server.root = chroot
38
38
  server.app = express()
39
+ server.guid = options.guid
39
40
  server.notifyq = []
40
41
  server.responseq = []
41
42
  server.projects = { }
@@ -99,8 +100,9 @@ closure MakeAPI(port, options, callback, local chroot, server) {
99
100
  // body parsing
100
101
  //
101
102
 
102
- server.app.use(bodyParser.text({limit: "20mb"}))
103
- server.app.use(bodyParser.raw({limit: "20mb"}))
103
+ server.app.use(bodyParser.json({limit: "5mb"}))
104
+ server.app.use(bodyParser.text({limit: "25mb"}))
105
+ server.app.use(bodyParser.raw({limit: "25mb"}))
104
106
 
105
107
  //
106
108
  // Express.js endpoints
@@ -108,8 +110,12 @@ closure MakeAPI(port, options, callback, local chroot, server) {
108
110
 
109
111
  server.app.use(function(req,res,next) {
110
112
  res.header("Access-Control-Allow-Origin", "*")
111
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
112
- next()
113
+ res.header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
114
+ res.header("Access-Control-Allow-Headers", "x-naanlang-api-guid")
115
+ if req.method == "OPTIONS"
116
+ res.sendStatus(200)
117
+ else
118
+ next()
113
119
  })
114
120
 
115
121
  server.app.use("", express.static(chroot))
@@ -121,15 +127,18 @@ closure MakeAPI(port, options, callback, local chroot, server) {
121
127
  // "/"
122
128
 
123
129
  server.app.get("/", function(req, res) {
124
- res.send("<u>NaanIDE API</u><br>\r\n"
130
+ res.send("<u>naan-server API</u><br>\r\n"
125
131
  " /readfile?path=<i>filepath</i><br>\r\n"
126
132
  " /status?timeout=<i>msec</i>&after=<i>stamp</i><br>\r\n")
127
133
  })
128
134
 
129
135
  // "/readfile"
130
136
 
131
- server.app.get("/readfile", closure(req, res) {
132
- if not string(req.query.path)
137
+ server.app.get("/readfile", closure(req, res, local guid) {
138
+ guid = req.get("x-naanlang-api-guid")
139
+ if server.guid && guid != server.guid
140
+ res.status(403).send("unauthorized")
141
+ else if not string(req.query.path)
133
142
  res.status(400).send("Invalid filename")
134
143
  else if RegExp("^\\.\\.[/\\\\]|[/\\\\]\\.\\.$|[/\\\\]\\.\\.[/\\\\]").test(req.query.path)
135
144
  res.status(403).send("\"Parent path references forbidden\"")
@@ -156,7 +165,10 @@ closure MakeAPI(port, options, callback, local chroot, server) {
156
165
 
157
166
  // "/status"
158
167
 
159
- server.app.get("/status", closure(req,res, local timeoutms, after, responder) {
168
+ server.app.get("/status", closure(req, res, local guid, timeoutms, after, responder) {
169
+ guid = req.get("x-naanlang-api-guid")
170
+ if server.guid && guid != server.guid
171
+ res.status(403).send("unauthorized")
160
172
  timeoutms = toint(req.query.timeout)
161
173
  after = toint(req.query.after)
162
174
  if not integer(timeoutms) or timeoutms > 10000
@@ -187,8 +199,11 @@ closure MakeAPI(port, options, callback, local chroot, server) {
187
199
 
188
200
  // "/psm"
189
201
 
190
- server.app.all("/psm", closure(req, res) {
191
- if !server.hostfs
202
+ server.app.all("/psm", closure(req, res, local guid) {
203
+ guid = req.get("x-naanlang-api-guid")
204
+ if server.guid && guid != server.guid && req.method !== "OPTIONS"
205
+ res.status(403).send("unauthorized")
206
+ else if !server.hostfs
192
207
  res.status(503).send("host filesystem not available")
193
208
  else
194
209
  server.hostfs.api(req, res)
@@ -227,7 +242,6 @@ closure MakeAPI(port, options, callback, local chroot, server) {
227
242
  server.ws = WebSocketServer(server)
228
243
  server.port = port
229
244
  callback(false, port, server) // notify of success
230
- debuglog("server open on port", port)
231
245
  result = server.psm.conns.NodeFS.connect("Host", "NideFS", list("")) // ### this should go through resource tracker
232
246
  if result.0
233
247
  ErrorDebuglog(Error("can't access HostFS", result.0))
@@ -264,7 +278,7 @@ closure WebSocketServer(server, local websocks) {
264
278
  // Event handlers
265
279
 
266
280
  websocks.wss.on("connection", function(socket, request) {
267
- websocks.conns.push(WesCon(websocks, socket, request))
281
+ websocks.conns.push(WesCon(server, websocks, socket, request))
268
282
  })
269
283
 
270
284
  server.wss.on("error", function(error) {
@@ -303,7 +317,7 @@ closure WebSocketServer(server, local websocks) {
303
317
  *
304
318
  */
305
319
 
306
- closure WesCon(websocks, socket, request, local wescon) {
320
+ closure WesCon(server, websocks, socket, request, local wescon) {
307
321
  wescon = new(object, this)
308
322
  wescon.socket = socket
309
323
  wescon.serverIDs = []
@@ -315,7 +329,9 @@ closure WesCon(websocks, socket, request, local wescon) {
315
329
  socket.on("message", function onmessage(text, local message, error) {
316
330
  try {
317
331
  message = new(js.g.JSON.parse(text))
318
- if message.clientID && websocks.subsystems[message.serverID] {
332
+ if server.guid && message.guid != server.guid
333
+ error = Error("unauthorized")
334
+ else if message.clientID && websocks.subsystems[message.serverID] {
319
335
  wescon.serverIDs.push(message.serverID) // ### track subsystems for closing
320
336
  websocks.subsystems[message.serverID].wsReceive(message, wescon)
321
337
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2017-2021 by Richard C. Zulch
9
+ * Copyright (c) 2017-2022 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -23,7 +23,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
23
23
  if !rootpath
24
24
  return (list(Error("mandatory rootpath missing")))
25
25
  else if rootpath == ""
26
- rootpath = js.g.process.cwd() // NodeJS uses CWD for empty rootpath
26
+ rootpath = JSpath.sep
27
27
  files = new(object, this)
28
28
  files.rootpath = rootpath
29
29
  files.path = JSpath
@@ -54,24 +54,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
54
54
  // Convert a path argument to a local path, returning a standard result tuple. If the path is
55
55
  // invalid this will report why. Otherwise this returns the correct localpath, which is the path
56
56
  // we use with the underlying API.
57
+ // Win32 driver letters are annoying here. If the rootpath specifies a drive letter then the
58
+ // paths must match it. If the rootpath doesn't specify a drive letter then path can have any
59
+ // drive letter, or none.
57
60
 
58
61
  badCreatePathRegEx = RegExp("[:]|[/\\\\][/\\\\]|[/\\\\]$|(^|[/\\\\])[.][.]?$")
59
62
 
60
- closure localPath(path, create, local pattern, testpath) {
63
+ files.localPath = closure localPath(path, create, local pattern, testpath, locroot) {
61
64
  if !path
62
65
  return (list(Error("filesystem path false")))
63
66
  else if !string(path)
64
67
  return (list(Error("filesystem path invalid:", typeof(path))))
68
+ if JSpath === JSpath.win32 {
69
+ locroot = JSpath.resolve(path, rootpath) // use path's drive letter if we don't have one
70
+ pattern = "\\:\\" // unchanged and unique on win32
71
+ }
72
+ else {
73
+ locroot = rootpath
74
+ pattern = "/:/" // unchanged and unique on posix
75
+ }
65
76
  if JSpath.isAbsolute(path)
66
- path = JSpath.resolve(path) // puts C: in front of \abs\win\path
77
+ path = JSpath.resolve(locroot, path) // add drive letter to absolute path
67
78
  else
68
79
  path = JSpath.normalize(path) // normalize for current platform
69
- if JSpath === JSpath.win32
70
- pattern = "\\:\\" // unchanged and unique on win32
71
- else
72
- pattern = "/:/" // unchanged and unique on posix
73
- if ((testpath = commonParents(path, rootpath)) != "")
74
- list(false, JSpath.resolve(rootpath, path)) // ### shouldn't be so lax on this test
80
+ if (commonParents(path, locroot) != "")
81
+ list(false, JSpath.resolve(locroot, path))
75
82
  else if ((testpath = JSpath.resolve(pattern, path)).indexOf(pattern.slice(0,-1)) < 0)
76
83
  list(Error("filesystem path reaches above root:", path))
77
84
  else if create && testpath.indexOf(pattern) < 0
@@ -79,7 +86,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
79
86
  else if create && badCreatePathRegEx.test(path)
80
87
  list(Error("filesystem create path invalid", path))
81
88
  else
82
- list(false, JSpath.resolve(rootpath, path))
89
+ list(false, JSpath.resolve(locroot, path))
90
+ }
91
+
92
+ // folderPath
93
+ //
94
+ // Return a folder path for the specified folderID, relative to our root. If the folder path is
95
+ // not within our root then it is returned as an absolute path.
96
+
97
+ files.folderPath = function folderPath(folderID, callback, local envID, path) {
98
+ if !callback
99
+ return (syncAdapter(folderPath, folderID))
100
+ envID = {
101
+ "HomeDir": "HOME",
102
+ "TempDir": "TMPDIR",
103
+ "PackageDir": "npm_config_prefix"
104
+ }[folderID]
105
+ if envID {
106
+ path = js.g.process.env[envID]
107
+ if path.startsWith(rootpath)
108
+ path = path.slice(rootpath.length) // remove rootpath from beginning
109
+ }
110
+ if path
111
+ callback(false, path)
112
+ else
113
+ callback(Error("unknown folderID:", folderID))
83
114
  }
84
115
 
85
116
  // winDrives
@@ -273,11 +304,48 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
273
304
  callback(false, { md5: md5data })
274
305
  })
275
306
  }
276
-
307
+
308
+ // readlink
309
+ //
310
+ // Read the contents of a symbolic link.
311
+
312
+ files.readlink = closure readlink(path, callback, local error, localpath) {
313
+ if !callback
314
+ return (syncAdapter(readlink, path))
315
+ `(error, localpath) = localPath(path)
316
+ if error
317
+ return (asyncResult(callback, error))
318
+ fs.readlink(localpath, function(err, linkString) {
319
+ if err
320
+ callback(Error("fs.readlink failed:", localpath, err))
321
+ else
322
+ callback(false, linkString)
323
+ })
324
+ }
325
+
326
+ // symlink
327
+ //
328
+ // Create a symbolic link "path" that points to "target". No options are currently supported.
329
+
330
+ files.symlink = closure symlink(target, path, options, callback,
331
+ local error, localpath) {
332
+ if !callback
333
+ return (syncAdapter(symlink, target, path, options))
334
+ `(error, localpath) = localPath(path)
335
+ if error
336
+ return (asyncResult(callback, error))
337
+ fs.symlink(target, localpath, function(err) {
338
+ if err
339
+ callback(Error("fs.symlink failed:", target, localpath, err))
340
+ else
341
+ callback(false, { ok: true })
342
+ })
343
+ }
344
+
277
345
  // delete
278
346
  //
279
347
  // Delete the file at the specified path, or a directory if the options dictionary includes:
280
- // recursive: true -- "recursively" delete directory and children
348
+ // recursive: true -- recursively delete directory and children
281
349
 
282
350
  files.delete = closure delete(path, options, callback, local error, localpath) {
283
351
  if !callback
@@ -286,9 +354,9 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
286
354
  if error
287
355
  return (asyncResult(callback, error))
288
356
  if options.recursive
289
- fs.rmdir(localpath, function(err) {
357
+ fs.rm(localpath, { recursive: true, force: true }, function(err) {
290
358
  if err
291
- callback(Error("fs.rmdir failed:", localpath, err))
359
+ callback(Error("fs.rm failed:", localpath, err))
292
360
  else
293
361
  callback(false, { ok: true })
294
362
  })
@@ -303,15 +371,22 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
303
371
 
304
372
  // info
305
373
  //
306
- // Report info on a file or directory.
374
+ // Report info on a file or directory. The options are:
375
+ // {
376
+ // follow: <boolean> // follow links
377
+ // }
307
378
 
308
- files.info = closure info(path, callback, local error, localpath) {
379
+ files.info = closure info(path, options, callback, local error, localpath, fsop) {
309
380
  if !callback
310
- return (syncAdapter(info, path))
381
+ return (syncAdapter(info, path, options))
311
382
  `(error, localpath) = localPath(path)
312
383
  if error
313
384
  return (asyncResult(callback, error))
314
- fs.lstat(localpath, function(err, stat, local data, info, nodetype) {
385
+ if options.follow
386
+ fsop = `stat
387
+ else
388
+ fsop = `lstat
389
+ fs[fsop](localpath, function(err, stat, local data, info, nodetype) {
315
390
  if err
316
391
  return (callback(Error("fs.stat failed:", localpath, err)))
317
392
  info = {
@@ -336,9 +411,102 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
336
411
  })
337
412
  }
338
413
 
414
+ // setInfo
415
+ //
416
+ // Set selected info on a file or directory. The info dictionary has the same layout as that
417
+ // returned by files.info(), and every applicable key defined in the dictionary is set. To omit
418
+ // setting some of the information, remove its key from the info dictionary. The applicable keys
419
+ // are:
420
+ // {
421
+ // mtimeMs: <milliseconds>
422
+ // mtime: <date>
423
+ // nodejs_stat: {
424
+ // mode: <number>
425
+ // uid: <uid>
426
+ // gid: <gid>
427
+ // atimeMs: <milliseconds>
428
+ // atime: <date>
429
+ // mtimeMs: <milliseconds>
430
+ // mtime: <date>
431
+ // }
432
+ // }
433
+ // If more than one timestamp exists of a certain type, the millisecond version is used. Some
434
+ // values are set together by the OS, like uid & guid, or mtime & atime. Specifying just one of
435
+ // these will cause setInfo to use the same value for both.
436
+
437
+ files.setInfo = closure setInfo(path, info, callback,
438
+ local error, localpath, mtime, atime, uid, gid) {
439
+ if !callback
440
+ return (syncAdapter(setInfo, path, info))
441
+ if !dictionary(info) && !xobject(info)
442
+ error = Error("setInfo: invalid argument:", typeof(info))
443
+ else
444
+ `(error, localpath) = localPath(path)
445
+ if error
446
+ return (asyncResult(callback, error))
447
+ atime = info.nodejs_stat.atimeMs/1000.0 || info.nodejs_stat.atime
448
+ mtime = info.mtimeMs/1000.0 || info.mtime
449
+ if !mtime
450
+ mtime = info.nodejs_stat.mtimeMs/1000.0 || info.nodejs_stat.mtime
451
+ if atime || mtime {
452
+ atime = atime || mtime
453
+ mtime = mtime || atime
454
+ `(error) = setTimes(path, atime, mtime)
455
+ }
456
+ if !error && info.nodejs_stat.mode
457
+ `(error) = setMode(path, info.nodejs_stat.mode)
458
+ uid = info.nodejs_stat.uid
459
+ gid = info.nodejs_stat.gid
460
+ if !error && (uid || gid) {
461
+ uid = uid || gid
462
+ gid = gid || uid
463
+ `(error) = setOwnerGroup(path, uid, gid)
464
+ }
465
+ if error
466
+ callback(Error("setInfo failed:", localpath, error))
467
+ else
468
+ callback(false, { ok: true})
469
+ }
470
+
471
+ // setOwnerGroup
472
+ //
473
+ // Set owner/group on a file or directory.
474
+
475
+ files.setOwnerGroup = closure setOwnerGroup(path, uid, gid, callback, local error, localpath) {
476
+ if !callback
477
+ return (syncAdapter(setOwnerGroup, path, uid, gid))
478
+ `(error, localpath) = localPath(path)
479
+ if error
480
+ return (asyncResult(callback, error))
481
+ fs.chown(localpath, uid, gid, function(err, stat) {
482
+ if err
483
+ callback(Error("fs.chown failed:", localpath, err))
484
+ else
485
+ callback(false, { ok: true})
486
+ })
487
+ }
488
+
489
+ // setMode
490
+ //
491
+ // Set mode bits on a file or directory.
492
+
493
+ files.setMode = closure setMode(path, mode, callback, local error, localpath) {
494
+ if !callback
495
+ return (syncAdapter(setMode, path, mode))
496
+ `(error, localpath) = localPath(path)
497
+ if error
498
+ return (asyncResult(callback, error))
499
+ fs.chmod(localpath, mode, function(err, stat) {
500
+ if err
501
+ callback(Error("fs.chmod failed:", localpath, err))
502
+ else
503
+ callback(false, { ok: true})
504
+ })
505
+ }
506
+
339
507
  // setTimes
340
508
  //
341
- // Set timestamps on a file or directory.
509
+ // Set timestamps on a file or directory. The timestamps can be dates or numeric seconds.
342
510
 
343
511
  files.setTimes = closure setTimes(path, atime, mtime, callback, local error, localpath) {
344
512
  if !callback
@@ -346,9 +514,6 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
346
514
  `(error, localpath) = localPath(path)
347
515
  if error
348
516
  return (asyncResult(callback, error))
349
- if !numeric(atime, mtime)
350
- return (asyncResult(callback, Error("invalid arguments:", atime, mtime)))
351
- localpath = JSpath.resolve(rootpath, localpath)
352
517
  fs.utimes(localpath, atime, mtime, function(err, stat) {
353
518
  if err
354
519
  callback(Error("fs.utimes failed:", localpath, err))
@@ -375,7 +540,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
375
540
  if name { // this record is fetchable
376
541
  ++pending.active
377
542
  filepath = JSpath.join(parentpath, name)
378
- files.info(filepath, function(error, info) {
543
+ files.info(filepath, false, function(error, info) {
379
544
  if error
380
545
  input[kdex].error = error // this record failed
381
546
  else
@@ -398,7 +563,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
398
563
 
399
564
  // dirList
400
565
  //
401
- // Return array of files in a directory if it exists. If options.head is true then this reads
566
+ // Return array of files in a directory if it exists. If options.stat is true then this reads
402
567
  // info for each file as well.
403
568
 
404
569
  files.dirList = closure dirList(path, options, callback, local error, localpath, output, nodelist, file) {
@@ -426,7 +591,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
426
591
  output.children.push({
427
592
  name: file
428
593
  })
429
- if options.head
594
+ if options.stat
430
595
  statfetch(output.children, localpath) // read all the info records
431
596
  callback(false, output)
432
597
  })
@@ -505,8 +670,10 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
505
670
  // Recursively build the tree
506
671
 
507
672
  closure recurTree(localpath, depth, parent) {
508
- if depthlimit && depth >= depthlimit
673
+ if depthlimit && depth >= depthlimit {
674
+ recurNext()
509
675
  return
676
+ }
510
677
  ++pending.active
511
678
  fs.readdir(localpath, { }, function(err, nodes, local child, node, stat) {
512
679
  if err.code == "ENOTDIR"
@@ -536,13 +703,21 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
536
703
  parent: child
537
704
  }) } }
538
705
  --pending.active
539
- while queue.length > 0 && pending.active < 20 {
540
- node = queue.pop()
541
- recurTree(node.path, node.depth, node.parent) }
542
- if pending.active == 0 && queue.length == 0
543
- pending.signal(list(false, root))
706
+ recurNext()
544
707
  })
545
708
  }
709
+
710
+ // recurNext
711
+ //
712
+ // Pull more queued items and signal when done.
713
+
714
+ function recurNext(local node) {
715
+ while queue.length > 0 && pending.active < 20 {
716
+ node = queue.pop()
717
+ recurTree(node.path, node.depth, node.parent) }
718
+ if pending.active == 0 && queue.length == 0
719
+ pending.signal(list(false, root))
720
+ }
546
721
 
547
722
  `(error, localpath) = localPath(path)
548
723
  if error
@@ -638,7 +813,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
638
813
  if dest.slice(-1) == JSpath.sep // new location, else entirely new pathname
639
814
  localdest = JSpath.join(localdest, JSpath.basename(srcpath))
640
815
  }
641
- fs.lstat(localdest, closure(err, stat, local cmd, args) {
816
+ fs.stat(localdest, closure(err, stat, local cmd, args) {
642
817
  if !err && !options.overwrite
643
818
  callback(Error("copy destination already exists",
644
819
  {
@@ -649,6 +824,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
649
824
  existing: localdest
650
825
  }))
651
826
  else {
827
+ debuglog("==> Warning: files.copy needs to be rewritten with fs primitives")
652
828
  if options.move {
653
829
  cmd = "mv"
654
830
  args = [localsrc, localdest] }
@@ -717,6 +893,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
717
893
  files.minimize = closure minimize(input, callback, local child, stdinStream) {
718
894
  if !callback
719
895
  return (syncAdapter(minimize, input))
896
+ debuglog("==> Warning: files.minimize is deprecated")
720
897
  child = nodecp.execFile("uglifyjs",
721
898
  ["--compress", "drop_debugger=false", "--mangle", "eval"], // ### uglifyjs options should be parameterized
722
899
  function(err, stdout, stderr) {
@@ -742,6 +919,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
742
919
  files.unzip = closure unzip(inpath, outpath, callback) {
743
920
  if !callback
744
921
  return (syncAdapter(unzip, inpath, outpath))
922
+ debuglog("==> Warning: files.unzip is deprecated")
745
923
  nodecp.execFile("unzip",
746
924
  ["-qo", inpath, "-d", outpath], // quiet; no queries
747
925
  { }, // execution options
@@ -757,21 +935,27 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
757
935
  //
758
936
  // touch
759
937
  //
760
- // Execute the touch utility, returning the result as a standard tuple: `(error, result)
938
+ // Touch the specified file or directory, returning the result as a standard result tuple
939
+ // `(error, result). When only the path is specified this sets the last-access and last-modified
940
+ // times to the current Date(). Otherwise the dates are set to the specified value(s). Note that
941
+ // this sets the times of a symbolic link file itself, not the file the link points to.
761
942
  //
762
- files.touch = closure touch(outpath, callback) {
943
+ files.touch = closure touch(path, atime, mtime, callback, local localpath) {
763
944
  if !callback
764
- return (syncAdapter(touch, outpath))
765
- nodecp.execFile("touch",
766
- [outpath],
767
- { }, // execution options
768
- function(err, stdout, stderr) {
769
- if err
770
- callback(Error("touch failed", err, stderr))
771
- else
772
- callback(false, { ok: true })
773
- }
774
- )
945
+ return (syncAdapter(touch, path, atime, mtime))
946
+ `(error, localpath) = localPath(path)
947
+ if error
948
+ return (asyncResult(callback, error))
949
+ if !atime && !mtime
950
+ atime = mtime = Date()
951
+ else if atime && !mtime
952
+ mtime = atime
953
+ fs.lutimes(localpath, atime, mtime, function(err) {
954
+ if err
955
+ callback(Error("touch failed", err))
956
+ else
957
+ callback(false, { ok: true })
958
+ })
775
959
  }
776
960
 
777
961
  //
@@ -23,6 +23,28 @@ closure Gitter(fs, local gitt) {
23
23
  gitt = new(object, this)
24
24
  gitt.roots = {}
25
25
 
26
+ // gitPath
27
+ //
28
+ // Convert a Windows path to a posix (git) path if needed.
29
+ //
30
+ function gitPath(fspath) {
31
+ if fs.path.sep == "/"
32
+ fspath
33
+ else
34
+ fspath.split(fs.path.sep).join("/")
35
+ }
36
+
37
+ // fsPath
38
+ //
39
+ // Convert a posix (git) path to a filesystem path if needed.
40
+ //
41
+ function fsPath(gitpath) {
42
+ if fs.path.sep == "/"
43
+ gitpath
44
+ else
45
+ gitpath.split("/").join(fs.path.sep)
46
+ }
47
+
26
48
  // repoRoot
27
49
  //
28
50
  // Compute the path from the fs root to the repo root.
@@ -54,11 +76,10 @@ closure Gitter(fs, local gitt) {
54
76
  // Compute the bridge path to an absolute repopath (which can point inside a repo) from the
55
77
  // phyiscal repo root. If these are the same, or an error occurs, then the result is "".
56
78
  //
57
-
58
79
  function bridgePath(repopath, local top) {
59
80
  top = repoRoot(repopath).1
60
81
  if top && repopath.startsWith(top) && repopath.length > top.length
61
- top = repopath.slice(top.length+1).concat("/") // move leading "/" of bridge path to trailing
82
+ top = repopath.slice(top.length+1).concat(fs.path.sep) // move leading "/" of bridge path to trailing
62
83
  else
63
84
  "" // we are looking at the root, or an error
64
85
  }
@@ -151,12 +172,12 @@ closure Gitter(fs, local gitt) {
151
172
  outLE: "\x00" // lines delimited by nulls
152
173
  }
153
174
  if options.pathspec
154
- execOptions.cmdargs = execOptions.cmdargs.concat("--", options.pathspec)
175
+ execOptions.cmdargs = execOptions.cmdargs.concat("--", gitPath(options.pathspec))
155
176
  result = []
156
177
  execOptions.outproc = function(output, liner, kill, local line, path) {
157
178
  while (line = liner.read())
158
179
  if line != "" {
159
- path = line.substring(3)
180
+ path = fsPath(line.substring(3))
160
181
  if path.startsWith(bridge) { // must start with bridge to be within repopath
161
182
  path = path.slice(bridge.length)
162
183
  result.push({
@@ -183,7 +204,7 @@ closure Gitter(fs, local gitt) {
183
204
  // Retrieve the content of a specific file and revision. The file pathspec must be the path of
184
205
  // the file within the repo.
185
206
 
186
- gitt.showfile = closure showfile(repopath, options, callback, local execOptions, result) {
207
+ gitt.showfile = closure showfile(repopath, options, callback, local execOptions) {
187
208
  if !callback
188
209
  return (syncAdapter(showfile, repopath, options))
189
210
  if !string(repopath)
@@ -192,7 +213,7 @@ closure Gitter(fs, local gitt) {
192
213
  return (asyncResult(callback, Error("both pathspec and hash required:", options.pathspec, options.hash)))
193
214
  repopath = fs.path.resolve(fs.rootpath, repopath)
194
215
  execOptions = {
195
- cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), options.pathspec)]
216
+ cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), gitPath(options.pathspec))]
196
217
  execops: {
197
218
  cwd: repopath
198
219
  }