@utilix-tech/sdk 0.51.0 → 0.53.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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  Full release notes for all Utilix surfaces (browser tools, REST API, SDKs, MCP server) live at [utilix.tech/changelog](https://utilix.tech/changelog). This file tracks just this package.
4
4
 
5
+ ## 0.53.0
6
+ - Add `calculateCreditUtilization` (credit-utilization-calculator): compute per-account and overall credit utilization (balance / limit) from one or more credit accounts, plus a qualitative tier matching common credit-scoring guidance.
7
+ - Add `analyzeCaptionSpeed` (caption-reading-speed-checker): parse an SRT or WebVTT file and report the words-per-minute reading speed of every cue, flagging ones that exceed common broadcast accessibility guidelines.
8
+
9
+ ## 0.52.0
10
+ - Add `validateEditorConfig` (editorconfig-validator): parse an .editorconfig file into its root flag and glob sections, and flag structural mistakes against the EditorConfig spec.
11
+ - Add `validateTsconfig` (tsconfig-validator): parse a tsconfig.json file as JSONC and flag unknown keys, unknown compilerOptions, and type mismatches against a curated list of known options.
12
+
5
13
  ## 0.51.0
6
14
  - Add `calculateGpa` (gpa-calculator): compute a weighted GPA from a list of courses, each with a letter grade and credit hours, using the standard 4.0-capped scale.
7
15
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **642 developer utility functions for Node.js: runs locally, no API key required.**
3
+ **646 developer utility functions for Node.js: runs locally, no API key required.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@utilix-tech/sdk?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@utilix-tech/sdk)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/@utilix-tech/sdk?style=flat-square)](https://www.npmjs.com/package/@utilix-tech/sdk)
@@ -204,7 +204,7 @@ analyzeKeywordDensity("machine learning models power machine learning systems");
204
204
  import { parseCsv, csvToJson, validateYaml, resolveYamlAnchors, tomlToJson, jsonToToml,
205
205
  formatXml, xmlToJson, jsonToXml, parseIni,
206
206
  validateNdjson, formatNdjson, ndjsonToJsonArray, diffEnvExample,
207
- parseSubtitles, convertSubtitles, shiftSubtitles, parseFrontMatter,
207
+ parseSubtitles, convertSubtitles, shiftSubtitles, analyzeCaptionSpeed, parseFrontMatter,
208
208
  generateMarkdownToc, csvToSqlInsert, findCsvDuplicateRows, calculateMarkdownTaskListProgress } from "@utilix-tech/sdk/data";
209
209
 
210
210
  // CSV
@@ -242,6 +242,10 @@ convertSubtitles("1\n00:00:01,000 --> 00:00:04,000\nHello there.\n", "vtt");
242
242
  shiftSubtitles("1\n00:00:01,000 --> 00:00:04,000\nHi\n", -2.5);
243
243
  // shifts every cue 2.5s earlier, clamping anything below zero to 00:00:00
244
244
 
245
+ // Caption reading speed: flag cues faster than ~180 WPM / ~20 chars/sec (both adjustable)
246
+ analyzeCaptionSpeed("1\n00:00:01,000 --> 00:00:02,000\nThis line is read far too fast for anyone to follow.\n");
247
+ // { cueCount: 1, averageWpm: 660, flaggedCount: 1, cues: [{ wpm: 660, exceedsWpm: true, ... }], ... }
248
+
245
249
  // Markdown front matter: split a document into its YAML front matter and body
246
250
  parseFrontMatter("---\ntitle: Hello World\ntags: [foo, bar]\n---\n# Body");
247
251
  // { frontMatter: { title: "Hello World", tags: ["foo", "bar"] }, body: "# Body", hasFrontMatter: true, ... }
@@ -322,7 +326,7 @@ formatIso8601Duration(90061); // { iso: "P1DT1H1M1S", ... }
322
326
  ### `/units`: Conversions
323
327
 
324
328
  ```ts
325
- import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue, calculatePercentageChange, convertAprApy, calculateMarginMarkup, calculateDiscount, applySuccessiveDiscounts, calculateBreakEven, calculateVideoBitrate, calculateRuleOf72, calculateEmi, calculateRoi, compareUnitPrices, calculatePaybackPeriod, calculateCdMaturity, calculateDepreciationSchedule, calculateCashRounding, calculateMovingAverages, calculateGpa } from "@utilix-tech/sdk/units";
329
+ import { convertBytes, pxToAll, convert, formatValue, validateCardNumber, validateIban, calculateLoan, validateBarcode, calculateTax, calculateCompoundInterest, validateIsbn, calculateTipSplit, calculateSimpleInterest, calculateTimeValue, calculatePercentageChange, convertAprApy, calculateMarginMarkup, calculateDiscount, applySuccessiveDiscounts, calculateBreakEven, calculateVideoBitrate, calculateRuleOf72, calculateEmi, calculateRoi, compareUnitPrices, calculatePaybackPeriod, calculateCdMaturity, calculateDepreciationSchedule, calculateCashRounding, calculateMovingAverages, calculateGpa, calculateCreditUtilization } from "@utilix-tech/sdk/units";
326
330
 
327
331
  // File sizes
328
332
  convertBytes(1073741824, "GB"); // 1
@@ -456,6 +460,10 @@ calculateMovingAverages([102, 104, 101, 108, 112], 3);
456
460
  // Weighted GPA from a list of courses (letter grade + credit hours)
457
461
  calculateGpa([{ name: "Calculus", grade: "A", credits: 4 }, { name: "History", grade: "B+", credits: 3 }]);
458
462
  // { gpa: 3.7, totalCredits: 7, totalQualityPoints: 25.9, courses: [...] }
463
+
464
+ // Per-account and overall credit utilization, plus a qualitative tier
465
+ calculateCreditUtilization([{ label: "Visa", balance: 500, limit: 5000 }, { label: "Amex", balance: 1200, limit: 3000 }]);
466
+ // { overallUtilizationPercent: 21.25, overallTier: "good", totalBalance: 1700, totalLimit: 8000, accounts: [...] }
459
467
  ```
460
468
 
461
469
  ---
@@ -623,7 +631,8 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
623
631
  ```ts
624
632
  import { formatSql, minifySql, testRegex, formatHtml, formatGql,
625
633
  detectDialect, splitStatements, lookupMime, parseGithubUrl,
626
- detectRedos, testGitignorePatterns } from "@utilix-tech/sdk/code";
634
+ detectRedos, testGitignorePatterns, validateEditorConfig,
635
+ validateTsconfig } from "@utilix-tech/sdk/code";
627
636
 
628
637
  // SQL
629
638
  formatSql("SELECT id,name FROM users WHERE active=1 ORDER BY name");
@@ -663,6 +672,14 @@ detectRedos("(a+)+");
663
672
  // Test paths against a .gitignore file's patterns
664
673
  testGitignorePatterns("node_modules/\n*.log", ["node_modules/foo.js", "debug.log", "src/index.js"]);
665
674
  // [{ path: "node_modules/foo.js", ignored: true, matchedPattern: "node_modules/", matchedLine: 1 }, ...]
675
+
676
+ // Validate an .editorconfig file
677
+ validateEditorConfig("root = true\n\n[*]\nindent_style = space\nindent_size = 2\n");
678
+ // { root: true, sections: [{ glob: "*", properties: [...] }], issues: [], valid: true }
679
+
680
+ // Validate a tsconfig.json file (JSONC comments and trailing commas are stripped first)
681
+ validateTsconfig(JSON.stringify({ compilerOptions: { target: "ES2020", strict: true } }));
682
+ // { parsed: true, topLevelKeys: ["compilerOptions"], compilerOptionKeys: ["target", "strict"], issues: [], valid: true }
666
683
  ```
667
684
 
668
685
  ---
@@ -5,6 +5,7 @@ import { parse, stringify } from 'smol-toml';
5
5
  // src/tools/data.ts
6
6
  var data_exports = {};
7
7
  __export(data_exports, {
8
+ analyzeCaptionSpeed: () => analyzeCaptionSpeed,
8
9
  calculateMarkdownTaskListProgress: () => calculateMarkdownTaskListProgress,
9
10
  convertSubtitles: () => convertSubtitles,
10
11
  csvToJson: () => csvToJson,
@@ -1340,6 +1341,53 @@ function shiftSubtitles(input, offsetSeconds, target) {
1340
1341
  warnings
1341
1342
  };
1342
1343
  }
1344
+ function analyzeCaptionSpeed(input, wpmThreshold = 180, cpsThreshold = 20) {
1345
+ if (!Number.isFinite(wpmThreshold) || wpmThreshold <= 0) {
1346
+ return { error: "wpmThreshold must be a finite number greater than zero" };
1347
+ }
1348
+ if (!Number.isFinite(cpsThreshold) || cpsThreshold <= 0) {
1349
+ return { error: "cpsThreshold must be a finite number greater than zero" };
1350
+ }
1351
+ const parsed = parseSubtitles(input);
1352
+ if ("error" in parsed) return parsed;
1353
+ const round1 = (n) => Math.round(n * 10) / 10;
1354
+ const cues = [];
1355
+ for (const cue of parsed.cues) {
1356
+ const text = cue.text.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
1357
+ const durationSeconds = cue.end - cue.start;
1358
+ if (text.length === 0 || durationSeconds <= 0) continue;
1359
+ const wordCount = text.split(" ").filter(Boolean).length;
1360
+ const charCount = text.length;
1361
+ const wpm = wordCount / durationSeconds * 60;
1362
+ const charsPerSecond = charCount / durationSeconds;
1363
+ cues.push({
1364
+ index: cues.length + 1,
1365
+ start: round1(cue.start),
1366
+ end: round1(cue.end),
1367
+ text,
1368
+ durationSeconds: round1(durationSeconds),
1369
+ wordCount,
1370
+ charCount,
1371
+ wpm: round1(wpm),
1372
+ charsPerSecond: round1(charsPerSecond),
1373
+ exceedsWpm: wpm > wpmThreshold,
1374
+ exceedsCps: charsPerSecond > cpsThreshold
1375
+ });
1376
+ }
1377
+ if (cues.length === 0) {
1378
+ return { error: "No cues with both text and a positive duration were found" };
1379
+ }
1380
+ const wpmValues = cues.map((c) => c.wpm);
1381
+ return {
1382
+ cueCount: cues.length,
1383
+ cues,
1384
+ averageWpm: round1(wpmValues.reduce((a, b) => a + b, 0) / wpmValues.length),
1385
+ maxWpm: round1(Math.max(...wpmValues)),
1386
+ flaggedCount: cues.filter((c) => c.exceedsWpm || c.exceedsCps).length,
1387
+ wpmThreshold,
1388
+ cpsThreshold
1389
+ };
1390
+ }
1343
1391
  function parseFrontMatterScalar(value) {
1344
1392
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1345
1393
  return value.slice(1, -1);
@@ -1673,4 +1721,4 @@ function calculateMarkdownTaskListProgress(markdown) {
1673
1721
  return { totalItems, completedItems, percentComplete, sections, items };
1674
1722
  }
1675
1723
 
1676
- export { calculateMarkdownTaskListProgress, convertSubtitles, csvToJson, csvToSqlInsert, data_exports, detectDelimiter, detectSubtitleFormat, diffEnvExample, envToExport, envToJson, findCsvDuplicateRows, formatNdjson, formatSubtitleTimestamp, formatToml, formatXml, generateMarkdownToc, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonArrayToNdjson, jsonToIni, jsonToToml, jsonToXml, minifyNdjson, minifyToml, minifyXml, ndjsonToJsonArray, parseCsv, parseEnv, parseFrontMatter, parseIni, parseSubtitleTimestamp, parseSubtitles, parseXmlToObject, resolveYamlAnchors, serializeSubtitles, shiftSubtitles, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateNdjson, validateToml, validateXml, validateYaml, xmlToJson };
1724
+ export { analyzeCaptionSpeed, calculateMarkdownTaskListProgress, convertSubtitles, csvToJson, csvToSqlInsert, data_exports, detectDelimiter, detectSubtitleFormat, diffEnvExample, envToExport, envToJson, findCsvDuplicateRows, formatNdjson, formatSubtitleTimestamp, formatToml, formatXml, generateMarkdownToc, getSection, getSectionNames, getTomlStats, getXmlStats, getYamlStats, iniToJson, jsonArrayToNdjson, jsonToIni, jsonToToml, jsonToXml, minifyNdjson, minifyToml, minifyXml, ndjsonToJsonArray, parseCsv, parseEnv, parseFrontMatter, parseIni, parseSubtitleTimestamp, parseSubtitles, parseXmlToObject, resolveYamlAnchors, serializeSubtitles, shiftSubtitles, stringifyIni, toCsvString, tomlToJson, validateEnvKey, validateIni, validateNdjson, validateToml, validateXml, validateYaml, xmlToJson };
@@ -55,7 +55,9 @@ __export(code_exports, {
55
55
  testGitignorePatterns: () => testGitignorePatterns,
56
56
  testPattern: () => testPattern,
57
57
  testRegex: () => testRegex,
58
- validateEnvKey: () => validateEnvKey
58
+ validateEditorConfig: () => validateEditorConfig,
59
+ validateEnvKey: () => validateEnvKey,
60
+ validateTsconfig: () => validateTsconfig
59
61
  });
60
62
  var ALL_FLAGS = ["g", "i", "m", "s", "u"];
61
63
  function testRegex(pattern, flags, input) {
@@ -2940,5 +2942,339 @@ function testGitignorePatterns(gitignoreContent, paths) {
2940
2942
  return { path, ignored, matchedPattern, matchedLine };
2941
2943
  });
2942
2944
  }
2945
+ var EDITORCONFIG_ENUM_PROPERTIES = {
2946
+ indent_style: ["tab", "space"],
2947
+ end_of_line: ["lf", "cr", "crlf"],
2948
+ charset: ["latin1", "utf-8", "utf-8-bom", "utf-16be", "utf-16le"],
2949
+ trim_trailing_whitespace: ["true", "false"],
2950
+ insert_final_newline: ["true", "false"]
2951
+ };
2952
+ var EDITORCONFIG_KNOWN_PROPERTIES = /* @__PURE__ */ new Set([
2953
+ "indent_style",
2954
+ "indent_size",
2955
+ "tab_width",
2956
+ "end_of_line",
2957
+ "charset",
2958
+ "trim_trailing_whitespace",
2959
+ "insert_final_newline",
2960
+ "max_line_length",
2961
+ "root"
2962
+ ]);
2963
+ var POSITIVE_INT_RE = /^[1-9][0-9]*$/;
2964
+ function validateEditorConfig(text) {
2965
+ if (!text || !text.trim()) return { error: "Input is empty" };
2966
+ const lines = text.split("\n");
2967
+ const issues = [];
2968
+ const sections = [];
2969
+ let root = false;
2970
+ let rootSeen = false;
2971
+ let currentSection = null;
2972
+ for (let i = 0; i < lines.length; i++) {
2973
+ const lineNum = i + 1;
2974
+ const trimmed = lines[i].trim();
2975
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";")) continue;
2976
+ const sectionMatch = trimmed.match(/^\[(.*)\]$/);
2977
+ if (sectionMatch) {
2978
+ const glob = sectionMatch[1];
2979
+ if (!glob) {
2980
+ issues.push({ severity: "warning", line: lineNum, message: 'Section header is empty ("[]" matches no files)' });
2981
+ }
2982
+ if (sections.some((s) => s.glob === glob)) {
2983
+ issues.push({ severity: "warning", line: lineNum, message: `Duplicate section "[${glob}]" (an earlier section already uses this exact glob)` });
2984
+ }
2985
+ currentSection = { glob, line: lineNum, properties: [] };
2986
+ sections.push(currentSection);
2987
+ continue;
2988
+ }
2989
+ const eqIdx = trimmed.indexOf("=");
2990
+ if (eqIdx === -1) {
2991
+ issues.push({ severity: "error", line: lineNum, message: `Malformed line (missing '='): "${trimmed}"` });
2992
+ continue;
2993
+ }
2994
+ const rawKey = trimmed.slice(0, eqIdx).trim();
2995
+ const key = rawKey.toLowerCase();
2996
+ const rawValue = trimmed.slice(eqIdx + 1).trim();
2997
+ const normalizedValue = rawValue.toLowerCase();
2998
+ if (!rawKey) {
2999
+ issues.push({ severity: "error", line: lineNum, message: "Property is missing a name" });
3000
+ continue;
3001
+ }
3002
+ if (key === "root") {
3003
+ if (sections.length > 0) {
3004
+ issues.push({ severity: "error", line: lineNum, message: `"root" must appear before any section, in the file's preamble` });
3005
+ } else if (rootSeen) {
3006
+ issues.push({ severity: "warning", line: lineNum, message: 'Duplicate "root" declaration' });
3007
+ } else if (normalizedValue !== "true" && normalizedValue !== "false") {
3008
+ issues.push({ severity: "error", line: lineNum, message: `"root" must be "true" or "false", got "${rawValue}"` });
3009
+ } else {
3010
+ root = normalizedValue === "true";
3011
+ }
3012
+ rootSeen = true;
3013
+ continue;
3014
+ }
3015
+ if (!currentSection) {
3016
+ issues.push({ severity: "error", line: lineNum, message: `Property "${rawKey}" found before any section header` });
3017
+ continue;
3018
+ }
3019
+ if (currentSection.properties.some((p) => p.key === key)) {
3020
+ issues.push({ severity: "warning", line: lineNum, message: `Duplicate property "${key}" in section "[${currentSection.glob}]"` });
3021
+ }
3022
+ currentSection.properties.push({ key, value: rawValue, line: lineNum });
3023
+ if (!EDITORCONFIG_KNOWN_PROPERTIES.has(key)) {
3024
+ issues.push({ severity: "warning", line: lineNum, message: `Unknown property "${key}"` });
3025
+ continue;
3026
+ }
3027
+ if (key in EDITORCONFIG_ENUM_PROPERTIES) {
3028
+ if (!EDITORCONFIG_ENUM_PROPERTIES[key].includes(normalizedValue)) {
3029
+ issues.push({ severity: "error", line: lineNum, message: `"${key}" must be one of ${EDITORCONFIG_ENUM_PROPERTIES[key].join(", ")}, got "${rawValue}"` });
3030
+ }
3031
+ } else if (key === "indent_size") {
3032
+ if (normalizedValue !== "tab" && !POSITIVE_INT_RE.test(rawValue)) {
3033
+ issues.push({ severity: "error", line: lineNum, message: `"indent_size" must be a positive integer or "tab", got "${rawValue}"` });
3034
+ }
3035
+ } else if (key === "tab_width") {
3036
+ if (!POSITIVE_INT_RE.test(rawValue)) {
3037
+ issues.push({ severity: "error", line: lineNum, message: `"tab_width" must be a positive integer, got "${rawValue}"` });
3038
+ }
3039
+ } else if (key === "max_line_length") {
3040
+ if (normalizedValue !== "off" && !POSITIVE_INT_RE.test(rawValue)) {
3041
+ issues.push({ severity: "error", line: lineNum, message: `"max_line_length" must be a positive integer or "off", got "${rawValue}"` });
3042
+ }
3043
+ }
3044
+ }
3045
+ if (sections.length === 0 && !rootSeen) {
3046
+ issues.push({ severity: "warning", line: 1, message: "File has no section headers and no root declaration" });
3047
+ }
3048
+ const valid = !issues.some((i) => i.severity === "error");
3049
+ return { root, sections, issues, valid };
3050
+ }
3051
+ function stripJsonComments(input) {
3052
+ let out = "";
3053
+ let i = 0;
3054
+ const n = input.length;
3055
+ let inString = false;
3056
+ while (i < n) {
3057
+ const c = input[i];
3058
+ const next = input[i + 1];
3059
+ if (inString) {
3060
+ out += c;
3061
+ if (c === "\\") {
3062
+ out += next ?? "";
3063
+ i += 2;
3064
+ continue;
3065
+ }
3066
+ if (c === '"') inString = false;
3067
+ i++;
3068
+ continue;
3069
+ }
3070
+ if (c === '"') {
3071
+ inString = true;
3072
+ out += c;
3073
+ i++;
3074
+ continue;
3075
+ }
3076
+ if (c === "/" && next === "/") {
3077
+ while (i < n && input[i] !== "\n") i++;
3078
+ continue;
3079
+ }
3080
+ if (c === "/" && next === "*") {
3081
+ i += 2;
3082
+ while (i < n && !(input[i] === "*" && input[i + 1] === "/")) {
3083
+ out += input[i] === "\n" ? "\n" : "";
3084
+ i++;
3085
+ }
3086
+ i += 2;
3087
+ continue;
3088
+ }
3089
+ out += c;
3090
+ i++;
3091
+ }
3092
+ return out;
3093
+ }
3094
+ function stripTrailingCommas(input) {
3095
+ return input.replace(/,(\s*[}\]])/g, "$1");
3096
+ }
3097
+ var TSCONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
3098
+ "compilerOptions",
3099
+ "files",
3100
+ "include",
3101
+ "exclude",
3102
+ "extends",
3103
+ "references",
3104
+ "compileOnSave",
3105
+ "typeAcquisition",
3106
+ "watchOptions",
3107
+ "buildOptions",
3108
+ "$schema"
3109
+ ]);
3110
+ var TSCONFIG_ENUM_OPTIONS = {
3111
+ target: ["es3", "es5", "es6", "es2015", "es2016", "es2017", "es2018", "es2019", "es2020", "es2021", "es2022", "es2023", "esnext"],
3112
+ module: ["none", "commonjs", "amd", "umd", "system", "es6", "es2015", "es2020", "es2022", "esnext", "node16", "nodenext", "node18", "preserve"],
3113
+ moduleResolution: ["classic", "node", "node10", "node16", "nodenext", "bundler"],
3114
+ moduleDetection: ["legacy", "auto", "force"],
3115
+ jsx: ["preserve", "react", "react-jsx", "react-jsxdev", "react-native"],
3116
+ newLine: ["crlf", "lf"],
3117
+ importsNotUsedAsValues: ["remove", "preserve", "error"]
3118
+ };
3119
+ var TSCONFIG_BOOLEAN_OPTIONS = /* @__PURE__ */ new Set([
3120
+ "allowJs",
3121
+ "checkJs",
3122
+ "strict",
3123
+ "noImplicitAny",
3124
+ "strictNullChecks",
3125
+ "strictFunctionTypes",
3126
+ "strictBindCallApply",
3127
+ "strictPropertyInitialization",
3128
+ "noImplicitThis",
3129
+ "useUnknownInCatchVariables",
3130
+ "alwaysStrict",
3131
+ "noUnusedLocals",
3132
+ "noUnusedParameters",
3133
+ "exactOptionalPropertyTypes",
3134
+ "noImplicitReturns",
3135
+ "noFallthroughCasesInSwitch",
3136
+ "noUncheckedIndexedAccess",
3137
+ "noImplicitOverride",
3138
+ "noPropertyAccessFromIndexSignature",
3139
+ "esModuleInterop",
3140
+ "allowSyntheticDefaultImports",
3141
+ "forceConsistentCasingInFileNames",
3142
+ "isolatedModules",
3143
+ "verbatimModuleSyntax",
3144
+ "declaration",
3145
+ "declarationMap",
3146
+ "emitDeclarationOnly",
3147
+ "sourceMap",
3148
+ "inlineSourceMap",
3149
+ "inlineSources",
3150
+ "noEmit",
3151
+ "noEmitOnError",
3152
+ "preserveConstEnums",
3153
+ "removeComments",
3154
+ "skipLibCheck",
3155
+ "composite",
3156
+ "incremental",
3157
+ "experimentalDecorators",
3158
+ "emitDecoratorMetadata",
3159
+ "resolveJsonModule",
3160
+ "allowUmdGlobalAccess",
3161
+ "downlevelIteration",
3162
+ "importHelpers",
3163
+ "preserveSymlinks",
3164
+ "allowUnreachableCode",
3165
+ "allowUnusedLabels",
3166
+ "disableSizeLimit",
3167
+ "noResolve",
3168
+ "skipDefaultLibCheck",
3169
+ "suppressImplicitAnyIndexErrors"
3170
+ ]);
3171
+ var TSCONFIG_STRING_OPTIONS = /* @__PURE__ */ new Set([
3172
+ "outDir",
3173
+ "rootDir",
3174
+ "baseUrl",
3175
+ "outFile",
3176
+ "declarationDir",
3177
+ "tsBuildInfoFile",
3178
+ "jsxFactory",
3179
+ "jsxFragmentFactory",
3180
+ "jsxImportSource"
3181
+ ]);
3182
+ var TSCONFIG_ARRAY_OPTIONS = /* @__PURE__ */ new Set(["lib", "types", "typeRoots", "rootDirs"]);
3183
+ function validateTsconfig(text) {
3184
+ if (!text || !text.trim()) return { error: "Input is empty" };
3185
+ const stripped = stripTrailingCommas(stripJsonComments(text));
3186
+ let parsed;
3187
+ try {
3188
+ parsed = JSON.parse(stripped);
3189
+ } catch (e) {
3190
+ return { error: `Invalid JSON: ${e.message}` };
3191
+ }
3192
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3193
+ return { error: "Root of tsconfig.json must be an object" };
3194
+ }
3195
+ const obj = parsed;
3196
+ const issues = [];
3197
+ const topLevelKeys = Object.keys(obj);
3198
+ for (const key of topLevelKeys) {
3199
+ if (!TSCONFIG_TOP_LEVEL_KEYS.has(key)) {
3200
+ issues.push({ severity: "warning", path: key, message: `Unknown top-level key "${key}"` });
3201
+ }
3202
+ }
3203
+ for (const key of ["files", "include", "exclude"]) {
3204
+ if (key in obj) {
3205
+ const v = obj[key];
3206
+ if (!Array.isArray(v) || !v.every((x) => typeof x === "string")) {
3207
+ issues.push({ severity: "error", path: key, message: `"${key}" must be an array of strings` });
3208
+ }
3209
+ }
3210
+ }
3211
+ if ("extends" in obj) {
3212
+ const v = obj.extends;
3213
+ const validExtends = typeof v === "string" || Array.isArray(v) && v.every((x) => typeof x === "string");
3214
+ if (!validExtends) {
3215
+ issues.push({ severity: "error", path: "extends", message: '"extends" must be a string or an array of strings' });
3216
+ }
3217
+ }
3218
+ if ("references" in obj) {
3219
+ const v = obj.references;
3220
+ const validRefs = Array.isArray(v) && v.every((r) => typeof r === "object" && r !== null && typeof r.path === "string");
3221
+ if (!validRefs) {
3222
+ issues.push({ severity: "error", path: "references", message: '"references" must be an array of objects with a "path" string' });
3223
+ }
3224
+ }
3225
+ let compilerOptionKeys = [];
3226
+ if ("compilerOptions" in obj) {
3227
+ const co = obj.compilerOptions;
3228
+ if (typeof co !== "object" || co === null || Array.isArray(co)) {
3229
+ issues.push({ severity: "error", path: "compilerOptions", message: '"compilerOptions" must be an object' });
3230
+ } else {
3231
+ const coObj = co;
3232
+ compilerOptionKeys = Object.keys(coObj);
3233
+ for (const key of compilerOptionKeys) {
3234
+ const value = coObj[key];
3235
+ const path = `compilerOptions.${key}`;
3236
+ if (key === "paths") {
3237
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3238
+ issues.push({ severity: "error", path, message: '"paths" must be an object mapping patterns to arrays of strings' });
3239
+ } else {
3240
+ for (const [k, v] of Object.entries(value)) {
3241
+ if (!Array.isArray(v) || !v.every((x) => typeof x === "string")) {
3242
+ issues.push({ severity: "error", path: `${path}.${k}`, message: `"paths.${k}" must be an array of strings` });
3243
+ }
3244
+ }
3245
+ }
3246
+ continue;
3247
+ }
3248
+ if (key in TSCONFIG_ENUM_OPTIONS) {
3249
+ if (typeof value !== "string" || !TSCONFIG_ENUM_OPTIONS[key].includes(value.toLowerCase())) {
3250
+ issues.push({ severity: "error", path, message: `"${key}" must be one of ${TSCONFIG_ENUM_OPTIONS[key].join(", ")}, got ${JSON.stringify(value)}` });
3251
+ }
3252
+ continue;
3253
+ }
3254
+ if (TSCONFIG_BOOLEAN_OPTIONS.has(key)) {
3255
+ if (typeof value !== "boolean") {
3256
+ issues.push({ severity: "error", path, message: `"${key}" must be a boolean, got ${JSON.stringify(value)}` });
3257
+ }
3258
+ continue;
3259
+ }
3260
+ if (TSCONFIG_STRING_OPTIONS.has(key)) {
3261
+ if (typeof value !== "string") {
3262
+ issues.push({ severity: "error", path, message: `"${key}" must be a string, got ${JSON.stringify(value)}` });
3263
+ }
3264
+ continue;
3265
+ }
3266
+ if (TSCONFIG_ARRAY_OPTIONS.has(key)) {
3267
+ if (!Array.isArray(value) || !value.every((x) => typeof x === "string")) {
3268
+ issues.push({ severity: "error", path, message: `"${key}" must be an array of strings, got ${JSON.stringify(value)}` });
3269
+ }
3270
+ continue;
3271
+ }
3272
+ issues.push({ severity: "warning", path, message: `Unknown compilerOption "${key}"` });
3273
+ }
3274
+ }
3275
+ }
3276
+ const valid = !issues.some((i) => i.severity === "error");
3277
+ return { parsed: true, topLevelKeys, compilerOptionKeys, issues, valid };
3278
+ }
2943
3279
 
