@poppinss/utils 7.0.0 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +218 -9
- package/build/{is_script_file-CbSEfxE_.js → is_script_file-D0Om5zlQ.js} +14 -1
- package/build/{safe_stringify-DenlQd3-.js → main-B9qlOEvt.js} +57 -5
- package/build/modules/assert.js +19 -0
- package/build/modules/base64.js +6 -0
- package/build/modules/fs/main.js +62 -2
- package/build/modules/json/main.js +1 -1
- package/build/modules/number.d.ts +70 -0
- package/build/modules/number.js +95 -0
- package/build/modules/string/main.js +2 -0
- package/build/modules/string/string_builder.js +2 -0
- package/build/src/get_git_worktree.d.ts +27 -0
- package/build/src/unique.d.ts +4 -0
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -119,6 +119,55 @@ import lodash from '@poppinss/utils/lodash'
|
|
|
119
119
|
lodash.pick(collection, keys)
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
## Number helpers
|
|
123
|
+
|
|
124
|
+
The number helpers are available from the `@poppinss/utils/number` subpath.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import number from '@poppinss/utils/number'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### clamp
|
|
131
|
+
|
|
132
|
+
Constrain a number to an inclusive range.
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
number.clamp(15, 0, 10) // 10
|
|
136
|
+
number.clamp(-2, 0, 10) // 0
|
|
137
|
+
number.clamp(5, 0, 10) // 5
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### between
|
|
141
|
+
|
|
142
|
+
Check whether a number is inside an inclusive range. You may specify the bounds in either order.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
number.between(5, 0, 10) // true
|
|
146
|
+
number.between(5, 10, 0) // true
|
|
147
|
+
number.between(11, 0, 10) // false
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### toFinite
|
|
151
|
+
|
|
152
|
+
Convert a value to a finite number. The method returns `0` when conversion fails or produces a non-finite number. You may provide a different fallback as the second argument.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
number.toFinite('42') // 42
|
|
156
|
+
number.toFinite('invalid') // 0
|
|
157
|
+
number.toFinite(Number.POSITIVE_INFINITY, 10) // 10
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### parse
|
|
161
|
+
|
|
162
|
+
Convert a value to a finite number without using a fallback. The method returns `null` for empty input, failed conversions, and non-finite numbers.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
number.parse('42') // 42
|
|
166
|
+
number.parse('') // null
|
|
167
|
+
number.parse('invalid') // null
|
|
168
|
+
number.parse(Number.POSITIVE_INFINITY) // null
|
|
169
|
+
```
|
|
170
|
+
|
|
122
171
|
## FS helpers
|
|
123
172
|
|
|
124
173
|
### fsReadAll
|
|
@@ -457,6 +506,17 @@ flatten({
|
|
|
457
506
|
// }
|
|
458
507
|
```
|
|
459
508
|
|
|
509
|
+
## unique
|
|
510
|
+
|
|
511
|
+
Return a new array with duplicate values removed while preserving the first occurrence order.
|
|
512
|
+
|
|
513
|
+
```ts
|
|
514
|
+
import { unique } from '@poppinss/utils'
|
|
515
|
+
|
|
516
|
+
unique(['foo', 'bar', 'foo', 'baz'])
|
|
517
|
+
// ['foo', 'bar', 'baz']
|
|
518
|
+
```
|
|
519
|
+
|
|
460
520
|
## isScriptFile
|
|
461
521
|
|
|
462
522
|
A filter to know if the file path ends with `.js`, `.json`, `.cjs`, `.mjs`, or `.ts`. In the case of `.ts` files, the `.d.ts` returns false.
|
|
@@ -496,6 +556,32 @@ const values = ['1_foo_bar', '12_foo_bar'].sort(naturalSort)
|
|
|
496
556
|
// Default sorting: ['1_foo_bar', '12_foo_bar']
|
|
497
557
|
```
|
|
498
558
|
|
|
559
|
+
## getGitWorktree
|
|
560
|
+
|
|
561
|
+
Returns information about the linked Git worktree containing the current working directory. The method returns `null` when called from the primary worktree, outside a Git repository, or when Git is unavailable.
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
import { getGitWorktree } from '@poppinss/utils'
|
|
565
|
+
|
|
566
|
+
const worktree = await getGitWorktree()
|
|
567
|
+
|
|
568
|
+
if (worktree) {
|
|
569
|
+
console.log(worktree)
|
|
570
|
+
// {
|
|
571
|
+
// name: 'feature-auth',
|
|
572
|
+
// slug: 'feature-auth',
|
|
573
|
+
// hash: '8d31cbe490f2',
|
|
574
|
+
// path: '/projects/worktrees/feature-auth'
|
|
575
|
+
// }
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
You may pass a different directory as the first argument. The `slug` is URL-safe, and the `hash` contains the first twelve characters of a SHA-256 hash generated from the canonical worktree path.
|
|
580
|
+
|
|
581
|
+
```ts
|
|
582
|
+
await getGitWorktree('/projects/worktrees/feature-auth/server')
|
|
583
|
+
```
|
|
584
|
+
|
|
499
585
|
## safeEqual
|
|
500
586
|
|
|
501
587
|
Check if two buffer or string values are the same. This method does not leak any timing information and prevents [timing attack](https://javascript.plainenglish.io/what-are-timing-attacks-and-how-to-prevent-them-using-nodejs-158cc7e2d70c).
|
package/build/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Secret } from './src/secret.js';
|
|
2
2
|
export { compose } from './src/compose.js';
|
|
3
3
|
export { flatten } from './src/flatten.js';
|
|
4
|
+
export { unique } from './src/unique.js';
|
|
4
5
|
export { safeEqual } from './src/safe_equal.js';
|
|
5
6
|
export { naturalSort } from './src/natural_sort.js';
|
|
6
7
|
export { isScriptFile } from './src/is_script_file.js';
|
|
@@ -9,3 +10,4 @@ export { MessageBuilder } from './src/message_builder.js';
|
|
|
9
10
|
export { ImportsBag, type ImportInfo } from './src/imports_bag.js';
|
|
10
11
|
export { defineStaticProperty } from './src/define_static_property.js';
|
|
11
12
|
export { detectAIAgent, isRunningInAIAgent } from './src/detect_ai_agent.js';
|
|
13
|
+
export { getGitWorktree, type GitWorktree } from './src/get_git_worktree.js';
|
package/build/index.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
import { n as safeParse, t as safeStringify } from "./
|
|
2
|
-
import { n as naturalSort, t as isScriptFile } from "./is_script_file-
|
|
1
|
+
import { n as safeParse, t as safeStringify } from "./main-B9qlOEvt.js";
|
|
2
|
+
import { n as naturalSort, t as isScriptFile } from "./is_script_file-D0Om5zlQ.js";
|
|
3
3
|
import { RuntimeException } from "./modules/exception.js";
|
|
4
|
-
import "./modules/json/main.js";
|
|
5
4
|
import { flattie } from "flattie";
|
|
6
5
|
import { Buffer } from "node:buffer";
|
|
7
|
-
import { timingSafeEqual } from "node:crypto";
|
|
6
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
7
|
+
import { basename, isAbsolute, relative, sep } from "node:path";
|
|
8
8
|
import string from "@poppinss/string";
|
|
9
9
|
import lodash from "@poppinss/utils/lodash";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { realpath } from "node:fs/promises";
|
|
12
|
+
import { execFile } from "node:child_process";
|
|
13
|
+
//#region src/secret.ts
|
|
10
14
|
const REDACTED = "[redacted]";
|
|
15
|
+
/**
|
|
16
|
+
* Define a Secret value that hides itself from the logs or the console
|
|
17
|
+
* statements.
|
|
18
|
+
*
|
|
19
|
+
* The idea is to prevent accidental leaking of sensitive information.
|
|
20
|
+
* Idea borrowed from.
|
|
21
|
+
* https://transcend.io/blog/keep-sensitive-values-out-of-your-logs-with-types
|
|
22
|
+
*/
|
|
11
23
|
var Secret = class Secret {
|
|
24
|
+
/** The secret value */
|
|
12
25
|
#value;
|
|
13
26
|
#keyword;
|
|
14
27
|
constructor(value, redactedKeyword) {
|
|
@@ -30,64 +43,156 @@ var Secret = class Secret {
|
|
|
30
43
|
toString() {
|
|
31
44
|
return this.#keyword;
|
|
32
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the original value
|
|
48
|
+
*/
|
|
33
49
|
release() {
|
|
34
50
|
return this.#value;
|
|
35
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Transform the original value and create a new
|
|
54
|
+
* secret from it.
|
|
55
|
+
*/
|
|
36
56
|
map(transformFunc) {
|
|
37
57
|
return new Secret(transformFunc(this.#value));
|
|
38
58
|
}
|
|
39
59
|
};
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/compose.ts
|
|
40
62
|
function compose(superclass, ...mixins) {
|
|
41
63
|
return mixins.reduce((c, mixin) => mixin(c), superclass);
|
|
42
64
|
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/flatten.ts
|
|
67
|
+
/**
|
|
68
|
+
* Recursively flatten an object/array.
|
|
69
|
+
*/
|
|
43
70
|
function flatten(input, glue, keepNullish) {
|
|
44
71
|
return flattie(input, glue, keepNullish);
|
|
45
72
|
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/unique.ts
|
|
75
|
+
/**
|
|
76
|
+
* Return a new array with duplicate values removed while preserving order.
|
|
77
|
+
*/
|
|
78
|
+
function unique(values) {
|
|
79
|
+
return Array.from(new Set(values));
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/safe_equal.ts
|
|
83
|
+
/**
|
|
84
|
+
* Compare two values to see if they are equal. The comparison is done in
|
|
85
|
+
* a way to avoid timing-attacks.
|
|
86
|
+
*/
|
|
46
87
|
function safeEqual(trustedValue, userInput) {
|
|
47
88
|
if (typeof trustedValue === "string" && typeof userInput === "string") {
|
|
89
|
+
/**
|
|
90
|
+
* The length of the comparison value.
|
|
91
|
+
*/
|
|
48
92
|
const trustedLength = Buffer.byteLength(trustedValue);
|
|
93
|
+
/**
|
|
94
|
+
* Expected value
|
|
95
|
+
*/
|
|
49
96
|
const trustedValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
|
|
50
97
|
trustedValueBuffer.write(trustedValue);
|
|
98
|
+
/**
|
|
99
|
+
* Actual value (taken from user input)
|
|
100
|
+
*/
|
|
51
101
|
const userValueBuffer = Buffer.alloc(trustedLength, 0, "utf-8");
|
|
52
102
|
userValueBuffer.write(userInput);
|
|
103
|
+
/**
|
|
104
|
+
* Ensure values are same and also have same length
|
|
105
|
+
*/
|
|
53
106
|
return timingSafeEqual(trustedValueBuffer, userValueBuffer) && trustedLength === Buffer.byteLength(userInput);
|
|
54
107
|
}
|
|
55
108
|
return timingSafeEqual(Buffer.from(trustedValue), Buffer.from(userInput));
|
|
56
109
|
}
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/import_default.ts
|
|
112
|
+
/**
|
|
113
|
+
* Dynamically import a module and ensure it has a default export
|
|
114
|
+
*/
|
|
57
115
|
async function importDefault(importFn, filePath) {
|
|
58
116
|
const moduleExports = await importFn();
|
|
59
|
-
|
|
117
|
+
/**
|
|
118
|
+
* Make sure a default export exists
|
|
119
|
+
*/
|
|
120
|
+
if (!("default" in moduleExports)) {
|
|
121
|
+
const errorMessage = filePath ? `Missing "export default" in module "${filePath}"` : `Missing "export default" from lazy import "${importFn}"`;
|
|
122
|
+
throw new RuntimeException(errorMessage, { cause: { source: importFn } });
|
|
123
|
+
}
|
|
60
124
|
return moduleExports.default;
|
|
61
125
|
}
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/message_builder.ts
|
|
128
|
+
/**
|
|
129
|
+
* Message builder exposes an API to "JSON.stringify" values by
|
|
130
|
+
* encoding purpose and expiry date inside them.
|
|
131
|
+
*
|
|
132
|
+
* The return value must be further encrypted to prevent tempering.
|
|
133
|
+
*/
|
|
62
134
|
var MessageBuilder = class {
|
|
63
135
|
#getExpiryDate(expiresIn) {
|
|
64
136
|
if (!expiresIn) return;
|
|
65
137
|
const expiryMs = string.milliseconds.parse(expiresIn);
|
|
66
138
|
return new Date(Date.now() + expiryMs);
|
|
67
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Returns a boolean telling, if message has been expired or not
|
|
142
|
+
*/
|
|
68
143
|
#isExpired(message) {
|
|
69
144
|
if (!message.expiryDate) return false;
|
|
70
145
|
const expiryDate = new Date(message.expiryDate);
|
|
71
146
|
return Number.isNaN(expiryDate.getTime()) || expiryDate < /* @__PURE__ */ new Date();
|
|
72
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Builds a message by encoding expiry date and purpose inside it.
|
|
150
|
+
*/
|
|
73
151
|
build(message, expiresIn, purpose) {
|
|
152
|
+
const expiryDate = this.#getExpiryDate(expiresIn);
|
|
74
153
|
return safeStringify({
|
|
75
154
|
message,
|
|
76
155
|
purpose,
|
|
77
|
-
expiryDate
|
|
156
|
+
expiryDate
|
|
78
157
|
});
|
|
79
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Verifies the message for expiry and purpose.
|
|
161
|
+
*/
|
|
80
162
|
verify(message, purpose) {
|
|
81
163
|
const parsed = safeParse(message);
|
|
164
|
+
/**
|
|
165
|
+
* After JSON.parse we do not receive a valid object
|
|
166
|
+
*/
|
|
82
167
|
if (typeof parsed !== "object" || !parsed) return null;
|
|
168
|
+
/**
|
|
169
|
+
* Missing ".message" property
|
|
170
|
+
*/
|
|
83
171
|
if (!parsed.message) return null;
|
|
172
|
+
/**
|
|
173
|
+
* Ensure purposes are same.
|
|
174
|
+
*/
|
|
84
175
|
if (parsed.purpose !== purpose) return null;
|
|
176
|
+
/**
|
|
177
|
+
* Ensure isn't expired
|
|
178
|
+
*/
|
|
85
179
|
if (this.#isExpired(parsed)) return null;
|
|
86
180
|
return parsed.message;
|
|
87
181
|
}
|
|
88
182
|
};
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/imports_bag.ts
|
|
185
|
+
/**
|
|
186
|
+
* ImportsBag manages and deduplicates imports from the same source
|
|
187
|
+
*/
|
|
89
188
|
var ImportsBag = class {
|
|
189
|
+
/**
|
|
190
|
+
* Map of source to ImportInfo
|
|
191
|
+
*/
|
|
90
192
|
#imports = /* @__PURE__ */ new Map();
|
|
193
|
+
/**
|
|
194
|
+
* Generate import statement from ImportInfo
|
|
195
|
+
*/
|
|
91
196
|
#generateImportStatement(imp) {
|
|
92
197
|
const parts = [];
|
|
93
198
|
if (imp.defaultImport || imp.namedImports && imp.namedImports.length > 0) {
|
|
@@ -104,15 +209,30 @@ var ImportsBag = class {
|
|
|
104
209
|
}
|
|
105
210
|
return parts.join("\n");
|
|
106
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Add an import to the bag
|
|
214
|
+
*/
|
|
107
215
|
add(importInfo) {
|
|
108
216
|
const existing = this.#imports.get(importInfo.source);
|
|
109
217
|
if (existing) {
|
|
218
|
+
/**
|
|
219
|
+
* Set default import (replaces existing if present)
|
|
220
|
+
*/
|
|
110
221
|
if (importInfo.defaultImport) existing.defaultImport = importInfo.defaultImport;
|
|
222
|
+
/**
|
|
223
|
+
* Set default type import (replaces existing if present)
|
|
224
|
+
*/
|
|
111
225
|
if (importInfo.defaultTypeImport) existing.defaultTypeImport = importInfo.defaultTypeImport;
|
|
226
|
+
/**
|
|
227
|
+
* Merge named imports without deduplication (deduplication happens in toArray)
|
|
228
|
+
*/
|
|
112
229
|
if (importInfo.namedImports) {
|
|
113
230
|
if (!existing.namedImports) existing.namedImports = [];
|
|
114
231
|
existing.namedImports.push(...importInfo.namedImports);
|
|
115
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Merge type imports without deduplication (deduplication happens in toArray)
|
|
235
|
+
*/
|
|
116
236
|
if (importInfo.typeImports) {
|
|
117
237
|
if (!existing.typeImports) existing.typeImports = [];
|
|
118
238
|
existing.typeImports.push(...importInfo.typeImports);
|
|
@@ -126,22 +246,38 @@ var ImportsBag = class {
|
|
|
126
246
|
});
|
|
127
247
|
return this;
|
|
128
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Get deduplicated imports as an array
|
|
251
|
+
*/
|
|
129
252
|
toArray() {
|
|
130
253
|
return Array.from(this.#imports.values()).map((imp) => ({
|
|
131
254
|
source: imp.source,
|
|
132
255
|
defaultImport: imp.defaultImport,
|
|
133
256
|
defaultTypeImport: imp.defaultTypeImport,
|
|
134
|
-
namedImports: imp.namedImports ?
|
|
135
|
-
typeImports: imp.typeImports ?
|
|
257
|
+
namedImports: imp.namedImports ? unique(imp.namedImports) : void 0,
|
|
258
|
+
typeImports: imp.typeImports ? unique(imp.typeImports) : void 0
|
|
136
259
|
}));
|
|
137
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Get deduplicated imports as a formatted string
|
|
263
|
+
*/
|
|
138
264
|
toString() {
|
|
139
265
|
return this.toArray().map((imp) => this.#generateImportStatement(imp)).join("\n");
|
|
140
266
|
}
|
|
141
267
|
};
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/define_static_property.ts
|
|
270
|
+
/**
|
|
271
|
+
* Define static properties on a class with inheritance in play.
|
|
272
|
+
*/
|
|
142
273
|
function defineStaticProperty(self, propertyName, { initialValue, strategy }) {
|
|
143
274
|
if (!self.hasOwnProperty(propertyName)) {
|
|
144
275
|
const value = self[propertyName];
|
|
276
|
+
/**
|
|
277
|
+
* Define the property as it is when the strategy is set
|
|
278
|
+
* to "define". Or the value on the prototype chain
|
|
279
|
+
* is set to undefined.
|
|
280
|
+
*/
|
|
145
281
|
if (strategy === "define" || value === void 0) {
|
|
146
282
|
Object.defineProperty(self, propertyName, {
|
|
147
283
|
value: initialValue,
|
|
@@ -159,6 +295,19 @@ function defineStaticProperty(self, propertyName, { initialValue, strategy }) {
|
|
|
159
295
|
});
|
|
160
296
|
}
|
|
161
297
|
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/detect_ai_agent.ts
|
|
300
|
+
/**
|
|
301
|
+
* Detects which AI coding agent the code is running under.
|
|
302
|
+
* Checks for environment variables set by different AI coding assistants:
|
|
303
|
+
* - CLAUDECODE='1' for Claude Code
|
|
304
|
+
* - GEMINI_CLI='1' for Gemini
|
|
305
|
+
* - GITHUB_COPILOT_CLI_MODE='1' for GitHub Copilot
|
|
306
|
+
* - WINDSURF_SESSION='1' or TERM_PROGRAM='windsurf' for Windsurf
|
|
307
|
+
* - CODEX_CLI='1' or CODEX_SANDBOX='1' for Codex
|
|
308
|
+
* - OPENCODE='1' for OpenCode
|
|
309
|
+
* - CURSOR_AGENT='1' for Cursor
|
|
310
|
+
*/
|
|
162
311
|
function detectAIAgent() {
|
|
163
312
|
if (process.env.CLAUDECODE === "1") return "claude";
|
|
164
313
|
if (process.env.GEMINI_CLI === "1") return "gemini";
|
|
@@ -169,7 +318,67 @@ function detectAIAgent() {
|
|
|
169
318
|
if (process.env.CURSOR_AGENT === "1") return "cursor";
|
|
170
319
|
return null;
|
|
171
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Returns true if the code is running within any AI coding agent.
|
|
323
|
+
*/
|
|
172
324
|
function isRunningInAIAgent() {
|
|
173
325
|
return detectAIAgent() !== null;
|
|
174
326
|
}
|
|
175
|
-
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/get_git_worktree.ts
|
|
329
|
+
const execFileAsync = promisify(execFile);
|
|
330
|
+
/**
|
|
331
|
+
* Returns true when the target path is inside the parent path.
|
|
332
|
+
*/
|
|
333
|
+
function isPathInside(parent, target) {
|
|
334
|
+
const relativePath = relative(parent, target);
|
|
335
|
+
return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Returns information about the linked Git worktree containing the given
|
|
339
|
+
* directory. Returns null for the main worktree and directories outside a
|
|
340
|
+
* Git repository.
|
|
341
|
+
*/
|
|
342
|
+
async function getGitWorktree(cwd = process.cwd()) {
|
|
343
|
+
try {
|
|
344
|
+
const [{ stdout }, resolvedCwd] = await Promise.all([execFileAsync("git", [
|
|
345
|
+
"worktree",
|
|
346
|
+
"list",
|
|
347
|
+
"--porcelain",
|
|
348
|
+
"-z"
|
|
349
|
+
], {
|
|
350
|
+
cwd,
|
|
351
|
+
encoding: "utf8",
|
|
352
|
+
windowsHide: true
|
|
353
|
+
}), realpath(cwd)]);
|
|
354
|
+
const worktreePaths = stdout.split("\0").filter((field) => field.startsWith("worktree ")).map((field) => field.slice(9));
|
|
355
|
+
const currentWorktree = (await Promise.all(worktreePaths.map(async (worktreePath, index) => {
|
|
356
|
+
try {
|
|
357
|
+
return {
|
|
358
|
+
index,
|
|
359
|
+
path: await realpath(worktreePath)
|
|
360
|
+
};
|
|
361
|
+
} catch {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
}))).filter((worktree) => {
|
|
365
|
+
return worktree !== null && isPathInside(worktree.path, resolvedCwd);
|
|
366
|
+
}).sort((current, next) => next.path.length - current.path.length)[0];
|
|
367
|
+
if (!currentWorktree || currentWorktree.index === 0) return null;
|
|
368
|
+
const name = basename(currentWorktree.path);
|
|
369
|
+
const hash = createHash("sha256").update(currentWorktree.path).digest("hex").slice(0, 12);
|
|
370
|
+
return {
|
|
371
|
+
name,
|
|
372
|
+
hash,
|
|
373
|
+
path: currentWorktree.path,
|
|
374
|
+
slug: string.slug(name, {
|
|
375
|
+
lower: true,
|
|
376
|
+
strict: true
|
|
377
|
+
}) || `worktree-${hash}`
|
|
378
|
+
};
|
|
379
|
+
} catch {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
384
|
+
export { ImportsBag, MessageBuilder, Secret, compose, defineStaticProperty, detectAIAgent, flatten, getGitWorktree, importDefault, isRunningInAIAgent, isScriptFile, naturalSort, safeEqual, unique };
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { extname } from "node:path";
|
|
2
|
+
//#region src/natural_sort.ts
|
|
3
|
+
let collator;
|
|
4
|
+
/**
|
|
5
|
+
* Perform natural sorting with "Array.sort()" method
|
|
6
|
+
*/
|
|
2
7
|
function naturalSort(current, next) {
|
|
3
|
-
|
|
8
|
+
collator ??= new Intl.Collator(void 0, {
|
|
4
9
|
numeric: true,
|
|
5
10
|
sensitivity: "base"
|
|
6
11
|
});
|
|
12
|
+
return collator.compare(current, next);
|
|
7
13
|
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/is_script_file.ts
|
|
8
16
|
const JS_MODULES = [
|
|
9
17
|
".js",
|
|
10
18
|
".json",
|
|
11
19
|
".cjs",
|
|
12
20
|
".mjs"
|
|
13
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Returns `true` when file ends with `.js`, `.json` or
|
|
24
|
+
* `.ts` but not `.d.ts`.
|
|
25
|
+
*/
|
|
14
26
|
function isScriptFile(filePath) {
|
|
15
27
|
const ext = extname(filePath);
|
|
16
28
|
if (JS_MODULES.includes(ext)) return true;
|
|
17
29
|
if (ext === ".ts" && !filePath.endsWith(".d.ts")) return true;
|
|
18
30
|
return false;
|
|
19
31
|
}
|
|
32
|
+
//#endregion
|
|
20
33
|
export { naturalSort as n, isScriptFile as t };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
8
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
9
9
|
var __copyProps = (to, from, except, desc) => {
|
|
10
10
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
11
|
key = keys[i];
|
|
@@ -16,14 +16,26 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
}
|
|
17
17
|
return to;
|
|
18
18
|
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
20
|
value: mod,
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region modules/json/safe_parse.ts
|
|
23
25
|
var import_secure_json_parse = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
24
26
|
const hasBuffer = typeof Buffer !== "undefined";
|
|
25
27
|
const suspectProtoRx = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/;
|
|
26
28
|
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
29
|
+
/**
|
|
30
|
+
* @description Internal parse function that parses JSON text with security checks.
|
|
31
|
+
* @private
|
|
32
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
33
|
+
* @param {Function} [reviver] - The JSON.parse() optional reviver argument.
|
|
34
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
35
|
+
* @returns {*} The parsed object.
|
|
36
|
+
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
|
|
37
|
+
* `options.constructorAction` is `'error'`.
|
|
38
|
+
*/
|
|
27
39
|
function _parse(text, reviver, options) {
|
|
28
40
|
if (options == null) {
|
|
29
41
|
if (reviver !== null && typeof reviver === "object") {
|
|
@@ -49,6 +61,14 @@ var import_secure_json_parse = (/* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
49
61
|
safe: options && options.safe
|
|
50
62
|
});
|
|
51
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* @description Scans and filters an object for forbidden prototype properties.
|
|
66
|
+
* @param {Object} obj - The object being scanned.
|
|
67
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
68
|
+
* @returns {Object|null} The filtered object, or `null` if safe mode is enabled and issues are found.
|
|
69
|
+
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
|
|
70
|
+
* `options.constructorAction` is `'error'`.
|
|
71
|
+
*/
|
|
52
72
|
function filter(obj, { protoAction = "error", constructorAction = "error", safe } = {}) {
|
|
53
73
|
let next = [obj];
|
|
54
74
|
while (next.length) {
|
|
@@ -73,6 +93,15 @@ var import_secure_json_parse = (/* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
73
93
|
}
|
|
74
94
|
return obj;
|
|
75
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* @description Parses a given JSON-formatted text into an object.
|
|
98
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
99
|
+
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument, or options object.
|
|
100
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
101
|
+
* @returns {*} The parsed object.
|
|
102
|
+
* @throws {SyntaxError} If the JSON text is malformed or contains forbidden prototype properties
|
|
103
|
+
* when `options.protoAction` or `options.constructorAction` is `'error'`.
|
|
104
|
+
*/
|
|
76
105
|
function parse(text, reviver, options) {
|
|
77
106
|
const { stackTraceLimit } = Error;
|
|
78
107
|
Error.stackTraceLimit = 0;
|
|
@@ -82,6 +111,12 @@ var import_secure_json_parse = (/* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
82
111
|
Error.stackTraceLimit = stackTraceLimit;
|
|
83
112
|
}
|
|
84
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* @description Safely parses a given JSON-formatted text into an object.
|
|
116
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
117
|
+
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument.
|
|
118
|
+
* @returns {*|null|undefined} The parsed object, `null` if security issues found, or `undefined` on parse error.
|
|
119
|
+
*/
|
|
85
120
|
function safeParse(text, reviver) {
|
|
86
121
|
const { stackTraceLimit } = Error;
|
|
87
122
|
Error.stackTraceLimit = 0;
|
|
@@ -99,12 +134,17 @@ var import_secure_json_parse = (/* @__PURE__ */ __commonJSMin(((exports, module)
|
|
|
99
134
|
module.exports.safeParse = safeParse;
|
|
100
135
|
module.exports.scan = filter;
|
|
101
136
|
})))();
|
|
137
|
+
/**
|
|
138
|
+
* A drop-in replacement for JSON.parse with prototype poisoning protection.
|
|
139
|
+
*/
|
|
102
140
|
function safeParse(jsonString, reviver) {
|
|
103
141
|
return (0, import_secure_json_parse.parse)(jsonString, reviver, {
|
|
104
142
|
protoAction: "remove",
|
|
105
143
|
constructorAction: "remove"
|
|
106
144
|
});
|
|
107
145
|
}
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region node_modules/safe-stable-stringify/esm/wrapper.js
|
|
108
148
|
var import_safe_stable_stringify = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
109
149
|
const { hasOwnProperty } = Object.prototype;
|
|
110
150
|
const stringify = configure();
|
|
@@ -132,7 +172,7 @@ var import_safe_stable_stringify = /* @__PURE__ */ __toESM((/* @__PURE__ */ __co
|
|
|
132
172
|
}
|
|
133
173
|
return array;
|
|
134
174
|
}
|
|
135
|
-
const typedArrayPrototypeGetSymbolToStringTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Object.getPrototypeOf(new Int8Array())), Symbol.toStringTag).get;
|
|
175
|
+
const typedArrayPrototypeGetSymbolToStringTag = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(Object.getPrototypeOf(/* @__PURE__ */ new Int8Array())), Symbol.toStringTag).get;
|
|
136
176
|
function isTypedArrayWithEntries(value) {
|
|
137
177
|
return typedArrayPrototypeGetSymbolToStringTag.call(value) !== void 0 && value.length !== 0;
|
|
138
178
|
}
|
|
@@ -513,15 +553,22 @@ var import_safe_stable_stringify = /* @__PURE__ */ __toESM((/* @__PURE__ */ __co
|
|
|
513
553
|
}
|
|
514
554
|
return stringify;
|
|
515
555
|
}
|
|
516
|
-
})))()
|
|
556
|
+
})))());
|
|
517
557
|
const configure = import_safe_stable_stringify.configure;
|
|
518
558
|
import_safe_stable_stringify.default;
|
|
519
559
|
import_safe_stable_stringify.default;
|
|
560
|
+
//#endregion
|
|
561
|
+
//#region modules/json/safe_stringify.ts
|
|
520
562
|
const stringify = configure({
|
|
521
563
|
bigint: false,
|
|
522
564
|
circularValue: void 0,
|
|
523
565
|
deterministic: false
|
|
524
566
|
});
|
|
567
|
+
/**
|
|
568
|
+
* Replacer to handle custom data types.
|
|
569
|
+
*
|
|
570
|
+
* - Bigints are converted to string
|
|
571
|
+
*/
|
|
525
572
|
function jsonStringifyReplacer(replacer) {
|
|
526
573
|
return function(key, value) {
|
|
527
574
|
const val = replacer ? replacer.call(this, key, value) : value;
|
|
@@ -529,7 +576,12 @@ function jsonStringifyReplacer(replacer) {
|
|
|
529
576
|
return val;
|
|
530
577
|
};
|
|
531
578
|
}
|
|
579
|
+
/**
|
|
580
|
+
* String Javascript values to a JSON string. Handles circular
|
|
581
|
+
* references and bigints
|
|
582
|
+
*/
|
|
532
583
|
function safeStringify(value, replacer, space) {
|
|
533
584
|
return stringify(value, jsonStringifyReplacer(replacer), space);
|
|
534
585
|
}
|
|
586
|
+
//#endregion
|
|
535
587
|
export { safeParse as n, safeStringify as t };
|
package/build/modules/assert.js
CHANGED
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
import { inspect } from "node:util";
|
|
2
2
|
import { AssertionError } from "node:assert";
|
|
3
|
+
//#region modules/assert.ts
|
|
4
|
+
/**
|
|
5
|
+
* @alias "assertExists"
|
|
6
|
+
*/
|
|
3
7
|
function assert(value, message) {
|
|
4
8
|
return assertExists(value, message);
|
|
5
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Assert the value is turthy or raise an exception.
|
|
12
|
+
*
|
|
13
|
+
* Truthy value excludes, undefined, null, and false values.
|
|
14
|
+
*/
|
|
6
15
|
function assertExists(value, message) {
|
|
7
16
|
if (!value) throw new AssertionError({ message: message ?? "value is falsy" });
|
|
8
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Throws error when method is called
|
|
20
|
+
*/
|
|
9
21
|
function assertUnreachable(x) {
|
|
10
22
|
throw new AssertionError({ message: `unreachable code executed: ${inspect(x)}` });
|
|
11
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Assert the value is not null.
|
|
26
|
+
*/
|
|
12
27
|
function assertNotNull(value, message) {
|
|
13
28
|
if (value === null) throw new AssertionError({ message: message ?? "unexpected null value" });
|
|
14
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Assert the value is not undefined.
|
|
32
|
+
*/
|
|
15
33
|
function assertIsDefined(value, message) {
|
|
16
34
|
if (value === void 0) throw new AssertionError({ message: message ?? "unexpected undefined value" });
|
|
17
35
|
}
|
|
36
|
+
//#endregion
|
|
18
37
|
export { assert, assertExists, assertIsDefined, assertNotNull, assertUnreachable };
|
package/build/modules/base64.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
//#region modules/base64.ts
|
|
2
|
+
/**
|
|
3
|
+
* Helper class to base64 encode/decode values with option
|
|
4
|
+
* for url encoding and decoding
|
|
5
|
+
*/
|
|
1
6
|
var Base64 = class {
|
|
2
7
|
encode(data, encoding) {
|
|
3
8
|
if (typeof data === "string") return Buffer.from(data, encoding).toString("base64");
|
|
@@ -23,4 +28,5 @@ var Base64 = class {
|
|
|
23
28
|
}
|
|
24
29
|
};
|
|
25
30
|
const base64 = new Base64();
|
|
31
|
+
//#endregion
|
|
26
32
|
export { base64 as default };
|
package/build/modules/fs/main.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { n as naturalSort, t as isScriptFile } from "../../is_script_file-
|
|
1
|
+
import { n as naturalSort, t as isScriptFile } from "../../is_script_file-D0Om5zlQ.js";
|
|
2
2
|
import { extname, join, relative, sep } from "node:path";
|
|
3
3
|
import string from "@poppinss/string";
|
|
4
4
|
import lodash from "@poppinss/utils/lodash";
|
|
5
5
|
import { readdir, stat } from "node:fs/promises";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
//#region modules/fs/fs_read_all.ts
|
|
8
|
+
/**
|
|
9
|
+
* Returns an array of file paths from the given location. You can
|
|
10
|
+
* optionally filter and sort files by passing relevant options
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* await fsReadAll(new URL('./', import.meta.url))
|
|
14
|
+
*
|
|
15
|
+
* await fsReadAll(new URL('./', import.meta.url), {
|
|
16
|
+
* filter: (filePath) => filePath.endsWith('.js')
|
|
17
|
+
* })
|
|
18
|
+
|
|
19
|
+
* await fsReadAll(new URL('./', import.meta.url), {
|
|
20
|
+
* absolute: true,
|
|
21
|
+
* unixPaths: true
|
|
22
|
+
* })
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
7
25
|
async function fsReadAll(location, options) {
|
|
8
26
|
const normalizedLocation = typeof location === "string" ? location : fileURLToPath(location);
|
|
9
27
|
const normalizedOptions = Object.assign({
|
|
@@ -11,6 +29,10 @@ async function fsReadAll(location, options) {
|
|
|
11
29
|
sort: naturalSort
|
|
12
30
|
}, options);
|
|
13
31
|
const pathType = normalizedOptions.pathType || "relative";
|
|
32
|
+
/**
|
|
33
|
+
* Check to see if the root directory exists and ignore
|
|
34
|
+
* error when "ignoreMissingRoot" is set to true
|
|
35
|
+
*/
|
|
14
36
|
try {
|
|
15
37
|
await stat(normalizedLocation);
|
|
16
38
|
} catch (error) {
|
|
@@ -22,7 +44,9 @@ async function fsReadAll(location, options) {
|
|
|
22
44
|
withFileTypes: true
|
|
23
45
|
})).filter((dirent) => {
|
|
24
46
|
if (!dirent.isFile()) return false;
|
|
25
|
-
if (dirent.name.startsWith(".")
|
|
47
|
+
if (dirent.name.startsWith(".")) return false;
|
|
48
|
+
const relativePath = relative(normalizedLocation, dirent.parentPath);
|
|
49
|
+
if (relativePath && relativePath.split(sep).some((segment) => segment.startsWith("."))) return false;
|
|
26
50
|
return true;
|
|
27
51
|
}).map((file) => {
|
|
28
52
|
switch (pathType) {
|
|
@@ -36,13 +60,45 @@ async function fsReadAll(location, options) {
|
|
|
36
60
|
if (normalizedOptions.filter) return files.filter(normalizedOptions.filter).sort(normalizedOptions.sort);
|
|
37
61
|
return files.sort(normalizedOptions.sort);
|
|
38
62
|
}
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region modules/fs/fs_import_all.ts
|
|
65
|
+
/**
|
|
66
|
+
* Import the file and update the values collection with the default
|
|
67
|
+
* export.
|
|
68
|
+
*/
|
|
39
69
|
async function importFile(basePath, fileURL, values, options) {
|
|
70
|
+
/**
|
|
71
|
+
* Converting URL to file path
|
|
72
|
+
*/
|
|
40
73
|
const filePath = fileURLToPath(fileURL);
|
|
74
|
+
/**
|
|
75
|
+
* Grab file extension
|
|
76
|
+
*/
|
|
41
77
|
const fileExtension = extname(filePath);
|
|
42
78
|
const collectionKey = relative(basePath, filePath).replace(new RegExp(`${fileExtension}$`), "").split(sep);
|
|
79
|
+
/**
|
|
80
|
+
* Import module
|
|
81
|
+
*/
|
|
43
82
|
const exportedValue = fileExtension === ".json" ? await import(fileURL, { with: { type: "json" } }) : await import(fileURL);
|
|
44
83
|
lodash.set(values, options.transformKeys ? options.transformKeys(collectionKey) : collectionKey, exportedValue.default ? exportedValue.default : { ...exportedValue });
|
|
45
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns an array of file paths from the given location. You can
|
|
87
|
+
* optionally filter and sort files by passing relevant options
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* await fsReadAll(new URL('./', import.meta.url))
|
|
91
|
+
*
|
|
92
|
+
* await fsReadAll(new URL('./', import.meta.url), {
|
|
93
|
+
* filter: (filePath) => filePath.endsWith('.js')
|
|
94
|
+
* })
|
|
95
|
+
|
|
96
|
+
* await fsReadAll(new URL('./', import.meta.url), {
|
|
97
|
+
* absolute: true,
|
|
98
|
+
* unixPaths: true
|
|
99
|
+
* })
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
46
102
|
async function fsImportAll(location, options) {
|
|
47
103
|
options = options || {};
|
|
48
104
|
const collection = {};
|
|
@@ -52,7 +108,11 @@ async function fsImportAll(location, options) {
|
|
|
52
108
|
...options,
|
|
53
109
|
pathType: "url"
|
|
54
110
|
});
|
|
111
|
+
/**
|
|
112
|
+
* Parallelly import all the files and mutate the values collection
|
|
113
|
+
*/
|
|
55
114
|
await Promise.all(files.map((file) => importFile(normalizedLocation, file, collection, options)));
|
|
56
115
|
return collection;
|
|
57
116
|
}
|
|
117
|
+
//#endregion
|
|
58
118
|
export { fsImportAll, fsReadAll };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as safeParse, t as safeStringify } from "../../
|
|
1
|
+
import { n as safeParse, t as safeStringify } from "../../main-B9qlOEvt.js";
|
|
2
2
|
export { safeParse, safeStringify };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection of number helpers to clamp and parse numeric values.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* number.clamp(15, 0, 10) // 10
|
|
6
|
+
* number.between(5, 0, 10) // true
|
|
7
|
+
* number.parse('42') // 42
|
|
8
|
+
* number.toFinite('abc', 0) // 0
|
|
9
|
+
*/
|
|
10
|
+
declare const number: {
|
|
11
|
+
/**
|
|
12
|
+
* Constrain a number to stay within the given bounds.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The number to constrain
|
|
15
|
+
* @param min - The lower bound
|
|
16
|
+
* @param max - The upper bound
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* number.clamp(15, 0, 10) // 10
|
|
20
|
+
* number.clamp(-2, 0, 10) // 0
|
|
21
|
+
* number.clamp(5, 0, 10) // 5
|
|
22
|
+
*/
|
|
23
|
+
clamp(value: number, min: number, max: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Check if a number is inside an inclusive range. The bounds may be
|
|
26
|
+
* passed in either order.
|
|
27
|
+
*
|
|
28
|
+
* @param value - The number to test
|
|
29
|
+
* @param min - One end of the range
|
|
30
|
+
* @param max - The other end of the range
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* number.between(5, 0, 10) // true
|
|
34
|
+
* number.between(0, 0, 10) // true
|
|
35
|
+
* number.between(11, 0, 10) // false
|
|
36
|
+
* number.between(5, 10, 0) // true
|
|
37
|
+
*/
|
|
38
|
+
between(value: number, min: number, max: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Convert a value to a finite number. Returns the fallback when the
|
|
41
|
+
* result is `NaN` or `Infinity`.
|
|
42
|
+
*
|
|
43
|
+
* @param value - The value to convert
|
|
44
|
+
* @param fallback - Value to return when conversion fails. Defaults to `0`
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* number.toFinite(5) // 5
|
|
48
|
+
* number.toFinite('42') // 42
|
|
49
|
+
* number.toFinite(Number.NaN) // 0
|
|
50
|
+
* number.toFinite(Number.POSITIVE_INFINITY) // 0
|
|
51
|
+
* number.toFinite('abc', 10) // 10
|
|
52
|
+
*/
|
|
53
|
+
toFinite(value: unknown, fallback?: number): number;
|
|
54
|
+
/**
|
|
55
|
+
* Parse a value into a finite number. Returns `null` for empty input,
|
|
56
|
+
* `NaN`, and `Infinity` instead of substituting a fallback.
|
|
57
|
+
*
|
|
58
|
+
* @param value - The value to parse
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* number.parse(5) // 5
|
|
62
|
+
* number.parse('42') // 42
|
|
63
|
+
* number.parse('') // null
|
|
64
|
+
* number.parse(null) // null
|
|
65
|
+
* number.parse(Number.NaN) // null
|
|
66
|
+
* number.parse(Number.POSITIVE_INFINITY) // null
|
|
67
|
+
*/
|
|
68
|
+
parse(value: unknown): number | null;
|
|
69
|
+
};
|
|
70
|
+
export default number;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
//#region modules/number.ts
|
|
2
|
+
/**
|
|
3
|
+
* Coerce an unknown value to a finite number without allowing numeric
|
|
4
|
+
* conversion errors to escape.
|
|
5
|
+
*/
|
|
6
|
+
function toFiniteNumber(value) {
|
|
7
|
+
try {
|
|
8
|
+
const numericValue = typeof value === "number" ? value : Number(value);
|
|
9
|
+
return Number.isFinite(numericValue) ? numericValue : null;
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Collection of number helpers to clamp and parse numeric values.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* number.clamp(15, 0, 10) // 10
|
|
19
|
+
* number.between(5, 0, 10) // true
|
|
20
|
+
* number.parse('42') // 42
|
|
21
|
+
* number.toFinite('abc', 0) // 0
|
|
22
|
+
*/
|
|
23
|
+
const number = {
|
|
24
|
+
/**
|
|
25
|
+
* Constrain a number to stay within the given bounds.
|
|
26
|
+
*
|
|
27
|
+
* @param value - The number to constrain
|
|
28
|
+
* @param min - The lower bound
|
|
29
|
+
* @param max - The upper bound
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* number.clamp(15, 0, 10) // 10
|
|
33
|
+
* number.clamp(-2, 0, 10) // 0
|
|
34
|
+
* number.clamp(5, 0, 10) // 5
|
|
35
|
+
*/
|
|
36
|
+
clamp(value, min, max) {
|
|
37
|
+
if (value < min) return min;
|
|
38
|
+
else if (value > max) return max;
|
|
39
|
+
else return value;
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* Check if a number is inside an inclusive range. The bounds may be
|
|
43
|
+
* passed in either order.
|
|
44
|
+
*
|
|
45
|
+
* @param value - The number to test
|
|
46
|
+
* @param min - One end of the range
|
|
47
|
+
* @param max - The other end of the range
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* number.between(5, 0, 10) // true
|
|
51
|
+
* number.between(0, 0, 10) // true
|
|
52
|
+
* number.between(11, 0, 10) // false
|
|
53
|
+
* number.between(5, 10, 0) // true
|
|
54
|
+
*/
|
|
55
|
+
between(value, min, max) {
|
|
56
|
+
return value >= Math.min(min, max) && value <= Math.max(min, max);
|
|
57
|
+
},
|
|
58
|
+
/**
|
|
59
|
+
* Convert a value to a finite number. Returns the fallback when the
|
|
60
|
+
* result is `NaN` or `Infinity`.
|
|
61
|
+
*
|
|
62
|
+
* @param value - The value to convert
|
|
63
|
+
* @param fallback - Value to return when conversion fails. Defaults to `0`
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* number.toFinite(5) // 5
|
|
67
|
+
* number.toFinite('42') // 42
|
|
68
|
+
* number.toFinite(Number.NaN) // 0
|
|
69
|
+
* number.toFinite(Number.POSITIVE_INFINITY) // 0
|
|
70
|
+
* number.toFinite('abc', 10) // 10
|
|
71
|
+
*/
|
|
72
|
+
toFinite(value, fallback = 0) {
|
|
73
|
+
return toFiniteNumber(value) ?? fallback;
|
|
74
|
+
},
|
|
75
|
+
/**
|
|
76
|
+
* Parse a value into a finite number. Returns `null` for empty input,
|
|
77
|
+
* `NaN`, and `Infinity` instead of substituting a fallback.
|
|
78
|
+
*
|
|
79
|
+
* @param value - The value to parse
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* number.parse(5) // 5
|
|
83
|
+
* number.parse('42') // 42
|
|
84
|
+
* number.parse('') // null
|
|
85
|
+
* number.parse(null) // null
|
|
86
|
+
* number.parse(Number.NaN) // null
|
|
87
|
+
* number.parse(Number.POSITIVE_INFINITY) // null
|
|
88
|
+
*/
|
|
89
|
+
parse(value) {
|
|
90
|
+
if (value === null || value === void 0 || value === "") return null;
|
|
91
|
+
return toFiniteNumber(value);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { number as default };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Information about a linked Git worktree.
|
|
3
|
+
*/
|
|
4
|
+
export type GitWorktree = {
|
|
5
|
+
/**
|
|
6
|
+
* Basename of the worktree directory.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* URL-safe slug generated from the worktree name.
|
|
11
|
+
*/
|
|
12
|
+
slug: string;
|
|
13
|
+
/**
|
|
14
|
+
* First twelve characters of the SHA-256 hash for the canonical path.
|
|
15
|
+
*/
|
|
16
|
+
hash: string;
|
|
17
|
+
/**
|
|
18
|
+
* Canonical absolute path to the worktree.
|
|
19
|
+
*/
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Returns information about the linked Git worktree containing the given
|
|
24
|
+
* directory. Returns null for the main worktree and directories outside a
|
|
25
|
+
* Git repository.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getGitWorktree(cwd?: string): Promise<GitWorktree | null>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poppinss/utils",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "Handy utilities for repetitive work",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./assert": "./build/modules/assert.js",
|
|
25
25
|
"./base64": "./build/modules/base64.js",
|
|
26
26
|
"./exception": "./build/modules/exception.js",
|
|
27
|
+
"./number": "./build/modules/number.js",
|
|
27
28
|
"./types": "./build/modules/types.js"
|
|
28
29
|
},
|
|
29
30
|
"scripts": {
|
|
@@ -42,34 +43,34 @@
|
|
|
42
43
|
"quick:test": "node --import=@poppinss/ts-exec bin/test.ts"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"@adonisjs/eslint-config": "^3.
|
|
46
|
-
"@adonisjs/logger": "^
|
|
47
|
-
"@adonisjs/prettier-config": "^1.
|
|
46
|
+
"@adonisjs/eslint-config": "^3.1.0",
|
|
47
|
+
"@adonisjs/logger": "^7.1.1",
|
|
48
|
+
"@adonisjs/prettier-config": "^1.5.0",
|
|
48
49
|
"@adonisjs/tsconfig": "^2.0.0",
|
|
49
50
|
"@japa/assert": "^4.2.0",
|
|
50
51
|
"@japa/expect-type": "^2.0.4",
|
|
51
52
|
"@japa/runner": "^5.3.0",
|
|
52
53
|
"@poppinss/ts-exec": "^1.4.4",
|
|
53
|
-
"@release-it/conventional-changelog": "^
|
|
54
|
+
"@release-it/conventional-changelog": "^12.0.0",
|
|
54
55
|
"@types/fs-extra": "^11.0.4",
|
|
55
|
-
"@types/node": "^
|
|
56
|
-
"c8": "^
|
|
57
|
-
"eslint": "^10.0
|
|
58
|
-
"fs-extra": "^11.
|
|
59
|
-
"lodash": "^4.
|
|
56
|
+
"@types/node": "^26.4.1",
|
|
57
|
+
"c8": "^12.0.0",
|
|
58
|
+
"eslint": "^10.10.0",
|
|
59
|
+
"fs-extra": "^11.4.0",
|
|
60
|
+
"lodash": "^4.18.1",
|
|
60
61
|
"lodash-cli": "^4.17.5",
|
|
61
62
|
"move-file-cli": "^3.0.0",
|
|
62
|
-
"prettier": "^3.
|
|
63
|
-
"release-it": "^
|
|
63
|
+
"prettier": "^3.9.6",
|
|
64
|
+
"release-it": "^21.0.2",
|
|
64
65
|
"safe-stable-stringify": "^2.5.0",
|
|
65
66
|
"secure-json-parse": "^4.1.0",
|
|
66
|
-
"tsdown": "^0.
|
|
67
|
-
"typescript": "^
|
|
67
|
+
"tsdown": "^0.23.0",
|
|
68
|
+
"typescript": "^6.0.3"
|
|
68
69
|
},
|
|
69
70
|
"dependencies": {
|
|
70
71
|
"@poppinss/exception": "^1.2.3",
|
|
71
72
|
"@poppinss/object-builder": "^1.1.0",
|
|
72
|
-
"@poppinss/string": "^1.7.
|
|
73
|
+
"@poppinss/string": "^1.7.2",
|
|
73
74
|
"@poppinss/types": "^1.2.1",
|
|
74
75
|
"flattie": "^1.1.1"
|
|
75
76
|
},
|
|
@@ -101,6 +102,7 @@
|
|
|
101
102
|
"./modules/assert.ts",
|
|
102
103
|
"./modules/base64.ts",
|
|
103
104
|
"./modules/exception.ts",
|
|
105
|
+
"./modules/number.ts",
|
|
104
106
|
"./modules/types.ts"
|
|
105
107
|
],
|
|
106
108
|
"external": [
|