@vimcord/internal 0.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.
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Deep partial type utility - recursively makes all properties optional.
3
+ * Preserves function types to maintain their signatures.
4
+ */
5
+ type PartialDeep<T> = T extends (...args: unknown[]) => unknown ? T : T extends object ? T extends ReadonlyArray<infer U> ? ReadonlyArray<PartialDeep<U>> : T extends Array<infer U> ? Array<PartialDeep<U>> : {
6
+ [K in keyof T]?: PartialDeep<T[K]>;
7
+ } : T;
8
+ /** A function used for indexing something. */
9
+ type IndexFn<T> = (module: T) => string | string[] | undefined;
10
+
11
+ declare class VimcordError extends Error {
12
+ readonly code: string;
13
+ constructor(message: string, code: string);
14
+ }
15
+
16
+ /** Ensures a value is always returned as an array */
17
+ declare function forceArray<T>(value: T | T[]): T[];
18
+
19
+ interface ImportedModule<T> {
20
+ module: T;
21
+ path: string;
22
+ }
23
+ declare function getProcessDir(): string;
24
+ declare function isTSOrJS(filename: string, suffix?: string | string[]): boolean;
25
+ declare function importModulesFromDir<T>(dir: string, suffix?: string | string[]): Promise<ImportedModule<T>[]>;
26
+
27
+ type LogLevel = "debug" | "info" | "success" | "warn" | "error";
28
+ type ColorScheme = {
29
+ primary: string;
30
+ success: string;
31
+ warn: string;
32
+ caution: string;
33
+ error: string;
34
+ muted: string;
35
+ info: string;
36
+ text: string;
37
+ };
38
+ type LoggerOptions = {
39
+ /** Emoji shown before the prefix. */
40
+ prefixEmoji?: string | null;
41
+ /** Prefix label shown before each message. */
42
+ prefix?: string | null;
43
+ /**
44
+ * Minimum log level to output.
45
+ * @default "debug"
46
+ */
47
+ minLevel?: LogLevel;
48
+ /**
49
+ * Log verbose messages.
50
+ * @default false
51
+ */
52
+ verbose?: boolean;
53
+ /** Color scheme to use. */
54
+ colors?: Partial<ColorScheme>;
55
+ };
56
+ type LoaderOptions = {
57
+ /** Returns the next loader message while the spinner is active. */
58
+ cycle?: () => string;
59
+ /** How often `cycle` should be called in milliseconds. */
60
+ interval?: number;
61
+ };
62
+ declare const DEFAULT_COLORS: ColorScheme;
63
+ /** Strips ANSI escape codes from a string for accurate length measurement */
64
+ declare function stripAnsi(str: string): string;
65
+ /** Timestamped console logger with level filtering, colors, and optional live terminal loaders. */
66
+ declare class Logger {
67
+ readonly options: Required<LoggerOptions> & {
68
+ colors: ColorScheme;
69
+ };
70
+ private activeLoaders;
71
+ private nextLoaderId;
72
+ private renderInterval;
73
+ private frameIndex;
74
+ constructor(options?: LoggerOptions);
75
+ private formatPrefix;
76
+ private formatLevel;
77
+ /** Checks if the given log level should be logged. */
78
+ private shouldLog;
79
+ /** Returns a colored `[HH:mm:ss]` timestamp for log prefixes. */
80
+ timestamp(): string;
81
+ /** Writes a line using the builtin `console`, keeping active TTY loaders pinned below the new output. */
82
+ write(stream: "log" | "warn" | "error", ...data: unknown[]): void;
83
+ /** Logs a message without level filtering. */
84
+ log(...data: unknown[]): void;
85
+ logVerbose(...data: unknown[]): void;
86
+ debug(message: string, ...data: unknown[]): void;
87
+ debugVerbose(message: string, ...data: unknown[]): void;
88
+ info(...data: unknown[]): void;
89
+ infoVerbose(...data: unknown[]): void;
90
+ success(message: string, ...data: unknown[]): void;
91
+ successVerbose(message: string, ...data: unknown[]): void;
92
+ warn(message: string, ...data: unknown[]): void;
93
+ warnVerbose(message: string, ...data: unknown[]): void;
94
+ error(message: string, error?: Error, ...data: unknown[]): void;
95
+ errorVerbose(message: string, error?: Error, ...data: unknown[]): void;
96
+ /** @deprecated */
97
+ table(title: string, data: Record<string, unknown>): void;
98
+ /** @deprecated */
99
+ section(title: string): void;
100
+ /**
101
+ * Starts a loader and returns a `stop` function.
102
+ *
103
+ * In an interactive TTY, loaders render as live spinners pinned below regular logs. In hosted logs, CI,
104
+ * Docker logs, and other non-TTY streams, loaders degrade to normal start/final lines without ANSI cursor codes.
105
+ * Always call `stop()` with `try/finally` so the render loop can be cleaned up.
106
+ *
107
+ * @param message Initial loader message.
108
+ * @param cycleOrOptions Optional message cycle function or options object.
109
+ * @param interval How often to call the cycle function in milliseconds. Defaults to `3000`.
110
+ * @returns A function that stops the loader. Pass `clear: true` to suppress the success line.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * const stop = logger.loader("Loading...")
115
+ * try {
116
+ * await doWork()
117
+ * stop("Done!")
118
+ * } catch (e) {
119
+ * stop("Failed")
120
+ * }
121
+ * ```
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * const stop = logger.loader("Starting...", () => "Still starting...", 1000)
126
+ * ```
127
+ */
128
+ loader(message: string, cycle?: () => string, interval?: number): (finalMessage?: string, clear?: boolean) => void;
129
+ loader(message: string, options?: LoaderOptions): (finalMessage?: string, clear?: boolean) => void;
130
+ /** Sets the minimum log level required for level-filtered messages. */
131
+ setLevel(level: LogLevel): void;
132
+ /** Enables or disables verbose-only log methods. */
133
+ setVerbose(verbose: boolean): void;
134
+ }
135
+
136
+ /** Deep merge objects - mutates target and returns it */
137
+ declare function mergeDeep<T extends object>(target: PartialDeep<T>, ...sources: Array<object | undefined>): T;
138
+
139
+ /** Reads the `package.json` file from the current working directory. */
140
+ declare function getPackageJson(): Record<string, unknown>;
141
+ /** Checks if the process was ran using the `--dev` flag. */
142
+ declare function getDevMode(): boolean;
143
+
144
+ declare function createRandomId(): string;
145
+ declare function createHumanId(): string;
146
+
147
+ export { type ColorScheme, DEFAULT_COLORS, type IndexFn, type LoaderOptions, type LogLevel, Logger, type LoggerOptions, type PartialDeep, VimcordError, createHumanId, createRandomId, forceArray, getDevMode, getPackageJson, getProcessDir, importModulesFromDir, isTSOrJS, mergeDeep, stripAnsi };
package/dist/index.js ADDED
@@ -0,0 +1,405 @@
1
+ // src/utils/VimcordError.ts
2
+ var VimcordError = class extends Error {
3
+ constructor(message, code) {
4
+ super(message);
5
+ this.code = code;
6
+ this.name = "VimcordError";
7
+ }
8
+ code;
9
+ };
10
+
11
+ // src/utils/arr.ts
12
+ function forceArray(value) {
13
+ return Array.isArray(value) ? value : [value];
14
+ }
15
+
16
+ // src/utils/import.ts
17
+ import { existsSync, readdirSync } from "fs";
18
+ import path from "path";
19
+ import { pathToFileURL } from "url";
20
+ function getProcessDir() {
21
+ const mainPath = process.argv[1];
22
+ if (!mainPath) return "";
23
+ return path.dirname(mainPath);
24
+ }
25
+ function isTSOrJS(filename, suffix) {
26
+ if (!suffix) return filename.endsWith(".ts") || filename.endsWith(".js");
27
+ if (Array.isArray(suffix)) {
28
+ return suffix.some((s) => filename.endsWith(`${s}.ts`) || filename.endsWith(`${s}.js`));
29
+ } else {
30
+ return filename.endsWith(`${suffix}.ts`) || filename.endsWith(`${suffix}.js`);
31
+ }
32
+ }
33
+ async function importModulesFromDir(dir, suffix) {
34
+ const cwd = getProcessDir();
35
+ const MODULE_RELATIVE_PATH = path.join(cwd, dir);
36
+ const MODULE_LOG_PATH = dir;
37
+ const files = (() => {
38
+ if (!existsSync(MODULE_RELATIVE_PATH)) return [];
39
+ const walk = (targetDir, base = "") => {
40
+ return readdirSync(targetDir, { withFileTypes: true }).flatMap((entry) => {
41
+ const relativePath = base ? path.join(base, entry.name) : entry.name;
42
+ const fullPath = path.join(targetDir, entry.name);
43
+ if (entry.isDirectory()) return walk(fullPath, relativePath);
44
+ if (entry.isFile()) return [relativePath];
45
+ return [];
46
+ });
47
+ };
48
+ return walk(MODULE_RELATIVE_PATH);
49
+ })().filter((filename) => isTSOrJS(filename, suffix));
50
+ if (!files.length) return [];
51
+ const modules = await Promise.all(
52
+ files.map(async (fn) => {
53
+ const modulePath = path.join(MODULE_RELATIVE_PATH, fn);
54
+ const logPath = `./${path.join(MODULE_LOG_PATH, fn)}`;
55
+ try {
56
+ const moduleUrl = pathToFileURL(modulePath);
57
+ moduleUrl.searchParams.set("updated", Date.now().toString());
58
+ const importedModule = await import(moduleUrl.href);
59
+ return { module: importedModule, path: logPath };
60
+ } catch (err) {
61
+ console.warn(`Failed to import module at '${logPath}'`, err);
62
+ return null;
63
+ }
64
+ })
65
+ );
66
+ const filteredModules = modules.filter((m) => Boolean(m));
67
+ if (!filteredModules.length) {
68
+ console.warn(`No valid modules were found in directory '${dir}'`);
69
+ }
70
+ return filteredModules;
71
+ }
72
+
73
+ // src/utils/Logger.ts
74
+ import ansis from "ansis";
75
+ import { spinners } from "unicode-animations";
76
+ var LEVEL_PRIORITY = {
77
+ debug: 0,
78
+ info: 1,
79
+ success: 2,
80
+ warn: 3,
81
+ error: 4
82
+ };
83
+ var DEFAULT_COLORS = {
84
+ primary: "#5865F2",
85
+ success: "#57F287",
86
+ warn: "#FEE75C",
87
+ caution: "#FF9D00",
88
+ error: "#ED4245",
89
+ muted: "#747F8D",
90
+ info: "#87CEEB",
91
+ text: "#FFFFFF"
92
+ };
93
+ var SPINNER_FRAMES = spinners.breathe.frames.map((f) => ansis.hex(DEFAULT_COLORS.muted)(f));
94
+ var SPINNER_INTERVAL = spinners.breathe.interval;
95
+ var DEFAULT_LOADER_CYCLE_INTERVAL_MS = 3e3;
96
+ function stripAnsi(str) {
97
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
98
+ }
99
+ function padVisible(str, length) {
100
+ const visible = stripAnsi(str).length;
101
+ return str + " ".repeat(Math.max(0, length - visible));
102
+ }
103
+ var Logger = class {
104
+ options;
105
+ activeLoaders = /* @__PURE__ */ new Map();
106
+ nextLoaderId = 0;
107
+ renderInterval = null;
108
+ frameIndex = 0;
109
+ constructor(options = {}) {
110
+ this.options = {
111
+ prefix: null,
112
+ prefixEmoji: null,
113
+ minLevel: "debug",
114
+ verbose: false,
115
+ ...options,
116
+ colors: { ...DEFAULT_COLORS, ...options.colors }
117
+ };
118
+ }
119
+ formatPrefix() {
120
+ const { prefixEmoji, prefix, colors } = this.options;
121
+ if (!prefix) return "";
122
+ const emoji = prefixEmoji ? `${prefixEmoji} ` : "";
123
+ return ansis.bold.hex(colors.primary)(`${emoji}${prefix}`);
124
+ }
125
+ formatLevel(level) {
126
+ const { colors } = this.options;
127
+ switch (level) {
128
+ case "debug":
129
+ return ansis.hex(colors.muted)("DEBUG");
130
+ case "info":
131
+ return ansis.hex(colors.info)("INFO");
132
+ case "success":
133
+ return ansis.bold.hex(colors.success)("\u2713 SUCCESS");
134
+ case "warn":
135
+ return ansis.bold.hex(colors.warn)("\u26A0 WARN");
136
+ case "error":
137
+ return ansis.bold.hex(colors.error)("\u2715 ERROR");
138
+ }
139
+ }
140
+ /** Checks if the given log level should be logged. */
141
+ shouldLog(level) {
142
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.options.minLevel];
143
+ }
144
+ /** Returns a colored `[HH:mm:ss]` timestamp for log prefixes. */
145
+ timestamp() {
146
+ const { colors } = this.options;
147
+ const now = /* @__PURE__ */ new Date();
148
+ const time = now.toLocaleTimeString("en-US", {
149
+ hour12: false,
150
+ hour: "2-digit",
151
+ minute: "2-digit",
152
+ second: "2-digit"
153
+ });
154
+ return ansis.hex(colors.muted)(`[${time}]`);
155
+ }
156
+ /** Writes a line using the builtin `console`, keeping active TTY loaders pinned below the new output. */
157
+ write(stream, ...data) {
158
+ const loaderCount = this.activeLoaders.size;
159
+ const shouldRedrawLoaders = process.stdout.isTTY === true && loaderCount > 0;
160
+ if (shouldRedrawLoaders) process.stdout.write(`\x1B[${loaderCount}A\x1B[J`);
161
+ console[stream](...data);
162
+ if (!shouldRedrawLoaders) return;
163
+ const now = Date.now();
164
+ const frame = SPINNER_FRAMES[this.frameIndex];
165
+ for (const [, loader] of this.activeLoaders) {
166
+ if (loader.cycle && now - loader.lastCycleAt >= loader.cycleInterval) {
167
+ loader.message = loader.cycle();
168
+ loader.lastCycleAt = now;
169
+ }
170
+ process.stdout.write(`${this.timestamp()} ${this.formatPrefix()} ${frame} ${loader.message}
171
+ `);
172
+ }
173
+ }
174
+ /** Logs a message without level filtering. */
175
+ log(...data) {
176
+ this.write("log", this.timestamp(), this.formatPrefix(), ...data);
177
+ }
178
+ logVerbose(...data) {
179
+ if (!this.options.verbose) return;
180
+ this.log(...data);
181
+ }
182
+ debug(message, ...data) {
183
+ if (!this.shouldLog("debug")) return;
184
+ this.write("log", this.timestamp(), this.formatPrefix(), this.formatLevel("debug"), ansis.dim(message), ...data);
185
+ }
186
+ debugVerbose(message, ...data) {
187
+ if (!this.options.verbose) return;
188
+ this.debug(message, ...data);
189
+ }
190
+ info(...data) {
191
+ if (!this.shouldLog("info")) return;
192
+ this.write("log", this.timestamp(), this.formatPrefix(), this.formatLevel("info"), ...data);
193
+ }
194
+ infoVerbose(...data) {
195
+ if (!this.options.verbose) return;
196
+ this.info(...data);
197
+ }
198
+ success(message, ...data) {
199
+ if (!this.shouldLog("success")) return;
200
+ this.write(
201
+ "log",
202
+ this.timestamp(),
203
+ this.formatPrefix(),
204
+ this.formatLevel("success"),
205
+ ansis.hex(this.options.colors.success)(message),
206
+ ...data
207
+ );
208
+ }
209
+ successVerbose(message, ...data) {
210
+ if (!this.options.verbose) return;
211
+ this.success(message, ...data);
212
+ }
213
+ warn(message, ...data) {
214
+ if (!this.shouldLog("warn")) return;
215
+ this.write(
216
+ "warn",
217
+ this.timestamp(),
218
+ this.formatPrefix(),
219
+ this.formatLevel("warn"),
220
+ ansis.hex(this.options.colors.warn)(message),
221
+ ...data
222
+ );
223
+ }
224
+ warnVerbose(message, ...data) {
225
+ if (!this.options.verbose) return;
226
+ this.warn(message, ...data);
227
+ }
228
+ error(message, error, ...data) {
229
+ if (!this.shouldLog("error")) return;
230
+ this.write(
231
+ "error",
232
+ this.timestamp(),
233
+ this.formatPrefix(),
234
+ this.formatLevel("error"),
235
+ ansis.hex(this.options.colors.error)(message),
236
+ ...data
237
+ );
238
+ if (error?.stack) {
239
+ this.write("error", ansis.dim(error.stack));
240
+ }
241
+ }
242
+ errorVerbose(message, error, ...data) {
243
+ if (!this.options.verbose) return;
244
+ this.error(message, error, ...data);
245
+ }
246
+ // --- Structured Output ---
247
+ /** @deprecated */
248
+ table(title, data) {
249
+ const { colors } = this.options;
250
+ this.write("log", this.timestamp(), this.formatPrefix(), ansis.bold(title));
251
+ for (const [key, value] of Object.entries(data)) {
252
+ const formattedKey = padVisible(ansis.hex(colors.warn)(` ${key}`), 25);
253
+ const formattedValue = ansis.hex(colors.muted)(String(value));
254
+ this.write("log", `${formattedKey} ${formattedValue}`);
255
+ }
256
+ }
257
+ // TODO: Make this prettier, replace it with `header` using the textThroughLine helper
258
+ /** @deprecated */
259
+ section(title) {
260
+ const { colors } = this.options;
261
+ const titleVisible = stripAnsi(title);
262
+ const width = Math.max(30, titleVisible.length + 4);
263
+ const line = "\u2500".repeat(width);
264
+ const paddedTitle = padVisible(ansis.bold.hex(colors.text)(title), width);
265
+ this.write("log", ansis.hex(colors.muted)(`
266
+ \u250C\u2500${line}\u2500\u2510`));
267
+ this.write("log", ansis.hex(colors.muted)("\u2502 ") + paddedTitle + ansis.hex(colors.muted)(" \u2502"));
268
+ this.write("log", ansis.hex(colors.muted)(`\u2514\u2500${line}\u2500\u2518`));
269
+ }
270
+ loader(message, cycleOrOptions, interval = DEFAULT_LOADER_CYCLE_INTERVAL_MS) {
271
+ const { colors } = this.options;
272
+ const cycle = typeof cycleOrOptions === "function" ? cycleOrOptions : cycleOrOptions?.cycle;
273
+ const cycleInterval = typeof cycleOrOptions === "function" ? interval : cycleOrOptions?.interval ?? interval;
274
+ let stopped = false;
275
+ const id = this.nextLoaderId++;
276
+ const prefix = `${this.timestamp()} ${this.formatPrefix()}`;
277
+ this.activeLoaders.set(id, { cycle, cycleInterval, lastCycleAt: Date.now(), message });
278
+ if (process.stdout.isTTY !== true) {
279
+ this.write("log", prefix, message);
280
+ } else {
281
+ process.stdout.write(`${prefix} ${SPINNER_FRAMES[0]} ${message}
282
+ `);
283
+ if (!this.renderInterval) {
284
+ this.renderInterval = setInterval(() => {
285
+ const loaderCount = this.activeLoaders.size;
286
+ if (!loaderCount) return;
287
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
288
+ process.stdout.write(`\x1B[${loaderCount}A\x1B[J`);
289
+ const now = Date.now();
290
+ const frame = SPINNER_FRAMES[this.frameIndex];
291
+ for (const [, loader] of this.activeLoaders) {
292
+ if (loader.cycle && now - loader.lastCycleAt >= loader.cycleInterval) {
293
+ loader.message = loader.cycle();
294
+ loader.lastCycleAt = now;
295
+ }
296
+ process.stdout.write(`${this.timestamp()} ${this.formatPrefix()} ${frame} ${loader.message}
297
+ `);
298
+ }
299
+ }, SPINNER_INTERVAL);
300
+ }
301
+ }
302
+ return (finalMessage, clear = false) => {
303
+ if (stopped) return;
304
+ stopped = true;
305
+ this.activeLoaders.delete(id);
306
+ if (this.activeLoaders.size === 0 && this.renderInterval) {
307
+ clearInterval(this.renderInterval);
308
+ this.renderInterval = null;
309
+ this.frameIndex = 0;
310
+ }
311
+ if (process.stdout.isTTY !== true) {
312
+ if (clear) {
313
+ if (finalMessage) this.write("log", finalMessage);
314
+ return;
315
+ }
316
+ this.write(
317
+ "log",
318
+ `${this.timestamp()} ${this.formatPrefix()}`,
319
+ ansis.hex(colors.success)("\u2713"),
320
+ finalMessage ?? message
321
+ );
322
+ return;
323
+ }
324
+ process.stdout.write(`\x1B[${this.activeLoaders.size + 1}A\x1B[J`);
325
+ if (clear) {
326
+ if (finalMessage) process.stdout.write(`${finalMessage}
327
+ `);
328
+ } else {
329
+ const check = ansis.hex(colors.success)("\u2713");
330
+ process.stdout.write(`${this.timestamp()} ${this.formatPrefix()} ${check} ${finalMessage ?? message}
331
+ `);
332
+ }
333
+ const frame = SPINNER_FRAMES[this.frameIndex];
334
+ for (const [, loader] of this.activeLoaders) {
335
+ process.stdout.write(`${this.timestamp()} ${this.formatPrefix()} ${frame} ${loader.message}
336
+ `);
337
+ }
338
+ };
339
+ }
340
+ /** Sets the minimum log level required for level-filtered messages. */
341
+ setLevel(level) {
342
+ this.options.minLevel = level;
343
+ }
344
+ /** Enables or disables verbose-only log methods. */
345
+ setVerbose(verbose) {
346
+ this.options.verbose = verbose;
347
+ }
348
+ };
349
+
350
+ // src/utils/obj.ts
351
+ function isPlainObject(value) {
352
+ if (typeof value !== "object" || value === null) return false;
353
+ if (Array.isArray(value)) return false;
354
+ return Object.prototype.toString.call(value) === "[object Object]";
355
+ }
356
+ function mergeDeep(target, ...sources) {
357
+ for (const source of sources) {
358
+ if (!source) continue;
359
+ for (const key in source) {
360
+ if (!Object.prototype.hasOwnProperty.call(source, key)) continue;
361
+ const sourceValue = source[key];
362
+ const targetValue = target[key];
363
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
364
+ mergeDeep(targetValue, sourceValue);
365
+ } else if (sourceValue !== void 0) {
366
+ target[key] = sourceValue;
367
+ }
368
+ }
369
+ }
370
+ return target;
371
+ }
372
+
373
+ // src/utils/process.ts
374
+ import { readFileSync } from "fs";
375
+ import { join } from "path";
376
+ function getPackageJson() {
377
+ return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8"));
378
+ }
379
+ function getDevMode() {
380
+ return process.argv.includes("--dev");
381
+ }
382
+
383
+ // src/utils/str.ts
384
+ import { humanId } from "human-id";
385
+ function createRandomId() {
386
+ return `v-${Math.random().toString(36).split(".")[1]}`;
387
+ }
388
+ function createHumanId() {
389
+ return humanId({ separator: "-", capitalize: false });
390
+ }
391
+ export {
392
+ DEFAULT_COLORS,
393
+ Logger,
394
+ VimcordError,
395
+ createHumanId,
396
+ createRandomId,
397
+ forceArray,
398
+ getDevMode,
399
+ getPackageJson,
400
+ getProcessDir,
401
+ importModulesFromDir,
402
+ isTSOrJS,
403
+ mergeDeep,
404
+ stripAnsi
405
+ };
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@vimcord/internal",
3
+ "version": "0.1.0",
4
+ "description": "Shared internal types and utilities for the vimcord framework.",
5
+ "author": "xsqu1znt",
6
+ "license": "MIT",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "type": "module",
18
+ "dependencies": {
19
+ "ansis": "^4.3.0",
20
+ "human-id": "^4.1.3",
21
+ "unicode-animations": "^1.0.3"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^25.8.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "check": "tsc --noEmit",
29
+ "format": "prettier --write \"./**/*.{ts,json}\""
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./types/helpers.js";
2
+ export * from "./utils/VimcordError.js";
3
+ export * from "./utils/arr.js";
4
+ export * from "./utils/import.js";
5
+ export * from "./utils/Logger.js";
6
+ export * from "./utils/obj.js";
7
+ export * from "./utils/process.js";
8
+ export * from "./utils/str.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Deep partial type utility - recursively makes all properties optional.
3
+ * Preserves function types to maintain their signatures.
4
+ */
5
+ export type PartialDeep<T> = T extends (...args: unknown[]) => unknown
6
+ ? T
7
+ : T extends object
8
+ ? T extends ReadonlyArray<infer U>
9
+ ? ReadonlyArray<PartialDeep<U>>
10
+ : T extends Array<infer U>
11
+ ? Array<PartialDeep<U>>
12
+ : {
13
+ [K in keyof T]?: PartialDeep<T[K]>;
14
+ }
15
+ : T;
16
+
17
+ /** A function used for indexing something. */
18
+ export type IndexFn<T> = (module: T) => string | string[] | undefined;