@sap/cds-dk 4.7.3 → 4.8.2
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.
- package/CHANGELOG.md +42 -1
- package/bin/activate.js +5 -1
- package/bin/add.js +2 -2
- package/lib/bind/bindingUtil.js +6 -1
- package/lib/client/activate.js +8 -3
- package/lib/client/base_command.js +5 -1
- package/lib/client/helper/cf_helper.js +5 -2
- package/lib/client/params.js +16 -2
- package/lib/client/settings_manager.js +15 -8
- package/lib/compile/openapi/csdl2openapi.js +6 -8
- package/lib/compile/to-xsuaa.js +0 -14
- package/lib/import/utils/MetadataConverter.js +6 -4
- package/lib/import/v2parser.js +59 -35
- package/lib/import/v4parser.js +72 -47
- package/lib/init/constants.js +10 -4
- package/lib/init/index.js +13 -7
- package/lib/init/template/_merging/existences.js +161 -0
- package/lib/init/template/approuter/files/default-env.json +9 -0
- package/lib/init/template/approuter/files/mta.yaml.hbs +48 -0
- package/lib/init/template/approuter/files/package.json +9 -0
- package/lib/init/template/approuter/files/xs-app.json +8 -0
- package/lib/init/template/approuter/index.js +66 -0
- package/lib/init/template/auditlog/files/dependencies.package.json +5 -0
- package/lib/init/template/auditlog/files/mta.yaml.hbs +13 -0
- package/lib/init/template/auditlog/index.js +38 -0
- package/lib/init/template/cf-manifest/index.js +1 -1
- package/lib/init/template/hana/files/mta.yaml.hbs +42 -0
- package/lib/init/template/hana/files/nodejs/dependencies.package.json +5 -0
- package/lib/init/template/hana/index.js +53 -24
- package/lib/init/template/java/index.js +1 -5
- package/lib/init/template/mta/files/mta.yaml.hbs +24 -220
- package/lib/init/template/mta/index.js +25 -17
- package/lib/init/template/mtx/files/cds.package.json +24 -0
- package/lib/init/template/mtx/files/dependencies.package.json +5 -0
- package/lib/init/template/mtx/files/mta.yaml.hbs +74 -0
- package/lib/init/template/mtx/files/xs-security.json.hbs +23 -9
- package/lib/init/template/mtx/index.js +126 -29
- package/lib/init/template/templateBase.js +14 -0
- package/lib/init/template/xsuaa/files/cds.package.json +9 -0
- package/lib/init/template/xsuaa/files/dependencies.package.json +6 -0
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +17 -0
- package/lib/init/template/xsuaa/index.js +75 -0
- package/lib/init/util/fsUtil.js +17 -3
- package/lib/init/util/logger.js +3 -1
- package/lib/init/{template/mta → util}/projectReader.js +26 -11
- package/lib/init/util/templateUtil.js +267 -3
- package/lib/util/exec.d.ts +54 -0
- package/lib/util/exec.js +210 -0
- package/lib/watch/livereload-server.js +43 -31
- package/lib/watch/open.js +1 -1
- package/npm-shrinkwrap.json +3762 -15624
- package/package.json +7 -8
- package/readme.md +1 -1
- package/lib/import/internalDoc/edm2cdsTypeMapping.md +0 -38
- package/lib/init/template/mtx/package/cds.package.json +0 -29
- 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
|
-
|
|
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 $ };
|
package/lib/util/exec.js
ADDED
|
@@ -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
|
-
|
|
18
|
-
const
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
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) {
|