expensify-common 2.0.187 → 2.0.189
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/dist/CLI.d.ts +160 -0
- package/dist/CLI.js +282 -0
- package/dist/esm/API.d.ts +11 -0
- package/dist/esm/API.js +786 -0
- package/dist/esm/APIDeferred.d.ts +7 -0
- package/dist/esm/APIDeferred.js +216 -0
- package/dist/esm/BrowserDetect.d.ts +19 -0
- package/dist/esm/BrowserDetect.js +105 -0
- package/dist/esm/CLI.d.ts +160 -0
- package/dist/esm/CLI.js +244 -0
- package/dist/esm/CONST.d.ts +1727 -0
- package/dist/esm/CONST.js +1847 -0
- package/dist/esm/Cookie.d.ts +68 -0
- package/dist/esm/Cookie.js +159 -0
- package/dist/esm/CredentialsWrapper.d.ts +32 -0
- package/dist/esm/CredentialsWrapper.js +46 -0
- package/dist/esm/Device.d.ts +8 -0
- package/dist/esm/Device.js +24 -0
- package/dist/esm/ExpenseRule.d.ts +39 -0
- package/dist/esm/ExpenseRule.js +77 -0
- package/dist/esm/ExpensiMark.d.ts +197 -0
- package/dist/esm/ExpensiMark.js +1374 -0
- package/dist/esm/Func.d.ts +40 -0
- package/dist/esm/Func.js +69 -0
- package/dist/esm/Log.d.ts +3 -0
- package/dist/esm/Log.js +35 -0
- package/dist/esm/Logger.d.ts +82 -0
- package/dist/esm/Logger.js +138 -0
- package/dist/esm/Network.d.ts +6 -0
- package/dist/esm/Network.js +168 -0
- package/dist/esm/Num.d.ts +95 -0
- package/dist/esm/Num.js +190 -0
- package/dist/esm/PageEvent.d.ts +25 -0
- package/dist/esm/PageEvent.js +22 -0
- package/dist/esm/PubSub.d.ts +2 -0
- package/dist/esm/PubSub.js +111 -0
- package/dist/esm/ReportHistoryStore.d.ts +8 -0
- package/dist/esm/ReportHistoryStore.js +199 -0
- package/dist/esm/SafeString.d.ts +8 -0
- package/{lib/SafeString.ts → dist/esm/SafeString.js} +8 -13
- package/dist/esm/Templates.d.ts +58 -0
- package/dist/esm/Templates.js +196 -0
- package/dist/esm/Url.d.ts +10 -0
- package/dist/esm/Url.js +15 -0
- package/dist/esm/components/CopyText.d.ts +45 -0
- package/dist/esm/components/CopyText.js +56 -0
- package/dist/esm/components/StepProgressBar.d.ts +26 -0
- package/dist/esm/components/StepProgressBar.js +40 -0
- package/dist/esm/components/form/element/combobox.d.ts +231 -0
- package/dist/esm/components/form/element/combobox.js +812 -0
- package/dist/esm/components/form/element/dropdown.d.ts +35 -0
- package/dist/esm/components/form/element/dropdown.js +61 -0
- package/dist/esm/components/form/element/dropdownItem.d.ts +55 -0
- package/dist/esm/components/form/element/dropdownItem.js +113 -0
- package/dist/esm/components/form/element/onOffSwitch.d.ts +94 -0
- package/dist/esm/components/form/element/onOffSwitch.js +167 -0
- package/dist/esm/components/form/element/switch.d.ts +58 -0
- package/dist/esm/components/form/element/switch.js +98 -0
- package/dist/esm/fastMerge.d.ts +9 -0
- package/dist/esm/fastMerge.js +58 -0
- package/dist/esm/index.d.ts +22 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/jquery.expensifyIframify.d.ts +9 -0
- package/dist/esm/jquery.expensifyIframify.js +397 -0
- package/dist/esm/md5.d.ts +2 -0
- package/dist/esm/md5.js +170 -0
- package/dist/esm/mixins/PubSub.d.ts +20 -0
- package/dist/esm/mixins/PubSub.js +47 -0
- package/dist/esm/mixins/extraClasses.d.ts +8 -0
- package/dist/esm/mixins/extraClasses.js +37 -0
- package/dist/esm/mixins/validationClasses.d.ts +12 -0
- package/dist/esm/mixins/validationClasses.js +30 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/str.d.ts +604 -0
- package/dist/esm/str.js +1036 -0
- package/dist/esm/tlds.d.ts +2 -0
- package/dist/esm/tlds.js +2 -0
- package/dist/esm/utils.d.ts +30 -0
- package/dist/esm/utils.js +65 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8 -1
- package/package.json +228 -8
- package/lib/CLI.ts +0 -409
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ----------------------------------------------------------------------------------------------
|
|
3
|
+
* WIP, This is in the process of migration from web-e. Please add methods to this as is needed.|
|
|
4
|
+
* ----------------------------------------------------------------------------------------------
|
|
5
|
+
*/
|
|
6
|
+
import get from 'lodash/get';
|
|
7
|
+
import once from 'lodash/once';
|
|
8
|
+
import * as Utils from './utils';
|
|
9
|
+
import * as Func from './Func';
|
|
10
|
+
/**
|
|
11
|
+
* @param {jquery.Deferred} promise
|
|
12
|
+
* @param {String} [extractedProperty]
|
|
13
|
+
*
|
|
14
|
+
* @returns {Object}
|
|
15
|
+
*/
|
|
16
|
+
export default function APIDeferred(promise, extractedProperty) {
|
|
17
|
+
/**
|
|
18
|
+
* @private
|
|
19
|
+
*/
|
|
20
|
+
const extractedPropertyName = extractedProperty || null;
|
|
21
|
+
let cachedResponse = null;
|
|
22
|
+
const doneCallbacks = [];
|
|
23
|
+
const alwaysCallbacks = [];
|
|
24
|
+
const unhandledCallbacks = [];
|
|
25
|
+
const failCallbacks = [];
|
|
26
|
+
const errorHandlers = {};
|
|
27
|
+
if (!promise) {
|
|
28
|
+
throw new Error('Cannot instantiate APIDeferred without a Deferred promise');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* @private
|
|
32
|
+
* @param {Object} [response]
|
|
33
|
+
* @returns {?Number}
|
|
34
|
+
*/
|
|
35
|
+
function extractJSONCode(response) {
|
|
36
|
+
return response ? response.jsonCode : null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Called when the jsonCode is not 200 and invokes the appropriate callbacks for the jsonCode
|
|
40
|
+
* If no callback is defined for the jsonCode then the unhandledCallbacks are invoked
|
|
41
|
+
* The failCallbacks are always called
|
|
42
|
+
*
|
|
43
|
+
* @private
|
|
44
|
+
* @param {Number} jsonCode
|
|
45
|
+
* @param {Object} [response]
|
|
46
|
+
*/
|
|
47
|
+
function handleError(jsonCode, response) {
|
|
48
|
+
// Look for handlers for this error code
|
|
49
|
+
const handlers = get(errorHandlers, jsonCode, []);
|
|
50
|
+
if (handlers.length > 0) {
|
|
51
|
+
Func.bulkInvoke(handlers, [jsonCode, response]);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// No explicit handlers, call the unhandled callbacks
|
|
55
|
+
Func.bulkInvoke(unhandledCallbacks, [jsonCode, response]);
|
|
56
|
+
}
|
|
57
|
+
// Always run the "fail" callbacks in case of error
|
|
58
|
+
Func.bulkInvoke(failCallbacks, [jsonCode, response]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Called when network call succeeds or when a callback is added via any of the following methods after the promise is resolved:
|
|
62
|
+
* done(), always(), handle(), unhandled(), or fail()
|
|
63
|
+
*
|
|
64
|
+
* @private
|
|
65
|
+
* @param {Object} [response]
|
|
66
|
+
*/
|
|
67
|
+
function handleDone(response) {
|
|
68
|
+
let returnedData;
|
|
69
|
+
// Figure out if we need to extract a property from the response, and if it is there.
|
|
70
|
+
const jsonCode = extractJSONCode(response);
|
|
71
|
+
const propertyRequested = extractedPropertyName && extractedPropertyName !== null;
|
|
72
|
+
const requestedPropertyPresent = propertyRequested && response && response[extractedPropertyName] !== undefined;
|
|
73
|
+
const propertyRequestedButMissing = propertyRequested && !requestedPropertyPresent;
|
|
74
|
+
// Save the response for any callbacks that might run in the future
|
|
75
|
+
cachedResponse = response;
|
|
76
|
+
// Handle three different success/failure scenarios: ok (jsonCode == 200), exception (jsonCode != 200), other error
|
|
77
|
+
if (jsonCode === 200 && (!propertyRequested || requestedPropertyPresent)) {
|
|
78
|
+
// Get the data that was requested
|
|
79
|
+
returnedData = propertyRequested && requestedPropertyPresent ? response[extractedPropertyName] : response;
|
|
80
|
+
// And then run the success callbacks
|
|
81
|
+
Func.bulkInvoke(doneCallbacks, [returnedData]);
|
|
82
|
+
}
|
|
83
|
+
else if (jsonCode !== null && jsonCode !== 200) {
|
|
84
|
+
// Exception thrown, handle it
|
|
85
|
+
handleError(jsonCode, response);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// Edge cases:
|
|
89
|
+
// If a specific property was requested but is missing, or if we got a blank response, throw an error
|
|
90
|
+
if (propertyRequestedButMissing) {
|
|
91
|
+
throw new Error(`Requested property " ${extractedPropertyName}" missing from response`);
|
|
92
|
+
}
|
|
93
|
+
if (!response) {
|
|
94
|
+
throw new Error('Blank response returned from API');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Always run the "always" callbacks
|
|
98
|
+
Func.bulkInvoke(alwaysCallbacks, [response]);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Re-runs the "done" flow with a saved response to ensure that all callbacks are invoked,
|
|
102
|
+
* even if they were added after the deferred was resolved
|
|
103
|
+
*
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
function ensureFutureCallbacksFire() {
|
|
107
|
+
if (promise.state() === 'pending') {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Re-run the done handler with the response we saved
|
|
111
|
+
handleDone(cachedResponse);
|
|
112
|
+
}
|
|
113
|
+
// Attach the function to be called when the promise is resolved
|
|
114
|
+
// See handleDone() for more details
|
|
115
|
+
promise.done(handleDone);
|
|
116
|
+
return {
|
|
117
|
+
/**
|
|
118
|
+
* Attaches a callback that will be invoked when the jsonCode is 200
|
|
119
|
+
*
|
|
120
|
+
* @param {Function} callback A function that takes the response as a parameter
|
|
121
|
+
*
|
|
122
|
+
* @returns {APIDeferred} itself, for chaining
|
|
123
|
+
*/
|
|
124
|
+
done(callback) {
|
|
125
|
+
if (Utils.isFunction(callback)) {
|
|
126
|
+
doneCallbacks.push(once(callback));
|
|
127
|
+
ensureFutureCallbacksFire();
|
|
128
|
+
}
|
|
129
|
+
return this;
|
|
130
|
+
},
|
|
131
|
+
/**
|
|
132
|
+
* Attaches a callback that will always be invoked
|
|
133
|
+
*
|
|
134
|
+
* @param {Function} callback A function that takes the response as a parameter
|
|
135
|
+
*
|
|
136
|
+
* @returns {APIDeferred} itself, for chaining
|
|
137
|
+
*/
|
|
138
|
+
always(callback) {
|
|
139
|
+
if (Utils.isFunction(callback)) {
|
|
140
|
+
alwaysCallbacks.push(once(callback));
|
|
141
|
+
ensureFutureCallbacksFire();
|
|
142
|
+
}
|
|
143
|
+
return this;
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Attaches a callback that will be invoked on non-200 jsonCodes
|
|
147
|
+
* Callbacks for jsonCode 200 are expected to be attached with done() and are therefore ignored here
|
|
148
|
+
*
|
|
149
|
+
* @param {Number[]} jsonCodes A list of error codes to handle
|
|
150
|
+
* @param {Function} callback A function that takes the jsonCode and response as parameters
|
|
151
|
+
*
|
|
152
|
+
* @returns {APIDeferred} itself, for chaining
|
|
153
|
+
*/
|
|
154
|
+
handle(jsonCodes, callback) {
|
|
155
|
+
if (Utils.isFunction(callback)) {
|
|
156
|
+
for (const code of jsonCodes) {
|
|
157
|
+
if (code === 200) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!errorHandlers[code]) {
|
|
161
|
+
errorHandlers[code] = [];
|
|
162
|
+
}
|
|
163
|
+
errorHandlers[code].push(once(callback));
|
|
164
|
+
}
|
|
165
|
+
ensureFutureCallbacksFire();
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
},
|
|
169
|
+
/**
|
|
170
|
+
* Attaches a callback that will be invoked on all errors not explicitly handled via handle()
|
|
171
|
+
*
|
|
172
|
+
* @param {Function} callback A function that takes the jsonCode and response as parameters
|
|
173
|
+
*
|
|
174
|
+
* @returns {APIDeferred} itself, for chaining
|
|
175
|
+
*/
|
|
176
|
+
unhandled(callback) {
|
|
177
|
+
if (Utils.isFunction(callback)) {
|
|
178
|
+
unhandledCallbacks.push(once(callback));
|
|
179
|
+
ensureFutureCallbacksFire();
|
|
180
|
+
}
|
|
181
|
+
return this;
|
|
182
|
+
},
|
|
183
|
+
/**
|
|
184
|
+
* Attaches a callback that will be always be invoked when the API does NOT return jsonCode 200, regardless of
|
|
185
|
+
* handle() or unhandled()
|
|
186
|
+
*
|
|
187
|
+
* @param {Function} callback A function that takes the jsonCode and response as parameters
|
|
188
|
+
*
|
|
189
|
+
* @returns {APIDeferred} itself, for chaining
|
|
190
|
+
*/
|
|
191
|
+
fail(callback) {
|
|
192
|
+
if (Utils.isFunction(callback)) {
|
|
193
|
+
failCallbacks.push(once(callback));
|
|
194
|
+
ensureFutureCallbacksFire();
|
|
195
|
+
}
|
|
196
|
+
return this;
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* Attaches a callback that will be invoked when this deferred resolves successfully
|
|
200
|
+
*
|
|
201
|
+
* @param {Function} callback A function that takes the response as a parameter
|
|
202
|
+
*
|
|
203
|
+
* @returns {APIDeferred} itself, for chaining
|
|
204
|
+
*/
|
|
205
|
+
then(callback) {
|
|
206
|
+
return promise.then((response) => {
|
|
207
|
+
const responseCode = extractJSONCode(response);
|
|
208
|
+
if (responseCode !== 200 || !Utils.isFunction(callback)) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
Func.invoke(callback, [response]);
|
|
212
|
+
return this;
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
export { BROWSERS };
|
|
3
|
+
export { MOBILE_PLATFORMS };
|
|
4
|
+
export let browser: string;
|
|
5
|
+
export let mobileDevice: string;
|
|
6
|
+
}
|
|
7
|
+
export default _default;
|
|
8
|
+
declare namespace BROWSERS {
|
|
9
|
+
let EDGE: string;
|
|
10
|
+
let CHROME: string;
|
|
11
|
+
let SAFARI: string;
|
|
12
|
+
let OPERA: string;
|
|
13
|
+
let EXPLORER: string;
|
|
14
|
+
let MOZILLA: string;
|
|
15
|
+
}
|
|
16
|
+
declare namespace MOBILE_PLATFORMS {
|
|
17
|
+
let iOS: string;
|
|
18
|
+
let android: string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as Utils from './utils';
|
|
2
|
+
const BROWSERS = {
|
|
3
|
+
EDGE: 'Edge',
|
|
4
|
+
CHROME: 'Chrome',
|
|
5
|
+
SAFARI: 'Safari',
|
|
6
|
+
OPERA: 'Opera',
|
|
7
|
+
EXPLORER: 'Explorer',
|
|
8
|
+
MOZILLA: 'Mozilla',
|
|
9
|
+
};
|
|
10
|
+
const MOBILE_PLATFORMS = {
|
|
11
|
+
iOS: 'iOS',
|
|
12
|
+
android: 'android',
|
|
13
|
+
};
|
|
14
|
+
function searchString() {
|
|
15
|
+
if (!Utils.isWindowAvailable() || !Utils.isNavigatorAvailable()) {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
const data = [
|
|
19
|
+
{
|
|
20
|
+
string: navigator.userAgent,
|
|
21
|
+
subString: 'Edge',
|
|
22
|
+
identity: BROWSERS.EDGE,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
string: navigator.userAgent,
|
|
26
|
+
subString: 'Chrome',
|
|
27
|
+
identity: BROWSERS.CHROME,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
string: navigator.vendor,
|
|
31
|
+
subString: 'Apple',
|
|
32
|
+
identity: BROWSERS.SAFARI,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
prop: window.opera,
|
|
36
|
+
identity: BROWSERS.OPERA,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
string: navigator.userAgent,
|
|
40
|
+
subString: 'Edge',
|
|
41
|
+
identity: BROWSERS.EDGE,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
string: navigator.userAgent,
|
|
45
|
+
subString: 'MSIE',
|
|
46
|
+
identity: BROWSERS.EXPLORER,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
string: navigator.userAgent,
|
|
50
|
+
subString: '.NET',
|
|
51
|
+
identity: BROWSERS.EXPLORER,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
string: navigator.userAgent,
|
|
55
|
+
subString: 'Gecko',
|
|
56
|
+
identity: BROWSERS.MOZILLA,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
let dataString;
|
|
60
|
+
let dataProp;
|
|
61
|
+
for (let i = 0; i < data.length; i++) {
|
|
62
|
+
dataString = data[i].string;
|
|
63
|
+
dataProp = data[i].prop;
|
|
64
|
+
if (dataString) {
|
|
65
|
+
if (dataString.indexOf(data[i].subString) !== -1) {
|
|
66
|
+
return data[i].identity;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (dataProp) {
|
|
70
|
+
return data[i].identity;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return '';
|
|
74
|
+
}
|
|
75
|
+
function getMobileDevice() {
|
|
76
|
+
if (!Utils.isNavigatorAvailable() || !navigator.userAgent) {
|
|
77
|
+
return '';
|
|
78
|
+
}
|
|
79
|
+
const data = [
|
|
80
|
+
{
|
|
81
|
+
devices: ['iPhone', 'iPad', 'iPod'],
|
|
82
|
+
identity: MOBILE_PLATFORMS.iOS,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
devices: ['Android'],
|
|
86
|
+
identity: MOBILE_PLATFORMS.android,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
const dataString = navigator.userAgent;
|
|
90
|
+
for (let i = 0; i < data.length; i++) {
|
|
91
|
+
const { devices, identity } = data[i];
|
|
92
|
+
for (let j = 0; j < devices.length; j++) {
|
|
93
|
+
if (dataString.indexOf(devices[j]) !== -1) {
|
|
94
|
+
return identity;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
export default {
|
|
101
|
+
BROWSERS,
|
|
102
|
+
MOBILE_PLATFORMS,
|
|
103
|
+
browser: searchString(),
|
|
104
|
+
mobileDevice: getMobileDevice(),
|
|
105
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { NonEmptyObject, NonEmptyTuple } from 'type-fest';
|
|
2
|
+
/**
|
|
3
|
+
* A base CLI arg has only a description, which we will use in the help/usage message (built-in to any CLI).
|
|
4
|
+
*/
|
|
5
|
+
type CLIArg = {
|
|
6
|
+
description: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A boolean arg is characterized only by its presence or absence so has no other fields,
|
|
10
|
+
* but we'll create a type alias to clearly distinguish it from other argument types.
|
|
11
|
+
*/
|
|
12
|
+
type BooleanArg = CLIArg;
|
|
13
|
+
/**
|
|
14
|
+
* Any other argument is provided raw in process.argv as a string.
|
|
15
|
+
* It can remain a string, or can be transformed into another type by a custom `parse` function.
|
|
16
|
+
* It can be optional (by providing a default) or required (no default value).
|
|
17
|
+
* It can also supersede other named arguments when provided.
|
|
18
|
+
*/
|
|
19
|
+
type StringArg<T = unknown> = CLIArg & {
|
|
20
|
+
default?: T;
|
|
21
|
+
parse?: (val: string) => T;
|
|
22
|
+
supersedes?: string[];
|
|
23
|
+
required?: boolean;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A positional argument is just a string arg, but also must be assigned a name which we will eventually expose the CLI consumer.
|
|
27
|
+
* If `variadic` is true, this must be the last positional arg and it collects all remaining positional args into a string[].
|
|
28
|
+
*/
|
|
29
|
+
type PositionalArg<T = unknown> = StringArg<T> & {
|
|
30
|
+
name: string;
|
|
31
|
+
variadic?: true;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* This type represents the config for a CLI.
|
|
35
|
+
* The last positional arg can be marked `variadic: true` to collect all remaining positional args into a string[].
|
|
36
|
+
*/
|
|
37
|
+
type CLIConfig = NonEmptyObject<{
|
|
38
|
+
/**
|
|
39
|
+
* Record of named flags that are fully characterized by their presence or absence (present=true,absent=false).
|
|
40
|
+
* @example `--verbose`
|
|
41
|
+
*/
|
|
42
|
+
flags?: Record<string, BooleanArg>;
|
|
43
|
+
/**
|
|
44
|
+
* Record of named arguments that are represented by a key and a value.
|
|
45
|
+
* @example `--threads=8`
|
|
46
|
+
* @example `--name Rory`
|
|
47
|
+
*/
|
|
48
|
+
namedArgs?: Record<string, StringArg>;
|
|
49
|
+
/**
|
|
50
|
+
* Tuple of positional args.
|
|
51
|
+
* @example `myScript.ts arg1 arg2 arg3`
|
|
52
|
+
*/
|
|
53
|
+
positionalArgs?: NonEmptyTuple<PositionalArg>;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Record of flags to boolean after parsing.
|
|
57
|
+
*/
|
|
58
|
+
type ParsedFlags<Flags extends CLIConfig['flags']> = {
|
|
59
|
+
[K in keyof NonNullable<Flags>]: boolean;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Utility type to infer the final value of a string param. Either:
|
|
63
|
+
* - it's a plain string, or
|
|
64
|
+
* - it has a parse function and the final value is inferred from the return type of that function
|
|
65
|
+
*/
|
|
66
|
+
type InferStringArgParsedValue<T extends StringArg> = T extends {
|
|
67
|
+
parse: (val: string) => infer R;
|
|
68
|
+
} ? R : string;
|
|
69
|
+
/**
|
|
70
|
+
* Record of named args after parsing.
|
|
71
|
+
*/
|
|
72
|
+
type ParsedNamedArgs<NamedArgs extends CLIConfig['namedArgs']> = {
|
|
73
|
+
[K in keyof NonNullable<NamedArgs>]: InferStringArgParsedValue<NonNullable<NamedArgs>[K]>;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Record of positional args after parsing.
|
|
77
|
+
* Variadic args are parsed as string[]; all others use InferStringArgParsedValue.
|
|
78
|
+
*/
|
|
79
|
+
type ParsedPositionalArgs<PositionalArgs extends CLIConfig['positionalArgs']> = {
|
|
80
|
+
[K in NonNullable<PositionalArgs>[number] as K['name']]: K extends {
|
|
81
|
+
variadic: true;
|
|
82
|
+
} ? string[] : InferStringArgParsedValue<K>;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Utility to parse command-line arguments to a script.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```
|
|
89
|
+
* const cli = new CLI({
|
|
90
|
+
* flags: {
|
|
91
|
+
* verbose: {
|
|
92
|
+
* description: 'Enable verbose logging',
|
|
93
|
+
* },
|
|
94
|
+
* },
|
|
95
|
+
* namedArgs: {
|
|
96
|
+
* time: {
|
|
97
|
+
* description: 'Time of day to greet (morning or evening)',
|
|
98
|
+
* default: 'morning',
|
|
99
|
+
* parse: (val) => {
|
|
100
|
+
* if (val !== 'morning' && val !== 'evening') {
|
|
101
|
+
* throw new Error('Must be "morning" or "evening"');
|
|
102
|
+
* }
|
|
103
|
+
* return val as 'morning' | 'evening';
|
|
104
|
+
* },
|
|
105
|
+
* },
|
|
106
|
+
* },
|
|
107
|
+
* positionalArgs: [
|
|
108
|
+
* {
|
|
109
|
+
* name: 'firstName'
|
|
110
|
+
* description: 'First name to greet',
|
|
111
|
+
* },
|
|
112
|
+
* {
|
|
113
|
+
* name: 'lastName',
|
|
114
|
+
* description: 'Last name to greet',
|
|
115
|
+
* default: '',
|
|
116
|
+
* },
|
|
117
|
+
* ],
|
|
118
|
+
* });
|
|
119
|
+
*
|
|
120
|
+
* let fullName = cli.positionalArgs.firstName;
|
|
121
|
+
* if (cli.flags.verbose) {
|
|
122
|
+
* fullName += cli.positionalArgs.lastName;
|
|
123
|
+
* }
|
|
124
|
+
* console.log(fullName);
|
|
125
|
+
* console.log(cli.namedArgs.time);
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Built-in flags that are always available on any CLI.
|
|
130
|
+
*/
|
|
131
|
+
type BuiltInFlags = {
|
|
132
|
+
yes: boolean;
|
|
133
|
+
no: boolean;
|
|
134
|
+
help: boolean;
|
|
135
|
+
};
|
|
136
|
+
declare class CLI<TConfig extends CLIConfig> {
|
|
137
|
+
private readonly config;
|
|
138
|
+
/**
|
|
139
|
+
* Flags after parsing (includes built-in flags like --yes, --no, and --help).
|
|
140
|
+
*/
|
|
141
|
+
readonly flags: ParsedFlags<TConfig['flags']> & BuiltInFlags;
|
|
142
|
+
/**
|
|
143
|
+
* Named args after parsing.
|
|
144
|
+
*/
|
|
145
|
+
readonly namedArgs: ParsedNamedArgs<TConfig['namedArgs']>;
|
|
146
|
+
/**
|
|
147
|
+
* Positional args after parsing, collected into a record keyed by the name of each arg.
|
|
148
|
+
*/
|
|
149
|
+
readonly positionalArgs: ParsedPositionalArgs<TConfig['positionalArgs']>;
|
|
150
|
+
constructor(config: TConfig);
|
|
151
|
+
private printHelp;
|
|
152
|
+
private static parseStringArg;
|
|
153
|
+
/**
|
|
154
|
+
* Prompts the user for confirmation and returns true if they confirm (y/yes), false otherwise.
|
|
155
|
+
* If --yes flag was passed, returns true immediately without prompting.
|
|
156
|
+
* If --no flag was passed, returns false immediately without prompting.
|
|
157
|
+
*/
|
|
158
|
+
promptUserConfirmation(message: string): Promise<boolean>;
|
|
159
|
+
}
|
|
160
|
+
export default CLI;
|