@sylphx/flow 2.18.1 → 2.18.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 +8 -0
- package/assets/slash-commands/continue.md +21 -66
- package/assets/slash-commands/review.md +17 -13
- package/package.json +1 -1
- package/src/core/backup-manager.ts +3 -14
- package/src/core/secrets-manager.ts +3 -14
- package/src/core/target-resolver.ts +28 -0
- package/src/utils/index.ts +1 -5
- package/src/utils/functional/array.ts +0 -355
- package/src/utils/functional/index.ts +0 -15
- package/src/utils/functional/object.ts +0 -279
- package/src/utils/functional/string.ts +0 -281
- package/src/utils/functional.ts +0 -543
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Functional object utilities
|
|
3
|
-
* Pure object transformation functions
|
|
4
|
-
*
|
|
5
|
-
* DESIGN RATIONALE:
|
|
6
|
-
* - Pure functions for object operations
|
|
7
|
-
* - Immutable transformations
|
|
8
|
-
* - Type-safe operations
|
|
9
|
-
* - No side effects
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get keys of object
|
|
14
|
-
*/
|
|
15
|
-
export const keys = <T extends object>(obj: T): Array<keyof T> => {
|
|
16
|
-
return Object.keys(obj) as Array<keyof T>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get values of object
|
|
21
|
-
*/
|
|
22
|
-
export const values = <T extends object>(obj: T): T[keyof T][] => {
|
|
23
|
-
return Object.values(obj);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get entries of object
|
|
28
|
-
*/
|
|
29
|
-
export const entries = <T extends object>(obj: T): [keyof T, T[keyof T]][] => {
|
|
30
|
-
return Object.entries(obj) as [keyof T, T[keyof T]][];
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create object from entries
|
|
35
|
-
*/
|
|
36
|
-
export const fromEntries = <K extends string | number | symbol, V>(
|
|
37
|
-
entries: [K, V][]
|
|
38
|
-
): Record<K, V> => {
|
|
39
|
-
return Object.fromEntries(entries) as Record<K, V>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Pick properties from object
|
|
44
|
-
*/
|
|
45
|
-
export const pick =
|
|
46
|
-
<T extends object, K extends keyof T>(keys: K[]) =>
|
|
47
|
-
(obj: T): Pick<T, K> => {
|
|
48
|
-
const result = {} as Pick<T, K>;
|
|
49
|
-
for (const key of keys) {
|
|
50
|
-
if (key in obj) {
|
|
51
|
-
result[key] = obj[key];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Omit properties from object
|
|
59
|
-
*/
|
|
60
|
-
export const omit =
|
|
61
|
-
<T extends object, K extends keyof T>(keys: K[]) =>
|
|
62
|
-
(obj: T): Omit<T, K> => {
|
|
63
|
-
const result = { ...obj };
|
|
64
|
-
for (const key of keys) {
|
|
65
|
-
delete result[key];
|
|
66
|
-
}
|
|
67
|
-
return result;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Map over object values
|
|
72
|
-
*/
|
|
73
|
-
export const mapValues =
|
|
74
|
-
<T extends object, U>(fn: (value: T[keyof T], key: keyof T) => U) =>
|
|
75
|
-
(obj: T): Record<keyof T, U> => {
|
|
76
|
-
const result = {} as Record<keyof T, U>;
|
|
77
|
-
for (const [key, value] of entries(obj)) {
|
|
78
|
-
result[key] = fn(value, key);
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Map over object keys
|
|
85
|
-
*/
|
|
86
|
-
export const mapKeys =
|
|
87
|
-
<T extends object, K extends string | number | symbol>(
|
|
88
|
-
fn: (key: keyof T, value: T[keyof T]) => K
|
|
89
|
-
) =>
|
|
90
|
-
(obj: T): Record<K, T[keyof T]> => {
|
|
91
|
-
const result = {} as Record<K, T[keyof T]>;
|
|
92
|
-
for (const [key, value] of entries(obj)) {
|
|
93
|
-
const newKey = fn(key, value);
|
|
94
|
-
result[newKey] = value;
|
|
95
|
-
}
|
|
96
|
-
return result;
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Filter object by predicate
|
|
101
|
-
*/
|
|
102
|
-
export const filterObj =
|
|
103
|
-
<T extends object>(predicate: (value: T[keyof T], key: keyof T) => boolean) =>
|
|
104
|
-
(obj: T): Partial<T> => {
|
|
105
|
-
const result = {} as Partial<T>;
|
|
106
|
-
for (const [key, value] of entries(obj)) {
|
|
107
|
-
if (predicate(value, key)) {
|
|
108
|
-
result[key] = value;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return result;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Merge objects (shallow)
|
|
116
|
-
*/
|
|
117
|
-
export const merge = <T extends object, U extends object>(obj1: T, obj2: U): T & U => {
|
|
118
|
-
return { ...obj1, ...obj2 };
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Deep merge objects
|
|
123
|
-
*/
|
|
124
|
-
export const deepMerge = <T extends object>(target: T, source: Partial<T>): T => {
|
|
125
|
-
const result = { ...target };
|
|
126
|
-
|
|
127
|
-
for (const key of keys(source)) {
|
|
128
|
-
const sourceValue = source[key];
|
|
129
|
-
const targetValue = result[key];
|
|
130
|
-
|
|
131
|
-
if (
|
|
132
|
-
typeof sourceValue === 'object' &&
|
|
133
|
-
sourceValue !== null &&
|
|
134
|
-
!Array.isArray(sourceValue) &&
|
|
135
|
-
typeof targetValue === 'object' &&
|
|
136
|
-
targetValue !== null &&
|
|
137
|
-
!Array.isArray(targetValue)
|
|
138
|
-
) {
|
|
139
|
-
result[key] = deepMerge(targetValue, sourceValue as any);
|
|
140
|
-
} else if (sourceValue !== undefined) {
|
|
141
|
-
result[key] = sourceValue as T[Extract<keyof T, string>];
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return result;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Check if object has property
|
|
150
|
-
*/
|
|
151
|
-
export const has =
|
|
152
|
-
<T extends object>(key: PropertyKey) =>
|
|
153
|
-
(obj: T): boolean => {
|
|
154
|
-
return key in obj;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Get property safely
|
|
159
|
-
*/
|
|
160
|
-
export const get =
|
|
161
|
-
<T extends object, K extends keyof T>(key: K) =>
|
|
162
|
-
(obj: T): T[K] | undefined => {
|
|
163
|
-
return obj[key];
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get nested property safely
|
|
168
|
-
*/
|
|
169
|
-
export const getPath =
|
|
170
|
-
(path: string) =>
|
|
171
|
-
(obj: any): any => {
|
|
172
|
-
const keys = path.split('.');
|
|
173
|
-
let current = obj;
|
|
174
|
-
|
|
175
|
-
for (const key of keys) {
|
|
176
|
-
if (current === null || current === undefined) {
|
|
177
|
-
return undefined;
|
|
178
|
-
}
|
|
179
|
-
current = current[key];
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return current;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Set property immutably
|
|
187
|
-
*/
|
|
188
|
-
export const set =
|
|
189
|
-
<T extends object, K extends keyof T>(key: K, value: T[K]) =>
|
|
190
|
-
(obj: T): T => {
|
|
191
|
-
return { ...obj, [key]: value };
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Update property immutably
|
|
196
|
-
*/
|
|
197
|
-
export const update =
|
|
198
|
-
<T extends object, K extends keyof T>(key: K, fn: (value: T[K]) => T[K]) =>
|
|
199
|
-
(obj: T): T => {
|
|
200
|
-
return { ...obj, [key]: fn(obj[key]) };
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if object is empty
|
|
205
|
-
*/
|
|
206
|
-
export const isEmpty = <T extends object>(obj: T): boolean => {
|
|
207
|
-
return keys(obj).length === 0;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Check if object is not empty
|
|
212
|
-
*/
|
|
213
|
-
export const isNotEmpty = <T extends object>(obj: T): boolean => {
|
|
214
|
-
return keys(obj).length > 0;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Remove undefined values
|
|
219
|
-
*/
|
|
220
|
-
export const compact = <T extends object>(obj: T): Partial<T> => {
|
|
221
|
-
return filterObj((value: any) => value !== undefined)(obj);
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Remove null and undefined values
|
|
226
|
-
*/
|
|
227
|
-
export const compactAll = <T extends object>(obj: T): Partial<T> => {
|
|
228
|
-
return filterObj((value: any) => value !== null && value !== undefined)(obj);
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Invert object (swap keys and values)
|
|
233
|
-
*/
|
|
234
|
-
export const invert = <T extends Record<string, string | number>>(
|
|
235
|
-
obj: T
|
|
236
|
-
): Record<string, string> => {
|
|
237
|
-
const result: Record<string, string> = {};
|
|
238
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
239
|
-
result[String(value)] = key;
|
|
240
|
-
}
|
|
241
|
-
return result;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Deep clone object
|
|
246
|
-
*/
|
|
247
|
-
export const clone = <T>(obj: T): T => {
|
|
248
|
-
if (obj === null || typeof obj !== 'object') {
|
|
249
|
-
return obj;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (Array.isArray(obj)) {
|
|
253
|
-
return obj.map(clone) as any;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const cloned = {} as T;
|
|
257
|
-
for (const key in obj) {
|
|
258
|
-
if (Object.hasOwn(obj, key)) {
|
|
259
|
-
cloned[key] = clone(obj[key]);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return cloned;
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Freeze object deeply (make immutable)
|
|
268
|
-
*/
|
|
269
|
-
export const deepFreeze = <T extends object>(obj: T): Readonly<T> => {
|
|
270
|
-
Object.freeze(obj);
|
|
271
|
-
|
|
272
|
-
for (const value of values(obj)) {
|
|
273
|
-
if (typeof value === 'object' && value !== null) {
|
|
274
|
-
deepFreeze(value);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return obj;
|
|
279
|
-
};
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Functional string utilities
|
|
3
|
-
* Pure string transformation functions
|
|
4
|
-
*
|
|
5
|
-
* DESIGN RATIONALE:
|
|
6
|
-
* - Pure functions for string operations
|
|
7
|
-
* - Composable transformations
|
|
8
|
-
* - Type-safe operations
|
|
9
|
-
* - No side effects
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Trim whitespace from string
|
|
14
|
-
*/
|
|
15
|
-
export const trim = (str: string): string => str.trim();
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Trim start of string
|
|
19
|
-
*/
|
|
20
|
-
export const trimStart = (str: string): string => str.trimStart();
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Trim end of string
|
|
24
|
-
*/
|
|
25
|
-
export const trimEnd = (str: string): string => str.trimEnd();
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Convert to lowercase
|
|
29
|
-
*/
|
|
30
|
-
export const toLowerCase = (str: string): string => str.toLowerCase();
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Convert to uppercase
|
|
34
|
-
*/
|
|
35
|
-
export const toUpperCase = (str: string): string => str.toUpperCase();
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Split string by delimiter
|
|
39
|
-
*/
|
|
40
|
-
export const split =
|
|
41
|
-
(delimiter: string | RegExp) =>
|
|
42
|
-
(str: string): string[] =>
|
|
43
|
-
str.split(delimiter);
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Join array of strings
|
|
47
|
-
*/
|
|
48
|
-
export const join =
|
|
49
|
-
(delimiter: string) =>
|
|
50
|
-
(arr: string[]): string =>
|
|
51
|
-
arr.join(delimiter);
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Replace pattern in string
|
|
55
|
-
*/
|
|
56
|
-
export const replace =
|
|
57
|
-
(pattern: string | RegExp, replacement: string) =>
|
|
58
|
-
(str: string): string =>
|
|
59
|
-
str.replace(pattern, replacement);
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Replace all occurrences
|
|
63
|
-
*/
|
|
64
|
-
export const replaceAll =
|
|
65
|
-
(pattern: string | RegExp, replacement: string) =>
|
|
66
|
-
(str: string): string =>
|
|
67
|
-
str.replaceAll(pattern, replacement);
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if string starts with prefix
|
|
71
|
-
*/
|
|
72
|
-
export const startsWith =
|
|
73
|
-
(prefix: string) =>
|
|
74
|
-
(str: string): boolean =>
|
|
75
|
-
str.startsWith(prefix);
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Check if string ends with suffix
|
|
79
|
-
*/
|
|
80
|
-
export const endsWith =
|
|
81
|
-
(suffix: string) =>
|
|
82
|
-
(str: string): boolean =>
|
|
83
|
-
str.endsWith(suffix);
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Check if string includes substring
|
|
87
|
-
*/
|
|
88
|
-
export const includes =
|
|
89
|
-
(substring: string) =>
|
|
90
|
-
(str: string): boolean =>
|
|
91
|
-
str.includes(substring);
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Test string against regex
|
|
95
|
-
*/
|
|
96
|
-
export const test =
|
|
97
|
-
(pattern: RegExp) =>
|
|
98
|
-
(str: string): boolean =>
|
|
99
|
-
pattern.test(str);
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Match string against regex
|
|
103
|
-
*/
|
|
104
|
-
export const match =
|
|
105
|
-
(pattern: RegExp) =>
|
|
106
|
-
(str: string): RegExpMatchArray | null =>
|
|
107
|
-
str.match(pattern);
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Slice string
|
|
111
|
-
*/
|
|
112
|
-
export const slice =
|
|
113
|
-
(start: number, end?: number) =>
|
|
114
|
-
(str: string): string =>
|
|
115
|
-
str.slice(start, end);
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Substring
|
|
119
|
-
*/
|
|
120
|
-
export const substring =
|
|
121
|
-
(start: number, end?: number) =>
|
|
122
|
-
(str: string): string =>
|
|
123
|
-
str.substring(start, end);
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Pad start of string
|
|
127
|
-
*/
|
|
128
|
-
export const padStart =
|
|
129
|
-
(length: number, fillString = ' ') =>
|
|
130
|
-
(str: string): string =>
|
|
131
|
-
str.padStart(length, fillString);
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Pad end of string
|
|
135
|
-
*/
|
|
136
|
-
export const padEnd =
|
|
137
|
-
(length: number, fillString = ' ') =>
|
|
138
|
-
(str: string): string =>
|
|
139
|
-
str.padEnd(length, fillString);
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Repeat string n times
|
|
143
|
-
*/
|
|
144
|
-
export const repeat =
|
|
145
|
-
(count: number) =>
|
|
146
|
-
(str: string): string =>
|
|
147
|
-
str.repeat(count);
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Check if string is empty
|
|
151
|
-
*/
|
|
152
|
-
export const isEmpty = (str: string): boolean => str.length === 0;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Check if string is blank (empty or whitespace)
|
|
156
|
-
*/
|
|
157
|
-
export const isBlank = (str: string): boolean => str.trim().length === 0;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Check if string is not empty
|
|
161
|
-
*/
|
|
162
|
-
export const isNotEmpty = (str: string): boolean => str.length > 0;
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Check if string is not blank
|
|
166
|
-
*/
|
|
167
|
-
export const isNotBlank = (str: string): boolean => str.trim().length > 0;
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Capitalize first letter
|
|
171
|
-
*/
|
|
172
|
-
export const capitalize = (str: string): string => {
|
|
173
|
-
if (str.length === 0) {
|
|
174
|
-
return str;
|
|
175
|
-
}
|
|
176
|
-
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Capitalize all words
|
|
181
|
-
*/
|
|
182
|
-
export const capitalizeWords = (str: string): string => {
|
|
183
|
-
return str
|
|
184
|
-
.split(' ')
|
|
185
|
-
.map((word) => capitalize(word))
|
|
186
|
-
.join(' ');
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Convert to camelCase
|
|
191
|
-
*/
|
|
192
|
-
export const toCamelCase = (str: string): string => {
|
|
193
|
-
return str
|
|
194
|
-
.replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))
|
|
195
|
-
.replace(/^[A-Z]/, (c) => c.toLowerCase());
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Convert to PascalCase
|
|
200
|
-
*/
|
|
201
|
-
export const toPascalCase = (str: string): string => {
|
|
202
|
-
const camel = toCamelCase(str);
|
|
203
|
-
return capitalize(camel);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Convert to kebab-case
|
|
208
|
-
*/
|
|
209
|
-
export const toKebabCase = (str: string): string => {
|
|
210
|
-
return str
|
|
211
|
-
.replace(/([a-z])([A-Z])/g, '$1-$2')
|
|
212
|
-
.replace(/[\s_]+/g, '-')
|
|
213
|
-
.toLowerCase();
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Convert to snake_case
|
|
218
|
-
*/
|
|
219
|
-
export const toSnakeCase = (str: string): string => {
|
|
220
|
-
return str
|
|
221
|
-
.replace(/([a-z])([A-Z])/g, '$1_$2')
|
|
222
|
-
.replace(/[\s-]+/g, '_')
|
|
223
|
-
.toLowerCase();
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Truncate string to max length
|
|
228
|
-
*/
|
|
229
|
-
export const truncate =
|
|
230
|
-
(maxLength: number, suffix = '...') =>
|
|
231
|
-
(str: string): string => {
|
|
232
|
-
if (str.length <= maxLength) {
|
|
233
|
-
return str;
|
|
234
|
-
}
|
|
235
|
-
return str.slice(0, maxLength - suffix.length) + suffix;
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Extract lines from string
|
|
240
|
-
*/
|
|
241
|
-
export const lines = (str: string): string[] => str.split(/\r?\n/);
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Remove empty lines
|
|
245
|
-
*/
|
|
246
|
-
export const removeEmptyLines = (str: string): string => {
|
|
247
|
-
return lines(str).filter(isNotBlank).join('\n');
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Indent each line
|
|
252
|
-
*/
|
|
253
|
-
export const indent =
|
|
254
|
-
(spaces: number) =>
|
|
255
|
-
(str: string): string => {
|
|
256
|
-
const indentation = ' '.repeat(spaces);
|
|
257
|
-
return lines(str)
|
|
258
|
-
.map((line) => indentation + line)
|
|
259
|
-
.join('\n');
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Remove indentation
|
|
264
|
-
*/
|
|
265
|
-
export const dedent = (str: string): string => {
|
|
266
|
-
const linesArray = lines(str);
|
|
267
|
-
|
|
268
|
-
// Find minimum indentation
|
|
269
|
-
const minIndent = linesArray.filter(isNotBlank).reduce((min, line) => {
|
|
270
|
-
const match = line.match(/^(\s*)/);
|
|
271
|
-
const indent = match ? match[1].length : 0;
|
|
272
|
-
return Math.min(min, indent);
|
|
273
|
-
}, Number.POSITIVE_INFINITY);
|
|
274
|
-
|
|
275
|
-
if (minIndent === 0 || minIndent === Number.POSITIVE_INFINITY) {
|
|
276
|
-
return str;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Remove minimum indentation from each line
|
|
280
|
-
return linesArray.map((line) => line.slice(minIndent)).join('\n');
|
|
281
|
-
};
|