@sap/cds-dk 4.7.3 → 4.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/bin/activate.js +5 -1
  3. package/bin/add.js +2 -2
  4. package/lib/client/activate.js +8 -3
  5. package/lib/client/base_command.js +5 -1
  6. package/lib/client/helper/cf_helper.js +5 -2
  7. package/lib/client/params.js +16 -2
  8. package/lib/client/settings_manager.js +15 -8
  9. package/lib/compile/openapi/csdl2openapi.js +6 -8
  10. package/lib/compile/to-xsuaa.js +0 -14
  11. package/lib/import/utils/MetadataConverter.js +6 -4
  12. package/lib/import/v2parser.js +59 -35
  13. package/lib/import/v4parser.js +70 -47
  14. package/lib/init/constants.js +9 -3
  15. package/lib/init/index.js +13 -7
  16. package/lib/init/template/_merging/existences.js +161 -0
  17. package/lib/init/template/approuter/files/default-env.json +9 -0
  18. package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
  19. package/lib/init/template/approuter/files/package.json +9 -0
  20. package/lib/init/template/approuter/files/xs-app.json +8 -0
  21. package/lib/init/template/approuter/index.js +66 -0
  22. package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
  23. package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
  24. package/lib/init/template/auditlog/index.js +38 -0
  25. package/lib/init/template/cf-manifest/index.js +1 -1
  26. package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
  27. package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
  28. package/lib/init/template/hana/index.js +53 -24
  29. package/lib/init/template/java/index.js +1 -5
  30. package/lib/init/template/mta/files/mta.yaml.hbs +24 -220
  31. package/lib/init/template/mta/index.js +25 -17
  32. package/lib/init/template/mtx/files/cds.package.json +24 -0
  33. package/lib/init/template/mtx/files/dependencies.package.json +5 -0
  34. package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
  35. package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
  36. package/lib/init/template/mtx/index.js +126 -29
  37. package/lib/init/template/templateBase.js +14 -0
  38. package/lib/init/template/xsuaa/files/cds.package.json +9 -0
  39. package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
  40. package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
  41. package/lib/init/template/xsuaa/index.js +75 -0
  42. package/lib/init/util/fsUtil.js +17 -3
  43. package/lib/init/util/logger.js +3 -1
  44. package/lib/init/{template/mta → util}/projectReader.js +26 -11
  45. package/lib/init/util/templateUtil.js +267 -3
  46. package/lib/util/exec.d.ts +54 -0
  47. package/lib/util/exec.js +210 -0
  48. package/lib/watch/livereload-server.js +43 -31
  49. package/lib/watch/open.js +1 -1
  50. package/npm-shrinkwrap.json +4089 -15628
  51. package/package.json +7 -8
  52. package/readme.md +1 -1
  53. package/lib/import/internalDoc/edm2cdsTypeMapping.md +0 -38
  54. package/lib/init/template/mtx/package/cds.package.json +0 -29
  55. package/lib/init/template/mtx/package/dependencies.package.json +0 -9
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
-
2
+ const YAML = require('@sap/cds-foss')('yaml');
3
3
  const Mustache = require('mustache');
4
4
 
5
5
  const TEMPLATE_EXTENSION = '.hbs';
@@ -41,7 +41,7 @@ module.exports = class TemplateUtil {
41
41
  }
42
42
 
43
43
  async _processTemplateFile(src, templateValues) {
44
- let content = await this.fsUtil.readFile(src);
44
+ const content = await this.fsUtil.readFile(src);
45
45
  return Mustache.render(content, templateValues);
46
46
  }
47
47
 