2944
- export { ALL_FLAGS, CHEATSHEET, DEFAULT_COMMIT_TYPES, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lintConventionalCommit, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testGitignorePatterns, testPattern, testRegex, validateEnvKey };
3280
+ export { ALL_FLAGS, CHEATSHEET, DEFAULT_COMMIT_TYPES, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lintConventionalCommit, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testGitignorePatterns, testPattern, testRegex, validateEditorConfig, validateEnvKey, validateTsconfig };
@@ -27,6 +27,7 @@ __export(units_exports, {
27
27
  calculateCdMaturity: () => calculateCdMaturity,
28
28
  calculateChange: () => calculateChange,
29
29
  calculateCompoundInterest: () => calculateCompoundInterest,
30
+ calculateCreditUtilization: () => calculateCreditUtilization,
30
31
  calculateDepreciationSchedule: () => calculateDepreciationSchedule,
31
32
  calculateDiscount: () => calculateDiscount,
32
33
  calculateEmi: () => calculateEmi,
@@ -2190,5 +2191,47 @@ function calculateGpa(courses) {
2190
2191
  gpa: round22(totalQualityPoints / totalCredits)
2191
2192
  };
2192
2193
  }
2194
+ function _classifyUtilization(percent) {
2195
+ if (percent < 10) return "excellent";
2196
+ if (percent < 30) return "good";
2197
+ if (percent < 50) return "fair";
2198
+ return "poor";
2199
+ }
2200
+ function calculateCreditUtilization(accounts) {
2201
+ if (!Array.isArray(accounts) || accounts.length === 0) {
2202
+ return { error: "accounts must be a non-empty array" };
2203
+ }
2204
+ const round22 = (n) => Math.round(n * 100) / 100;
2205
+ let totalBalance = 0;
2206
+ let totalLimit = 0;
2207
+ const results = [];
2208
+ for (let i = 0; i < accounts.length; i++) {
2209
+ const account = accounts[i];
2210
+ if (typeof account.balance !== "number" || !Number.isFinite(account.balance) || account.balance < 0) {
2211
+ return { error: `account ${i + 1}: balance must be a finite, non-negative number` };
2212
+ }
2213
+ if (typeof account.limit !== "number" || !Number.isFinite(account.limit) || account.limit <= 0) {
2214
+ return { error: `account ${i + 1}: limit must be a finite number greater than zero` };
2215
+ }
2216
+ const utilizationPercent = round22(account.balance / account.limit * 100);
2217
+ totalBalance += account.balance;
2218
+ totalLimit += account.limit;
2219
+ results.push({
2220
+ label: account.label?.trim() || `Account ${i + 1}`,
2221
+ balance: account.balance,
2222
+ limit: account.limit,
2223
+ utilizationPercent,
2224
+ tier: _classifyUtilization(utilizationPercent)
2225
+ });
2226
+ }
2227
+ const overallUtilizationPercent = round22(totalBalance / totalLimit * 100);
2228
+ return {
2229
+ accounts: results,
2230
+ totalBalance: round22(totalBalance),
2231
+ totalLimit: round22(totalLimit),
2232
+ overallUtilizationPercent,
2233
+ overallTier: _classifyUtilization(overallUtilizationPercent)
2234
+ };
2235
+ }
2193
2236
 
2194
- export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, GPA_GRADE_POINTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBreakEven, calculateCashBreakdown, calculateCashRounding, calculateCdMaturity, calculateChange, calculateCompoundInterest, calculateDepreciationSchedule, calculateDiscount, calculateEmi, calculateGpa, calculateLoan, calculateMarginMarkup, calculateMovingAverages, calculatePaybackPeriod, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };
2237
+ export { BASE_LABELS, BASE_PREFIXES, BYTE_UNITS, COMMON_CURRENCIES, COMMON_LOCALES, COMMON_PERMISSIONS, COMMON_RATIOS, COMMON_SIZES, COMMON_TAX_RATES, COMMON_TIP_PERCENTAGES, COMMON_VIEWPORTS, GPA_GRADE_POINTS, USD_DENOMINATIONS, applySuccessiveDiscounts, autoFormat, bumpVersion, calcAspectRatio, calcSubnet, calculateBreakEven, calculateCashBreakdown, calculateCashRounding, calculateCdMaturity, calculateChange, calculateCompoundInterest, calculateCreditUtilization, calculateDepreciationSchedule, calculateDiscount, calculateEmi, calculateGpa, calculateLoan, calculateMarginMarkup, calculateMovingAverages, calculatePaybackPeriod, calculatePercentageChange, calculateRoi, calculateRuleOf72, calculateSavingsGoal, calculateSimpleInterest, calculateTax, calculateTimeValue, calculateTipSplit, calculateVideoBitrate, compareSemver, compareUnitPrices, convert, convertAprApy, convertBytes, convertIp, coverBox, decimalToIp, decimalToIpCidr, describePermissions, fitIntoBox, formatNumber, formatValue, formatVwValue, formatWithSeparator, gcd, ipFromBinary, ipFromDecimal, ipFromHex, ipToBinary, ipToDecimal, ipToDecimalCidr, ipToHex, ipToOctal, isValid, isValidCidr, isValidIp, isValidIpv4, marginToMarkup, markupToMargin, octalToPermissions, parseFileSize, parseInput, parseNumberString, parseSemver, permissionsToChmod, permissionsToOctal, permissionsToSymbolic, ptToPx, pxToAll, pxToViewport, remToPx, satisfiesRange, scaleByHeight, scaleByWidth, sortVersions, splitSubnet, symbolicToPermissions, units_exports, validateBarcode, validateCardNumber, validateIban, validateIsbn, viewportToPx };