@cap-js/cds-typer 0.2.5-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/file.js ADDED
@@ -0,0 +1,497 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs').promises
4
+ const { readFileSync } = require('fs')
5
+ const path = require('path')
6
+
7
+ const AUTO_GEN_NOTE = "// This is an automatically generated file. Please do not change its contents manually!"
8
+
9
+ /** @typedef {Object<string, Buffer>} Namespace */
10
+
11
+ class File {
12
+ /**
13
+ * Creates one string from the buffers representing the type definitions.
14
+ * @returns {string} complete file contents.
15
+ */
16
+ toTypeDefs() { return '' }
17
+
18
+ /**
19
+ * Concats the classnames to an export dictionary
20
+ * to create the accompanying JS file for the typings.
21
+ * @returns {string} a string containing the module.exports for the JS file.
22
+ */
23
+ toJSExports() { return '' }
24
+ }
25
+
26
+ /**
27
+ * Library files that contain predefined types.
28
+ * For example, the cap.hana types are included in a separate predefined library file
29
+ * which is only included if the model that is being compiled references any of these types.
30
+ * A library is uniquely identified by the namespace it represents. That namespace is directly
31
+ * derived from the file's name. i.e. path/to/file/cap.hana.ts will be considered to hold
32
+ * definitions describing the namespace "cap.hana".
33
+ * These files are supposed to contain fully usable types, not CSN or a CDS file, as they are just
34
+ * being copied verbatim when they are being used.
35
+ */
36
+ class Library extends File {
37
+ toTypeDefs() {
38
+ return this.contents
39
+ }
40
+
41
+ constructor(file) {
42
+ super()
43
+ this.contents = readFileSync(file, 'utf-8')
44
+ /**
45
+ * The path to the file where the lib definitions are stored (some/path/name.space.ts)
46
+ * @type {string}
47
+ * @private
48
+ */
49
+ this.file = file
50
+
51
+ /**
52
+ * List of entity names (plain, without namespace)
53
+ * @type {string[]}
54
+ */
55
+ this.entities = Array.from(this.contents.matchAll(/export class (\w+)/g), ([,m]) => m)
56
+
57
+ /**
58
+ * Namespace (a.b.c.d)
59
+ * @type {string}
60
+ */
61
+ this.namespace = path.basename(file, '.ts')
62
+
63
+ /**
64
+ * Whether this library was referenced at least once
65
+ * @type {boolean}
66
+ */
67
+ this.referenced = false
68
+
69
+ /**
70
+ * The Path for this library file, which is constructed from its namespace.
71
+ * @type {Path}
72
+ */
73
+ this.path = new Path(this.namespace.split('.'))
74
+ }
75
+
76
+ /**
77
+ * Whether this library offers an entity of a given type (fully qualified).
78
+ * @param {string} entity the entity's name, e.g. cap.hana.TINYINT
79
+ * @returns {boolean} true, iff the namespace inferred from the passed string matches that of this library
80
+ * and this library contains a class of that name. i.e.:
81
+ * ```js
82
+ * new Library('cap.hana.ts').offers('cap.hana.TINYINT') // -> true`
83
+ * ```
84
+ */
85
+ offers(entity) {
86
+ return this.entities.some(e => `${this.namespace}.${e}` === entity)
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Source file containing several buffers.
92
+ */
93
+ class SourceFile extends File {
94
+ constructor(path) {
95
+ super()
96
+ /** @type {Path} */
97
+ this.path = new Path(path.split('.'))
98
+ /** @type {Object} */
99
+ this.imports = {}
100
+ /** @type {Buffer} */
101
+ this.preamble = new Buffer()
102
+ /** @type {Buffer} */
103
+ this.types = new Buffer()
104
+ /** @type {Buffer} */
105
+ this.enums = new Buffer()
106
+ /** @type {Buffer} */
107
+ this.classes = new Buffer()
108
+ /** @type {{ buffer: Buffer, names: string[]} */
109
+ this.actions = { buffer: new Buffer(), names: [] }
110
+ /** @type {Buffer} */
111
+ this.aspects = new Buffer()
112
+ /** @type {Namespace} */
113
+ this.namespaces = {}
114
+ /** @type {Object<string, any>} */
115
+ this.classNames = {} // for .js file
116
+ /** @type {Object<string, any>} */
117
+ this.typeNames = {}
118
+ /** @type {[string, string, string][]} */
119
+ this.inflections = []
120
+ }
121
+
122
+ static stringifyLambda(name, parameters = [], returns = 'any') {
123
+ return `${name}: (${parameters.map(([n, t]) => `${n}: ${t}`).join(', ')}) => ${returns}`
124
+ }
125
+
126
+ /**
127
+ * Adds a pair of singular and plural inflection.
128
+ * These are later used to generate the singular -> plural
129
+ * aliases in the index.js file.
130
+ * @param {string} singular singular type without namespace.
131
+ * @param {string} plural plural type without namespace
132
+ * @param {string} original original entity name without namespace.
133
+ * In many cases this will be the same as plural.
134
+ */
135
+ addInflection(singular, plural, original) {
136
+ this.inflections.push([singular, plural, original])
137
+ }
138
+
139
+ /**
140
+ * Adds an action definition in form of a arrow function to the file.
141
+ * @param {string} name name of the action
142
+ * @param {{relative: string | undefined, local: boolean, posix: boolean}} params list of parameters, passed as [name, type] pairs
143
+ * @param returns the return type of the action
144
+ */
145
+ addAction(name, params, returns) {
146
+ //const ps = params.map(([n, t]) => `${n}: ${t}`).join(', ')
147
+ this.actions.buffer.add("// action")
148
+ //this.actions.buffer.add(`export declare const ${name}: ( args: { ${ps} }) => ${returns};`)
149
+ this.actions.buffer.add(`export declare const ${SourceFile.stringifyLambda(name, params, returns)};`)
150
+ this.actions.names.push(name)
151
+ }
152
+
153
+ /**
154
+ * Retrieves or creates and retrieves a sub namespace
155
+ * with a given name.
156
+ * @param {string} name of the sub namespace.
157
+ * @returns {Namespace} the sub namespace.
158
+ */
159
+ getSubNamespace(name) {
160
+ if (!(name in this.namespaces)) {
161
+ const buffer = new Buffer()
162
+ buffer.closed = false
163
+ buffer.add(`export namespace ${name} {`)
164
+ buffer.indent()
165
+ this.namespaces[name] = buffer
166
+ }
167
+ const buffer = this.namespaces[name]
168
+ if (buffer.closed) {
169
+ throw new Error(`Tried to add content to namespace buffer '${name}' that was already closed.`)
170
+ }
171
+ return this.namespaces[name]
172
+ }
173
+
174
+ /**
175
+ * Adds an enum to this file.
176
+ * @param {string} fq fully qualified name of the enum
177
+ * @param {string} name local name of the enum
178
+ * @param {[string, string][]} kvs list of key-value pairs
179
+ */
180
+ addEnum(fq, name, kvs) {
181
+ this.enums.add(`export enum ${name} {`)
182
+ this.enums.indent()
183
+ for (const [k, v] of kvs) {
184
+ this.enums.add(`${k} = ${v},`)
185
+ }
186
+ this.enums.outdent()
187
+ this.enums.add('}')
188
+ }
189
+
190
+ /**
191
+ * Adds a class to this file.
192
+ * This differs from writing to the classes buffer,
193
+ * as it is just a cache to collect all classes that
194
+ * are supposed to be present in this file.
195
+ * @param {string} clean cleaned name of the class
196
+ * @param {string} fq fully qualified name, including the namespace
197
+ */
198
+ addClass(clean, fq) {
199
+ this.classNames[clean] = fq
200
+ }
201
+
202
+ /**
203
+ * Adds an import if it does not exist yet.
204
+ * @param {Path} imp qualifier for the namespace to import.
205
+ */
206
+ addImport(imp) {
207
+ const dir = imp.asDirectory({relative: this.path.asDirectory()})
208
+ if (!(dir in this.imports)) {
209
+ this.imports[dir] = imp
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Adds an arbitrary piece of code that is added
215
+ * right after the imports.
216
+ * @param {string} code the preamble code.
217
+ */
218
+ addPreamble(code) {
219
+ this.preamble.add(code)
220
+ }
221
+
222
+ /**
223
+ * Adds a type alias to this file.
224
+ * @param {string} fq fully qualified name of the enum
225
+ * @param {string} name local name of the enum
226
+ * @param {string} rhs the right hand side of the assignment
227
+ */
228
+ addType(fq, clean, rhs) {
229
+ this.typeNames[clean] = fq
230
+ this.types.add(`export type ${clean} = ${rhs};`)
231
+ }
232
+
233
+ /**
234
+ * Writes all imports to a buffer, relative to the current file.
235
+ * Creates a new buffer on each call, as concatenating import strings directly
236
+ * upon discovering them would complicate filtering out duplicate entries.
237
+ * @returns {Buffer} all imports written to a buffer.
238
+ */
239
+ getImports() {
240
+ const buffer = new Buffer()
241
+ for (const imp of Object.values(this.imports)) {
242
+ if (!imp.isCwd(this.path.asDirectory())) {
243
+ buffer.add(`import * as ${imp.asIdentifier()} from '${imp.asDirectory({relative: this.path.asDirectory()})}';`)
244
+ }
245
+ }
246
+ return buffer
247
+ }
248
+
249
+ toTypeDefs() {
250
+ const namespaces = new Buffer()
251
+ for (const ns of Object.values(this.namespaces)) {
252
+ ns.outdent()
253
+ ns.add('}')
254
+ ns.closed = true
255
+ namespaces.add(ns.join())
256
+ }
257
+ return [
258
+ AUTO_GEN_NOTE,
259
+ this.getImports().join(),
260
+ this.preamble.join(),
261
+ this.types.join(),
262
+ this.enums.join(),
263
+ namespaces.join(),
264
+ this.aspects.join(), // needs to be before classes
265
+ this.classes.join(),
266
+ this.actions.buffer.join(),
267
+ ].filter(Boolean).join('\n')
268
+ }
269
+
270
+ toJSExports() {
271
+ return [AUTO_GEN_NOTE, "const cds = require('@sap/cds')", `const csn = cds.entities('${this.path.asNamespace()}')`] // boilerplate
272
+ .concat(
273
+ this.inflections
274
+ // sorting the entries based on the number of dots in their singular.
275
+ // that makes sure we have defined all parent namespaces before adding subclasses to them e.g.:
276
+ // "module.exports.Books" is defined before "module.exports.Books.text"
277
+ .sort(([a], [b]) => a.split('.').length - b.split('.').length)
278
+ // by using a temporary Set we make sure to catch cases where
279
+ // (1) plural is the same as original (default case) and
280
+ // (2) plural differs from original, i.e. when a @plural annotation is present
281
+ // or when plural4 produced weird inflection.
282
+ .flatMap(([singular, plural, original]) => Array.from(new Set([
283
+ `module.exports.${singular} = csn.${original}`,
284
+ `module.exports.${plural} = csn.${original}`,
285
+ `module.exports.${original} = csn.${original}`
286
+ ])))
287
+ ) // singular -> plural aliases
288
+ .concat(['// actions'])
289
+ .concat(this.actions.names.map(name => `module.exports.${name} = '${name}'`))
290
+ .join('\n') + '\n'
291
+ }
292
+ }
293
+
294
+ /**
295
+ * String buffer to conveniently append strings to.
296
+ */
297
+ class Buffer {
298
+
299
+ /**
300
+ * @param {string} indentation
301
+ */
302
+ constructor(indentation = ' ') {
303
+ /**
304
+ * @type {string[]}
305
+ */
306
+ this.parts = []
307
+ /**
308
+ * @type {string}
309
+ */
310
+ this.indentation = indentation
311
+ /**
312
+ * @type {string}
313
+ */
314
+ this.currentIndent = ''
315
+ }
316
+
317
+ /**
318
+ * Indents by the predefined spacing.
319
+ */
320
+ indent() {
321
+ this.currentIndent += this.indentation
322
+ }
323
+
324
+ /**
325
+ * Removes one level of indentation.
326
+ */
327
+ outdent() {
328
+ if (this.currentIndent.length === 0) {
329
+ throw new Error('Can not outdent buffer further. Probably mismatched indent.')
330
+ }
331
+ this.currentIndent = this.currentIndent.slice(0, -this.indentation.length)
332
+ }
333
+
334
+ /**
335
+ * Concats all elements in the buffer into a single string.
336
+ * @param {string} glue string to intersperse all buffer contents with
337
+ * @returns {string} string spilled buffer contents.
338
+ */
339
+ join(glue = '\n') {
340
+ return this.parts.join(glue)
341
+ }
342
+
343
+ /**
344
+ * Clears the buffer.
345
+ */
346
+ clear() {
347
+ this.parts = []
348
+ }
349
+
350
+ /**
351
+ * Adds an element to the buffer with the current indentation level.
352
+ * @param {string} part
353
+ */
354
+ add(part) {
355
+ this.parts.push(this.currentIndent + part)
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Convenience class to handle path qualifiers.
361
+ */
362
+ class Path {
363
+
364
+ /**
365
+ * @param {string[]} parts parts of the path. 'a.b.c' -> ['a', 'b', 'c']
366
+ * @param kind FIXME: currently unused
367
+ */
368
+ constructor(parts, kind) {
369
+ this.parts = parts
370
+ this.kind = kind
371
+ }
372
+
373
+ /**
374
+ * @returns {Path} the path to the parent directory. 'a.b.c'.getParent() -> 'a.b'
375
+ */
376
+ getParent() {
377
+ return new Path(this.parts.slice(0, -1))
378
+ }
379
+
380
+ /**
381
+ * Transfoms the Path into a directory path.
382
+ * @param {string?} params.relative if defined, the path is constructed relative to this directory
383
+ * @param {boolean} params.local if set to true, './' is prefixed to the directory
384
+ * @param {boolean} params.posix if set to true, all slashes will be forward slashes on every OS. Useful for require/ import
385
+ * @returns {string} directory 'a.b.c'.asDirectory() -> 'a/b/c' (or a\b\c when on Windows without passing posix = true)
386
+ */
387
+ asDirectory(params = {}) {
388
+ const { relative, local, posix } = {relative: undefined, local: true, posix: true, ...params}
389
+ const sep = posix ? path.posix.sep : path.sep
390
+ const prefix = local ? `.${sep}` : ''
391
+ const absolute = path.join(...this.parts)
392
+ let p = relative ? path.relative(relative, absolute) : absolute
393
+ if (posix) {
394
+ // NOTE: this could fail for absolute paths (D:\\...) or network drives on windows
395
+ p = p.split(path.sep).join(path.posix.sep)
396
+ }
397
+ // path.join removes leading ./, so we have to concat manually here
398
+ return prefix + p
399
+ }
400
+
401
+ /**
402
+ * Transforms the Path into a namespace qualifier.
403
+ * @returns {string} namespace qualifier 'a.b.c'.asNamespace() -> 'a.b.c'
404
+ */
405
+ asNamespace() {
406
+ return this.parts.join('.')
407
+ }
408
+
409
+ /**
410
+ * Transforms the Path into an identifier that can be used as variable name.
411
+ * @returns {string} identifier 'a.b.c'.asIdentifier() -> '_a_b_c', ''.asIdentifier() -> '_'
412
+ */
413
+ asIdentifier() {
414
+ return `_${this.parts.join('_')}`
415
+ }
416
+
417
+ /**
418
+ * @returns {boolean} true, iff the Path refers to the current working directory, aka './'
419
+ */
420
+ isCwd(relative = undefined) {
421
+ return (!relative && this.parts.length === 0) || (!!relative && this.asDirectory({relative}) === './')
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Base definitions used throughout the typing process,
427
+ * such as Associations and Compositions.
428
+ * @type {SourceFile}
429
+ */
430
+ const baseDefinitions = new SourceFile('_')
431
+ baseDefinitions.addPreamble(`
432
+ export namespace Association {
433
+ export type to <T> = T;
434
+ export namespace to {
435
+ export type many <T extends readonly any[]> = T;
436
+ }
437
+ }
438
+
439
+ export namespace Composition {
440
+ export type of <T> = T;
441
+ export namespace of {
442
+ export type many <T extends readonly any[]> = T;
443
+ }
444
+ }
445
+
446
+ export class Entity {
447
+ static data<T extends Entity> (this:T, input:Object) : T {
448
+ return {} as T // mock
449
+ }
450
+ }
451
+
452
+ export type EntitySet<T> = T[] & {
453
+ data (input:object[]) : T[]
454
+ data (input:object) : T
455
+ };
456
+
457
+ export type DeepRequired<T> = {
458
+ [K in keyof T]: DeepRequired<T[K]>
459
+ } & Required<T>;
460
+ `)
461
+
462
+ /**
463
+ * Writes the files to disk. For each source, a index.d.ts holding the type definitions
464
+ * and a index.js holding implementation stubs is generated at the appropriate directory.
465
+ * Missing directories are created automatically and asynchronously.
466
+ * @param {string} root root directory to prefix all directories with
467
+ * @param {File[]} sources source files to write to disk
468
+ * @returns {Promise<string[]>} Promise that resolves to a list of all directory paths pointing to generated files.
469
+ */
470
+ const writeout = async (root, sources) =>
471
+ Promise.all(
472
+ sources.map(async (source) => {
473
+ const dir = path.join(root, source.path.asDirectory({local: false, posix: false}))
474
+ try {
475
+ await fs.mkdir(dir, { recursive: true })
476
+ await Promise.all([
477
+ fs.writeFile(path.join(dir, 'index.ts'), source.toTypeDefs()),
478
+ fs.writeFile(path.join(dir, 'index.js'), source.toJSExports()),
479
+ ])
480
+
481
+ } catch (err) {
482
+ // eslint-disable-next-line no-console
483
+ console.error(`Could not create parent directory ${dir}: ${err}.`)
484
+ }
485
+ return dir
486
+ })
487
+ )
488
+
489
+ module.exports = {
490
+ Library,
491
+ Buffer,
492
+ File,
493
+ SourceFile,
494
+ Path,
495
+ writeout,
496
+ baseDefinitions,
497
+ }
@@ -0,0 +1,50 @@
1
+ export enum Levels {
2
+ TRACE = 1,
3
+ DEBUG = 2,
4
+ INFO = 3,
5
+ WARNING = 4,
6
+ ERROR = 8,
7
+ CRITICAL = 16,
8
+ NONE = 32
9
+ }
10
+
11
+ export class Logger {
12
+ private mask: number;
13
+
14
+ public constructor();
15
+
16
+ /**
17
+ * Add all log levels starting at level.
18
+ * @param level level to start from.
19
+ */
20
+ public addFrom(level: number): void;
21
+
22
+ /**
23
+ * Adds a log level to react to.
24
+ * @param level the level to react to.
25
+ */
26
+ public add(level: number): void;
27
+
28
+ /**
29
+ * Ignores a log level.
30
+ * @param level the level to ignore.
31
+ */
32
+ public ignore(level: number): void;
33
+
34
+ /**
35
+ * Attempts to log a message.
36
+ * Only iff levelName is a valid log level
37
+ * and the corresponding number if part of mask,
38
+ * the message gets logged.
39
+ * @param levelName name of the log level.
40
+ * @param message message to log.
41
+ */
42
+ private _log(levelName: Levels, message: string);
43
+
44
+ public trace(message: string);
45
+ public debug(message: string);
46
+ public info(message: string);
47
+ public warning(message: string);
48
+ public error(message: string);
49
+ public critical(message: string);
50
+ }
package/lib/logging.js ADDED
@@ -0,0 +1,73 @@
1
+ /** @enum {number} */
2
+ const Levels = {
3
+ TRACE: 1,
4
+ DEBUG: 2,
5
+ INFO: 3,
6
+ WARNING: 4,
7
+ ERROR: 8,
8
+ CRITICAL: 16,
9
+ NONE: 32,
10
+ }
11
+
12
+ class Logger {
13
+ constructor() {
14
+ this.mask = 0
15
+ const lvls = Object.keys(Levels)
16
+ for (let i = 0; i < lvls.length - 1; i++) {
17
+ // -1 to ignore NONE
18
+ const level = lvls[i]
19
+ this[level.toLowerCase()] = function (message) {
20
+ this._log(level, message)
21
+ }.bind(this)
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Add all log levels starting at level.
27
+ * @param {number} level level to start from.
28
+ */
29
+ addFrom(baseLevel) {
30
+ const vals = Object.values(Levels)
31
+ const highest = vals[vals.length - 1]
32
+ for (let l = Math.log2(baseLevel); Math.pow(2, l) <= highest; l++) {
33
+ this.add(Math.pow(2, l))
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Adds a log level to react to.
39
+ * @param {number} level the level to react to.
40
+ */
41
+ add(level) {
42
+ this.mask = this.mask | level
43
+ }
44
+
45
+ /**
46
+ * Ignores a log level.
47
+ * @param {number} level the level to ignore.
48
+ */
49
+ ignore(level) {
50
+ this.mask = this.mask ^ level
51
+ }
52
+
53
+ /**
54
+ * Attempts to log a message.
55
+ * Only iff levelName is a valid log level
56
+ * and the corresponding number if part of mask,
57
+ * the message gets logged.
58
+ * @param {Levels} levelName name of the log level.
59
+ * @param {string} message message to log.
60
+ */
61
+ _log(levelName, message) {
62
+ const level = Levels[levelName]
63
+ if (level && (this.mask & level) === level) {
64
+ // eslint-disable-next-line no-console
65
+ console.log(`[${levelName}]`, message)
66
+ }
67
+ }
68
+ }
69
+
70
+ module.exports = {
71
+ Levels,
72
+ Logger,
73
+ }
package/lib/util.d.ts ADDED
@@ -0,0 +1,87 @@
1
+ interface Annotations {
2
+ name?: string,
3
+ '@singular'?: string,
4
+ '@plural'?: string
5
+ }
6
+
7
+ interface CommandlineFlag {
8
+ desc: string,
9
+ default?: any
10
+ }
11
+
12
+ interface ParsedFlags {
13
+ positional: string[],
14
+ named: {[key: string]: any}
15
+ }
16
+
17
+ /**
18
+ * Tries to retrieve an annotation that specifies the singular name
19
+ * from a CSN. Valid annotations are listed in util.annotations
20
+ * and their precedence is in order of definition.
21
+ * If no singular is specified at all, undefined is returned.
22
+ * @param csn the CSN of an entity to check
23
+ * @returns the singular annotation or undefined
24
+ */
25
+ export function getSingularAnnotation(csn: {}): string | undefined;
26
+
27
+ /**
28
+ * Tries to retrieve an annotation that specifies the plural name
29
+ * from a CSN. Valid annotations are listed in util.annotations
30
+ * and their precedence is in order of definition.
31
+ * If no plural is specified at all, undefined is returned.
32
+ * @param csn the CSN of an entity to check
33
+ * @returns the plural annotation or undefined
34
+ */
35
+ export function getPluralAnnotation(csn: {}): string | undefined;
36
+
37
+
38
+ /**
39
+ * Users can specify that they want to refer to localisation
40
+ * using the syntax {i18n>Foo}, where Foo is the name of the
41
+ * entity as found in the .cds file
42
+ * (see: https://pages.github.tools.sap/cap/docs/guides/i18n)
43
+ * As this throws off the naming, we remove this wrapper
44
+ * unlocalize("{i18n>Foo}") -> "Foo"
45
+ * @param name the entity name (singular or plural).
46
+ * @returns the name without localisation syntax or untouched.
47
+ */
48
+ export function unlocalize(name: string): string;
49
+
50
+ /**
51
+ * Attempts to derive the singular form of an English noun.
52
+ * If '@singular' is passed as annotation, that is preferred.
53
+ * @param dn annotations
54
+ * @param stripped if true, leading namespace will be stripped
55
+ */
56
+ export function singular4(dn: Annotations | string, stripped: boolean): string;
57
+
58
+ /**
59
+ * Attempts to derive the plural form of an English noun.
60
+ * If '@plural' is passed as annotation, that is preferred.
61
+ * @param dn annotations
62
+ * @param stripped if true, leading namespace will be stripped
63
+ */
64
+ export function plural4(dn: Annotations | string, stripped: boolean): string;
65
+
66
+ /**
67
+ * Parses command line arguments into named and positional parameters.
68
+ * Named parameters are expected to start with a double dash (--).
69
+ * If the next argument `B` after a named parameter `A` is not a named parameter itself,
70
+ * `B` is used as value for `A`.
71
+ * If `A` and `B` are both named parameters, `A` is just treated as a flag (and may receive a default value).
72
+ * Only named parameters that occur in validFlags are allowed. Specifying named flags that are not listed there
73
+ * will cause an error.
74
+ * Named parameters that are either not specified or do not have a value assigned to them may draw a default value
75
+ * from their definition in validFlags.
76
+ * @param argv list of command line arguments
77
+ * @param validFlags allowed flags. May specify default values.
78
+ */
79
+ export function parseCommandlineArgs(argv: string[], validFlags: {[key: string]: CommandlineFlag}): ParsedFlags;
80
+
81
+ /**
82
+ * Performs a deep merge of the passed objects into the first object.
83
+ * See Object.assign(target, source).
84
+ * @param target object to assign into.
85
+ * @param source object to assign from.
86
+ */
87
+ export function deepMerge(target: {}, source: {}): void;