@@ -52,4 +52,268 @@ module.exports = class TemplateUtil {
52
52
  _isTemplateFile(filename) {
53
53
  return path.extname(filename) === TEMPLATE_EXTENSION;
54
54
  }
55
- }
55
+
56
+ /*
57
+ Will do a syntactic merge when `semantics` is undefined.
58
+ Syntactic merges are sufficient in some cases,
59
+ e.g merging dependencies in a package.json, as they are
60
+ just maps where you can do a key-value replacement.
61
+ */
62
+ _deepMerge(target, source) {
63
+ if (this._isObject(target) && this._isObject(source)) {
64
+ for (const key in source) {
65
+ if (this._isObject(source[key])) {
66
+ if (!target[key]) Object.assign(target, { [key]: source[key] });
67
+ if ('#overwrite' in source[key]) Object.assign(target, { [key]: source[key]["#overwrite"] })
68
+ else this._deepMerge(target[key], source[key]);
69
+ } else if (Array.isArray(source[key]) && Array.isArray(target[key])) {
70
+ const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse);
71
+ target[key] = unique([...source[key], ...target[key]])
72
+ } else {
73
+ Object.assign(target, { [key]: target[key] || source[key] });
74
+ }
75
+ }
76
+ } else if (Array.isArray(target) && Array.isArray(source)) {
77
+ const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse);
78
+ target = unique([...source, ...target])
79
+ }
80
+ return target || source;
81
+ }
82
+
83
+ async mergeJSON(targetPath, sourcePath, projectDescriptor, semantics) {
84
+ const source = await this.fsUtil.readJSON(sourcePath, projectDescriptor)
85
+
86
+ if (! await this.fsUtil.pathExists(targetPath)) {
87
+ await this.fsUtil.writeJSON(targetPath, source);
88
+ } else if (semantics) {
89
+ const target = await this.fsUtil.readJSON(targetPath)
90
+
91
+ if (!target) return // Has no JSON file
92
+
93
+ const targetYAML = await YAML.parseDocument(YAML.stringify(target))
94
+ const sourceYAML = await YAML.parseDocument(YAML.stringify(source))
95
+ const resultYAML = await this.mergeYAML(targetYAML, sourceYAML, projectDescriptor, semantics)
96
+ const result = YAML.parse(YAML.stringify(resultYAML))
97
+ await this.fsUtil.writeJSON(targetPath, result)
98
+ } else {
99
+ const target = await this.fsUtil.readJSON(targetPath);
100
+ const sourceFile = await this._processTemplateFile(sourcePath, projectDescriptor);
101
+ const source = JSON.parse(sourceFile);
102
+ const result = target && source ? this._deepMerge(target, source) : target || source;
103
+ await this.fsUtil.writeJSON(targetPath, result);
104
+ }
105
+ }
106
+
107
+ async mergeYAML(into, from, projectDescriptor, semantics) {
108
+ const target = typeof into === 'string' ? await this.fsUtil.readYAML(into) : into
109
+ const source = typeof from === 'string' ? await this.fsUtil.readYAML(from, projectDescriptor) : from
110
+
111
+ if (!target || !target.contents) return // Has no YAML file
112
+
113
+ const existenceMap = new Map()
114
+ const templateExistenceMap = new Map()
115
+
116
+ const existences = semantics.existences && semantics.existences.filter(e => e)
117
+
118
+ existences && existences.forEach(existence => {
119
+ existenceMap.set(existence.ref, undefined)
120
+ templateExistenceMap.set(existence.ref, undefined)
121
+ });
122
+
123
+ const missingRelationshipsMap = new Map()
124
+
125
+ const relationships = semantics.relationships && semantics.relationships.filter(r => r)
126
+ relationships && relationships.forEach(relationship =>
127
+ missingRelationshipsMap.set(relationship.in, undefined)
128
+ )
129
+
130
+ let collectionStack = [target.contents]
131
+
132
+ const _getProperty = (object, keyPath) => keyPath.reduce((p, n) => p && p[n], object)
133
+ const _getYAMLProperty = (object, keyPath) => keyPath.reduce((p, n) => p && p.get(n), object)
134
+
135
+ const _validateConstraints = (node, dict, index) => {
136
+ existences.forEach(existence => {
137
+ const json = JSON.parse(String(node))
138
+ const constraintsFulfilled = existence.constraints.every(constraint =>
139
+ constraint.value === _getProperty(json, constraint.comparisonKeyPath)
140
+ )
141
+ if (constraintsFulfilled) dict.set(existence.ref, { json, node, index })
142
+ })
143
+ }
144
+
145
+ function _traverseYAMLNode(node, index, actions, templateNode) {
146
+ let shifted = false
147
+ switch (node.type) {
148
+ case 'MAP':
149
+ actions && actions.visitMap && actions.visitMap(node, index)
150
+ if (collectionStack[0] && collectionStack[0].type === 'SEQ') { collectionStack.unshift(node); shifted = true}
151
+ break;
152
+ case 'SEQ':
153
+ break;
154
+ case 'PAIR':
155
+ if (node.value && node.value.items) collectionStack.unshift(node.value)
156
+ break;
157
+ case 'PLAIN':
158
+ break;
159
+ }
160
+
161
+ if (node.items) {
162
+ if ((node.type === 'SEQ' || node.type === 'FLOW_SEQ') && templateNode && templateNode.items) {
163
+ actions.mergeCollection && actions.mergeCollection(node, templateNode)
164
+ node.type === 'SEQ' && _traverseYAMLCollection(node, actions, templateNode)
165
+ } else {
166
+ _traverseYAMLCollection(node, actions, templateNode)
167
+ }
168
+ } else if (node.value && node.value.items) {
169
+ _traverseYAMLNode(node.value, index, actions, templateNode && templateNode.value)
170
+ }
171
+
172
+ if (node.value && node.value.items || shifted) {
173
+ collectionStack.shift()
174
+ }
175
+ }
176
+ _traverseYAMLNode.bind(this)
177
+
178
+ const _traverseYAMLCollection = (collection, actions, templateCollection) => {
179
+ if (!collection) return
180
+
181
+ const keyIndexMap = new Map()
182
+ const templateIndexMap = new Map()
183
+
184
+ // Map collection items to their semantic counterpart(s)
185
+ collection.items.forEach((node, i) => {
186
+ if (node.key) keyIndexMap.set(node.key.value, i)
187
+ const existence = [...existenceMap.entries()].find(([,value]) => value && value.node === node)
188
+ if (existence) {
189
+ const [existenceKey] = existence
190
+ const templateExistence = existenceKey && templateExistenceMap.get(existenceKey)
191
+ const templateIndex = templateExistence && templateExistence.index
192
+ if (templateIndex !== undefined) templateIndexMap.set(templateIndex, i)
193
+ }
194
+ if (!templateCollection) _traverseYAMLNode(node, i, actions)
195
+ });
196
+
197
+ if (!templateCollection) return
198
+
199
+ templateCollection.items.forEach((templateNode, templateIndex) => {
200
+ if (templateNode.type === 'PAIR') {
201
+ const i = keyIndexMap.get(templateNode.key.value)
202
+ const [collection] = collectionStack
203
+ const targetNode = collection.items && collection.items[i]
204
+ if (targetNode) {
205
+ _traverseYAMLNode(targetNode, templateIndex, actions, templateNode)
206
+ } else if (actions.mergePair) {
207
+ actions.mergePair(collection, templateNode)
208
+ }
209
+ } else if (templateNode.type === 'MAP') {
210
+ const targetNode = collection.items[templateIndexMap.get(templateIndex)]
211
+ if (actions.mergeCollection) actions.mergeCollection(targetNode, templateNode)
212
+ if (targetNode) {
213
+ _traverseYAMLNode(targetNode, templateIndex, actions, templateNode);
214
+ } else if (!actions.mergeCollection) {
215
+ collection.add(YAML.createNode(templateNode))
216
+ //this.fsUtil.writeYAML(targetPath, target)
217
+ }
218
+ }
219
+ })
220
+ }
221
+ _traverseYAMLCollection.bind(this)
222
+
223
+ // 1. Validate the existences in the template
224
+ _traverseYAMLNode(source.contents, null, {
225
+ visitMap: (node, index) => {
226
+ _validateConstraints(node, templateExistenceMap, index)
227
+ }
228
+ })
229
+
230
+ // 2. Validate the existences in the project
231
+ _traverseYAMLNode(target.contents, null, {
232
+ visitMap: (node, index) => {
233
+ _validateConstraints(node, existenceMap, index)
234
+ }
235
+ })
236
+
237
+ // 3. Apply overwrites to already found existences
238
+ semantics.overwrites && semantics.overwrites.forEach(({ in: ref, keyPath, replacement }) => {
239
+ const existence = existenceMap.get(ref)
240
+ if (existence) {
241
+ _getYAMLProperty(existence.node, keyPath.slice(0, keyPath.length - 1))
242
+ .set(keyPath[keyPath.length - 1], replacement)
243
+ }
244
+ })
245
+
246
+ // 4. Create missing existences and pairs
247
+ _traverseYAMLNode(target.contents, null, {
248
+ mergePair: (collection, templateNode) => {
249
+ const childIsArray = false//templateNode.value.type !== 'MAP' && !!templateNode.value.items
250
+ if (collection.type === 'MAP' && !childIsArray) {
251
+ collection.add(YAML.createNode(templateNode))
252
+ //this.fsUtil.writeYAML(targetPath, target)
253
+ }
254
+ },
255
+ mergeCollection: (targetNode, templateNode) => {
256
+ if (templateNode.type === 'SEQ' || templateNode.type === 'FLOW_SEQ') {
257
+ const [,parent] = collectionStack
258
+ const missingExistences = semantics.existences
259
+ .filter(e => e)
260
+ .filter(existence =>
261
+ parent.get(existence.keyPath[0]) === targetNode
262
+ ).filter(existence =>
263
+ !existenceMap.get(existence.ref)
264
+ )
265
+ missingExistences.forEach(existence => {
266
+ const templateExistence = YAML.createNode(templateExistenceMap.get(existence.ref).node)
267
+ templateExistence.spaceBefore = existence.spaceBefore
268
+ targetNode.add(templateExistence)
269
+ existenceMap.set(existence.ref, templateExistence)
270
+ //this.fsUtil.writeYAML(targetPath, target)
271
+ })
272
+ }
273
+ },
274
+ }, source.contents)
275
+
276
+ // 5. Re-validate the existences in the project
277
+ _traverseYAMLNode(target.contents, null, {
278
+ visitMap: (node, index) => {
279
+ _validateConstraints(node, existenceMap, index)
280
+ }
281
+ })
282
+
283
+ // 6. Create missing relationships
284
+ _traverseYAMLNode(target.contents, null, {
285
+ mergeCollection: (targetNode, templateNode) => {
286
+ if (templateNode.type === 'SEQ') {
287
+ const targetJSON = YAML.parse(String(targetNode))
288
+ const relationship = semantics.relationships && semantics.relationships
289
+ .filter(r => r)
290
+ .find(relationship => {
291
+ const existingNode = _getYAMLProperty(existenceMap.get(relationship.in).node, relationship.inKeyPath)
292
+ return targetNode === existingNode
293
+ })
294
+ if (!relationship) return
295
+
296
+ const missingPairs = relationship.existences
297
+ .filter(existenceKey =>
298
+ !targetJSON.some(item =>
299
+ _getProperty(existenceMap.get(existenceKey).json, relationship.existenceKeyPath) === item[relationship.into]
300
+ )
301
+ )
302
+ .map(existenceKey => existenceMap.get(existenceKey).node.get(relationship.into))
303
+ missingPairs.forEach(pair => {
304
+ targetNode.add(YAML.createNode({ [relationship.into]: pair }))
305
+ //await this.fsUtil.writeYAML(targetPath, target)
306
+ })
307
+ }
308
+ }
309
+ }, source.contents)
310
+
311
+ typeof into === 'string' && await this.fsUtil.writeYAML(into, target)
312
+ return target
313
+ }
314
+
315
+ _isObject(item) {
316
+ return item && typeof item === 'object' && !Array.isArray(item)
317
+ }
318
+ };
319
+
@@ -0,0 +1,54 @@
1
+ /*
2
+ * Typings only for common use cases
3
+ */
4
+
5
+ interface AdditionalExecOptions {
6
+ acceptStatuses? : Array<Number> | '*',
7
+ appendEnv?: {[key: string]: string},
8
+ log?: object
9
+ }
10
+
11
+ interface SpawnReturns {
12
+ status : number,
13
+ stdout? : string,
14
+ stderr? : string
15
+ }
16
+
17
+ import { SpawnOptions, SpawnSyncOptions, SpawnSyncReturns } from "child_process";
18
+
19
+ type execTemplate<T> = (template: TemplateStringsArray, ...args : any[]) => T;
20
+
21
+ /**
22
+ * Execute commands with template strings asynchronously.
23
+ *
24
+ * @example
25
+ *
26
+ * await exec `ls -l .`
27
+ * await exec `ls -l ${'spaces are safe in expressions'}` => spawn('ls', [ '-l', 'spaces are safe in expressions' ])
28
+ * await exec `ls -l "dir ${'with spaces'}"
29
+ * await exec `ls -l ${['db', 'srv']}`
30
+ * const out = (await exec({stdio: ['inherit', 'pipe', 'inherit']}) `ls -l`).stdout
31
+ * const out = await exec({result: 'stdout}) `ls -l`
32
+ */
33
+ export function exec( template : TemplateStringsArray, ...args : any[] ) : Promise<SpawnReturns>
34
+ export function exec( options : { result: 'stdout' | 'stderr' } & SpawnOptions & AdditionalExecOptions) : execTemplate<Promise<string>>;
35
+ export function exec( options : SpawnOptions & AdditionalExecOptions) : execTemplate<Promise<SpawnReturns>>;
36
+
37
+ /**
38
+ * Execute commands with template strings synchronously.
39
+ *
40
+ * @example
41
+ *
42
+ * execSync `ls -l .`
43
+ * execSync `ls -l ${'spaces are safe in expressions'}` => spawn('ls', [ '-l', 'spaces are safe in expressions' ])
44
+ * execSync `ls -l "dir ${'with spaces'}"
45
+ * execSync `ls -l ${['db', 'srv']}`
46
+ * const out = (execSync({stdio: ['inherit', 'pipe', 'inherit']}) `ls -l`).stdout
47
+ * const out = execSync({result: 'stdout}) `ls -l`
48
+ */
49
+ export function execSync( template : TemplateStringsArray, ...args : any[] ) : SpawnSyncReturns<string>;
50
+ export function execSync( options : { result: 'stdout' | 'stderr' } & SpawnSyncOptions & AdditionalExecOptions) : execTemplate<string>;
51
+ export function execSync( options : { encoding : 'buffer' | null } & SpawnOptions & AdditionalExecOptions) : execTemplate<SpawnSyncReturns<Buffer>>;
52
+ export function execSync( options : SpawnOptions & AdditionalExecOptions) : execTemplate<SpawnSyncReturns<string>>;
53
+
54
+ export { execSync as $ };
@@ -0,0 +1,210 @@
1
+ const { spawn, spawnSync } = require('child_process');
2
+
3
+ const cds = require('@sap/cds/lib');
4
+ const log = cds.log('exec');
5
+
6
+ function* commandIterator(strs, ...args) {
7
+ for (let i=0; i<strs.length; i++) {
8
+ const str = strs[i];
9
+ const regex = /((\s+)|(["'])|$)/g;
10
+ let match;
11
+ let lastIndex = 0;
12
+ do {
13
+ match = regex.exec(str);
14
+ if (match.index > lastIndex) {
15
+ yield { type: 'WORD', value: str.substr(lastIndex, match.index - lastIndex) };
16
+ }
17
+ if (match[2]) {
18
+ yield { type: 'SPACE', value: match[2] };
19
+ }
20
+ if (match[3]) {
21
+ yield { type: 'QUOTE', value: match[3] }
22
+ }
23
+ lastIndex = match.index + match[0].length;
24
+ } while (lastIndex < str.length);
25
+
26
+ if (args[i]) {
27
+ if (Array.isArray(args[i])) {
28
+ let sep = false;
29
+ for (const value of args[i]) {
30
+ if (sep) yield { type: 'SPACE', value: ' '};
31
+ yield { type: 'WORD', value };
32
+ sep = true;
33
+ }
34
+ } else {
35
+ yield { type: 'WORD', value: args[i] };
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ function processTemplate(strs, args) {
42
+ let quote;
43
+ const result = [];
44
+ let index = 0;
45
+
46
+ const append = str => result[index] = (result[index] || '') + str;
47
+ const push = () => { if (index < result.length) index++; };
48
+
49
+ for (const token of commandIterator(strs, args)) {
50
+ switch (token.type) {
51
+ case 'WORD':
52
+ append(token.value);
53
+ break;
54
+ case 'SPACE':
55
+ if (quote) {
56
+ append(token.value);
57
+ } else {
58
+ push();
59
+ }
60
+ break;
61
+ case 'QUOTE':
62
+ if (quote === token.value) {
63
+ quote = undefined;
64
+ } else if (quote) {
65
+ append(token.value);
66
+ } else {
67
+ quote = token.value;
68
+ }
69
+ break;
70
+ }
71
+ }
72
+
73
+ return result;
74
+ }
75
+
76
+ function fetchOptions(cmd) {
77
+ const { options, args } = cmd;
78
+ if (typeof args[0] === 'object' && !Array.isArray(args[0])) {
79
+ const newOptions = args[0];
80
+ cmd.options = {...options, ...newOptions};
81
+ cmd.args = args.slice(1);
82
+ return true;
83
+ } else {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ function exec_func(...args) {
89
+ const cmd = { options: {
90
+ log,
91
+ acceptStatuses: [ 0 ],
92
+ stdio: 'inherit',
93
+ shell: false,
94
+ encoding: 'utf-8'
95
+ }, args };
96
+
97
+ let hasOptions = false;
98
+ while (fetchOptions(cmd)) hasOptions = true;
99
+ if (hasOptions && cmd.args.length === 0) {
100
+ return (...args) => exec_func(cmd.options, ...args);
101
+ }
102
+
103
+ if (Array.isArray(cmd.args[0])) cmd.args = processTemplate(...cmd.args);
104
+ return cmd.options.executer(cmd.options, ...cmd.args);
105
+ }
106
+
107
+ function getExecOptions(options) {
108
+ const execOptions = {...options};
109
+ [ 'acceptStatuses', 'appendEnv', 'exec', 'log', 'result' ].forEach( name => delete execOptions[name] );
110
+
111
+ const { appendEnv, result } = options;
112
+ if (appendEnv) {
113
+ execOptions.env = {...(options.env || process.env),...appendEnv};
114
+ }
115
+
116
+ switch (result) {
117
+ case 'stdout':
118
+ execOptions.stdio = [ 'inherit', 'pipe', 'inherit' ];
119
+ break;
120
+ case 'stderr':
121
+ execOptions.stdio = [ 'inherit', 'inherit', 'pipe' ];
122
+ break;
123
+ case undefined:
124
+ break;
125
+
126
+ default:
127
+ throw new Error `Unexpected "result" option: "${result}"`;
128
+ }
129
+
130
+ return execOptions;
131
+ }
132
+
133
+ function toString(a) {
134
+ return typeof a === 'string' ? a : Buffer.from(a).toString('utf-8');
135
+ }
136
+
137
+ function processResult(result, options) {
138
+ switch (options.result) {
139
+ case 'stdout':
140
+ return toString(result.stdout);
141
+ case 'stderr':
142
+ return toString(result.stderr);
143
+ case undefined:
144
+ return result;
145
+
146
+ default:
147
+ throw new Error(`Unexpected "result" option: "${result}"`);
148
+ }
149
+ }
150
+
151
+
152
+ function executeSync(options = {}, command, ...args) {
153
+ const { acceptStatuses } = options;
154
+ const execOptions = getExecOptions(options);
155
+
156
+ log.debug(`exec: ${command} ${args.map( v => v.match(/\s/) ? `"${v}"` : v ).join(' ')}`);
157
+ const result = spawnSync(command, args, execOptions);
158
+
159
+ if (acceptStatuses !== '*' && acceptStatuses.indexOf(result.status) === -1) throw new Error(`Command returned with status code ${result.status}`);
160
+ return processResult(result, options);
161
+ }
162
+
163
+ async function executeAsync(options = {}, command, ...args) {
164
+ const { acceptStatuses } = options;
165
+ const execOptions = getExecOptions(options);
166
+
167
+ // ctx.log(`exec: ${command} ${args.map( v => v.match(/\s/) ? `"${v}"` : v ).join(' ')}`);
168
+
169
+
170
+ const result = await new Promise( (resolve, reject) => {
171
+ const child = spawn(command, args, execOptions);
172
+ const stdoutChunks = [];
173
+ const stderrChunks = [];
174
+
175
+ if (child.stdout) {
176
+ child.stdout.on('data', data => {
177
+ stdoutChunks.push(Buffer.from(data));
178
+ });
179
+ }
180
+
181
+ if (child.stderr) {
182
+ child.stderr.on('data', data => {
183
+ stderrChunks.push(Buffer.from(data));
184
+ });
185
+ }
186
+
187
+ const exit = status => {
188
+ if (acceptStatuses !== '*' && acceptStatuses.indexOf(status) === -1) reject(new Error(`Command returned with status code ${status}`))
189
+ else resolve({
190
+ status,
191
+ stdout: child.stdout ? Buffer.concat(stdoutChunks).toString("utf-8") : undefined,
192
+ stderr: child.stderr ? Buffer.concat(stderrChunks).toString("utf-8") : undefined
193
+ })
194
+ };
195
+
196
+ child.on('close', exit);
197
+ child.on('exit', exit);
198
+ });
199
+
200
+ return processResult(result, options);
201
+ }
202
+
203
+ const exec = exec_func({ executer: executeAsync });
204
+ const execSync = exec_func({ executer: executeSync });
205
+
206
+ module.exports = {
207
+ exec,
208
+ execSync,
209
+ $ : execSync
210
+ }
@@ -14,46 +14,58 @@ module.exports = class LiveReload {
14
14
  app.use('/livereload.js', express.static(require.resolve('livereload-js')))
15
15
 
16
16
  return new Promise ((resolve, reject) => {
17
- // start server
18
- const server = app.listen (port,'localhost')
19
- server.on('error', reject)
20
- server.on('listening', async () => {
17
+ let server
18
+ const listening = async () => {
21
19
  const {address,port} = server.address()
20
+ startWs(server)
22
21
  this.url = await livereloadURL(address, port)
23
22
  debug && debug(`[cds] - live reload available at ${this.url}`)
24
23
  resolve(this.url)
24
+ }
25
+ // start server
26
+ server = app.listen (port,'localhost')
27
+ server.once('listening', listening)
28
+ server.on('error', e => {
29
+ // try once more w/ ephemeral port 0
30
+ if (e.code === 'EADDRINUSE') {
31
+ server = app.listen (0,'localhost')
32
+ server.once('listening', listening)
33
+ server.on('error', reject)
34
+ }
35
+ else reject(e)
25
36
  })
26
37
 
27
38
  // start websocket
28
- this.wsServer = new ws.Server({ server })
29
- this.wsServer.on('connection', (socket) => {
30
- debug && debug('Client connected')
31
- socket.on('message', (msg) => {
32
- debug && debug('Client message', msg)
33
- const request = JSON.parse(msg)
39
+ const startWs = (server) => {
40
+ this.wsServer = new ws.Server({ server })
41
+ this.wsServer.on('connection', (socket) => {
42
+ debug && debug('Client connected')
43
+ socket.on('message', (msg) => {
44
+ debug && debug('Client message', msg)
45
+ const request = JSON.parse(msg)
34
46
 
35
- if (request.command === 'hello') {
36
- debug && debug('Client handshake')
37
- const data = JSON.stringify({
38
- command: 'hello',
39
- protocols: [
40
- 'http://livereload.com/protocols/official-7',
41
- 'http://livereload.com/protocols/official-8',
42
- 'http://livereload.com/protocols/official-9',
43
- 'http://livereload.com/protocols/2.x-origin-version-negotiation',
44
- 'http://livereload.com/protocols/2.x-remote-control',
45
- ],
46
- serverName: 'cds-watch',
47
- })
48
- socket.send(data)
49
- } else if (request.command === 'info') {
50
- debug && debug('Server received client data. Not sending response.')
51
- }
47
+ if (request.command === 'hello') {
48
+ debug && debug('Client handshake')
49
+ const data = JSON.stringify({
50
+ command: 'hello',
51
+ protocols: [
52
+ 'http://livereload.com/protocols/official-7',
53
+ 'http://livereload.com/protocols/official-8',
54
+ 'http://livereload.com/protocols/official-9',
55
+ 'http://livereload.com/protocols/2.x-origin-version-negotiation',
56
+ 'http://livereload.com/protocols/2.x-remote-control',
57
+ ],
58
+ serverName: 'cds-watch',
59
+ })
60
+ socket.send(data)
61
+ } else if (request.command === 'info') {
62
+ debug && debug('Server received client data. Not sending response.')
63
+ }
64
+ })
52
65
  })
53
- })
54
- this.wsServer.on('close', () => { debug && debug('Socket closed') })
55
- this.wsServer.on('error', (e) => debug && debug(e))
56
-
66
+ this.wsServer.on('close', () => { debug && debug('Socket closed') })
67
+ this.wsServer.on('error', (e) => debug && debug(e))
68
+ }
57
69
  })
58
70
  }
59
71
 
package/lib/watch/open.js CHANGED
@@ -19,7 +19,7 @@ function _open(args) {
19
19
  // http://stackoverflow.com/q/1480971/3191, but see below for Windows.
20
20
  let platform = process.platform
21
21
 
22
- // Attempt to detect Windows Subystem for Linux (WSL). WSL itself as Linux (which works in most cases), but in
22
+ // Attempt to detect Windows Subsystem for Linux (WSL). WSL itself as Linux (which works in most cases), but in
23
23
  // this specific case we need to treat it as actually being Windows. The 'Windows-way' of opening things through
24
24
  // cmd.exe works just fine here, whereas using xdg-open does not, since there is no X Windows in WSL.
25
25
  if (platform === 'linux' && os.release().indexOf('Microsoft') !== -1) {