@utilix-tech/sdk 0.79.0 → 0.81.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.81.0
6
+ - Add `validateFenceLanguageTags` (markdown-fence-lang-validator): scan a Markdown document's fenced code blocks and flag a missing, unknown, or inconsistently-cased language tag against a curated list of common syntax-highlighter identifiers.
7
+ - Add `countSqlPlaceholders` (sql-placeholder-counter): count positional (`?`, `$1`) and named (`:name`, `@name`) parameter placeholders in a SQL query, flagging mixed styles and numbered-sequence gaps or duplicates.
8
+
9
+ ## 0.80.0
10
+ - Add `diffJsonPatch`, `applyJsonPatch`, `generateJsonPatch` (json-patch): diff two JSON documents into an RFC 6902 JSON Patch operation list (add/remove/replace/move/copy/test), or apply a patch to a target document.
11
+ - Add `parseConnectionString` (connection-string-parser): parse a database connection URI (postgres, mysql, mongodb, redis, amqp, and similar) into its scheme, credentials, hosts, database, and query parameters.
12
+
5
13
  ## 0.79.0
6
14
  - Add `calculateRealRateOfReturn` (real-rate-of-return-calculator): apply the Fisher equation to solve for whichever of the nominal rate, inflation rate, or inflation-adjusted real rate is missing, given exactly two of the three.
7
15
  - Add `calculateRetryBackoffSchedule` (retry-backoff-calculator): compute the full sequence of delays an exponential-backoff retry policy would actually produce, optionally capped and with full/equal jitter ranges, plus the total best-case and worst-case wait across all retries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @utilix-tech/sdk
2
2
 
3
- **706 developer utility functions for Node.js: runs locally, no API key required.**
3
+ **715 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)
@@ -66,7 +66,8 @@ const { formatJson } = require("@utilix-tech/sdk/json");
66
66
 
67
67
  ```ts
68
68
  import { formatJson, minifyJson, diffJson, jsonToTypescript, jsonSchemaToTypescript, validateJson,
69
- evaluateJsonPath, resolveJsonPointer, applyJsonMergePatch, jsonToCsv, yamlToJson, jsonToYaml, getJsonStats } from "@utilix-tech/sdk/json";
69
+ evaluateJsonPath, resolveJsonPointer, applyJsonMergePatch, diffJsonPatch, applyJsonPatch,
70
+ jsonToCsv, yamlToJson, jsonToYaml, getJsonStats } from "@utilix-tech/sdk/json";
70
71
 
71
72
  // Format with 2-space indent
72
73
  formatJson('{"name":"alice","age":30}', { indent: 2 });
@@ -91,6 +92,12 @@ resolveJsonPointer('{"users":[{"name":"Alice"}]}', "/users/0/name");
91
92
  applyJsonMergePatch('{"a":"b","c":{"d":"e","f":"g"}}', '{"a":"z","c":{"f":null}}');
92
93
  // { result: { a: "z", c: { d: "e" } } }
93
94
 
95
+ // Diff two documents into an RFC 6902 JSON Patch, or apply one
96
+ diffJsonPatch({ a: 1 }, { a: 2 });
97
+ // [{ op: "replace", path: "/a", value: 2 }]
98
+ applyJsonPatch({ foo: "bar" }, [{ op: "add", path: "/baz", value: "qux" }]);
99
+ // { result: { foo: "bar", baz: "qux" } }
100
+
94
101
  // Convert JSON to CSV
95
102
  jsonToCsv('[{"a":1,"b":2}]');
96
103
 
@@ -773,7 +780,8 @@ generateCspHeader({ "default-src": ["'self'"], "script-src": ["'self'", "cdn.exa
773
780
  import { formatSql, minifySql, testRegex, formatHtml, formatGql,
774
781
  detectDialect, splitStatements, lookupMime, parseGithubUrl,
775
782
  detectRedos, analyzeRegexGroups, testGitignorePatterns, validateEditorConfig,
776
- validateTsconfig, validatePackageExports } from "@utilix-tech/sdk/code";
783
+ validateTsconfig, validatePackageExports, parseConnectionString,
784
+ validateFenceLanguageTags, countSqlPlaceholders } from "@utilix-tech/sdk/code";
777
785
 
778
786
  // SQL
779
787
  formatSql("SELECT id,name FROM users WHERE active=1 ORDER BY name");
@@ -806,6 +814,18 @@ lookupMime("image/jpeg"); // { kind: "mime", matches: [{ extension: "jpg", ...
806
814
  parseGithubUrl("https://github.com/facebook/react/pull/1234");
807
815
  // { owner: "facebook", repo: "react", type: "pull", number: 1234 }
808
816
 
817
+ // Database connection string parsing
818
+ parseConnectionString("postgres://user:pass@localhost:5432/mydb?sslmode=require");
819
+ // { scheme: "postgres", user: "user", password: "pass", hosts: [{ host: "localhost", port: 5432 }], database: "mydb", params: { sslmode: "require" } }
820
+
821
+ // Markdown code fence language tag validation
822
+ validateFenceLanguageTags("```JS\nconsole.log(1)\n```\n");
823
+ // { totalFences: 1, issues: [{ line: 1, tag: "JS", issue: "inconsistent-case", suggestion: "js" }] }
824
+
825
+ // SQL query parameter placeholder counting
826
+ countSqlPlaceholders("SELECT * FROM users WHERE id = ? AND age > $1");
827
+ // { placeholders: [...], counts: { anonymous: 1, numbered: 1, "named-colon": 0, "named-at": 0 }, mixedStyles: true, numberedGaps: [], numberedDuplicates: [] }
828
+
809
829
  // Regex ReDoS (catastrophic-backtracking) detection
810
830
  detectRedos("(a+)+");
811
831
  // { risk: "high", safe: false, findings: [{ type: "nested-quantifier", ... }] }
@@ -1026,7 +1046,7 @@ readMidiInfo(new Uint8Array(midiBytes));
1026
1046
  | `@utilix-tech/sdk/units` | Bytes, CSS px/rem, number bases, currency format, credit card/IBAN/barcode/ISBN/VIN/ISIN validation, loan calculator, EMI calculator, compound/simple interest calculators, VAT/sales tax calculator, tip/bill split calculator, break-even point calculator, Rule of 72 calculator, savings goal calculator, ROI calculator, cash denomination/till calculator, payback period calculator, CD maturity calculator, debt-to-income ratio calculator, freelancer hourly rate calculator, debt snowball/avalanche payoff planner, tax-equivalent yield calculator, mortgage discount points break-even calculator, loan prepayment penalty calculator, data transfer time / required bandwidth estimator, real rate of return (Fisher equation) calculator |
1027
1047
  | `@utilix-tech/sdk/network` | HTTP status codes, IP validation, country flags, geolocation URLs, HAR file parsing, User-Agent parsing, IPv6 address compress/expand, Permissions-Policy header builder, Client Hints header builder/parser, Content-Disposition header builder/parser, X-Content-Type-Options header builder/risk assessor, sitemap.xml validator, sitemap index splitter, Prefer header builder/parser (RFC 7240), Retry-After header builder/parser (RFC 9110), Priority header builder/parser (RFC 9218), WebSocket close code reference/lookup, Strict-Transport-Security (HSTS) header builder/parser, X-Robots-Tag header builder/parser, WebSocket frame opcode reference/lookup, robots meta tag builder/parser, Cookie header byte-size budget calculator |
1028
1048
  | `@utilix-tech/sdk/api` | cURL build/parse, JWT decode/sign, JWKS parse, CORS headers, CSP header, TOTP/HOTP code generator, webhook HMAC signature verifier |
1029
- | `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format, MIME type lookup, GitHub URL parsing, regex ReDoS detector, Conventional Commits linter, package.json exports field validator, package.json dependency range auditor, regex capture group/backreference visualizer, SVG sprite symbol extractor, ARIA role & attribute validator |
1049
+ | `@utilix-tech/sdk/code` | SQL format/minify, HTML format/minify, regex tester, GraphQL format, MIME type lookup, GitHub URL parsing, regex ReDoS detector, Conventional Commits linter, package.json exports field validator, package.json dependency range auditor, regex capture group/backreference visualizer, SVG sprite symbol extractor, ARIA role & attribute validator, database connection string parser, Markdown code fence language tag validator, SQL query parameter placeholder counter |
1030
1050
  | `@utilix-tech/sdk/color` | Hex/RGB/HSL/HSV/CMYK convert, palette generation, contrast check |
1031
1051
  | `@utilix-tech/sdk/css` | CSS gradient, box-shadow, border-radius, keyframe animation generators |
1032
1052
  | `@utilix-tech/sdk/misc` | SVG optimize/sanitize, file size format, Unicode inspector |
@@ -7,6 +7,7 @@ __export(code_exports, {
7
7
  ALL_FLAGS: () => ALL_FLAGS,
8
8
  CHEATSHEET: () => CHEATSHEET,
9
9
  DEFAULT_COMMIT_TYPES: () => DEFAULT_COMMIT_TYPES,
10
+ KNOWN_LANGUAGE_TAGS: () => KNOWN_LANGUAGE_TAGS,
10
11
  MIME_TABLE: () => MIME_TABLE,
11
12
  OPERATIONS: () => OPERATIONS,
12
13
  SQL_DEFAULT_OPTIONS: () => SQL_DEFAULT_OPTIONS,
@@ -19,6 +20,7 @@ __export(code_exports, {
19
20
  collapseJsWhitespace: () => collapseJsWhitespace,
20
21
  countProperties: () => countProperties,
21
22
  countRules: () => countRules,
23
+ countSqlPlaceholders: () => countSqlPlaceholders,
22
24
  detectDialect: () => detectDialect,
23
25
  detectOperationType: () => detectOperationType,
24
26
  detectRedos: () => detectRedos,
@@ -44,6 +46,7 @@ __export(code_exports, {
44
46
  minifySql: () => minifySql,
45
47
  normalizeRef: () => normalizeRef,
46
48
  parseCommitTrailers: () => parseCommitTrailers,
49
+ parseConnectionString: () => parseConnectionString,
47
50
  parseDockerImage: () => parseDockerImage,
48
51
  parseEnv: () => parseEnv,
49
52
  parseGitCommand: () => parseGitCommand,
@@ -63,6 +66,7 @@ __export(code_exports, {
63
66
  validateAria: () => validateAria,
64
67
  validateEditorConfig: () => validateEditorConfig,
65
68
  validateEnvKey: () => validateEnvKey,
69
+ validateFenceLanguageTags: () => validateFenceLanguageTags,
66
70
  validatePackageExports: () => validatePackageExports,
67
71
  validateTsconfig: () => validateTsconfig
68
72
  });
@@ -3876,5 +3880,393 @@ function validateAria(html) {
3876
3880
  valid: issues.every((i) => i.severity !== "error")
3877
3881
  };
3878
3882
  }
3883
+ var CONNECTION_STRING_RE = /^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/(?:([^@/?#]*)@)?([^/?#]*)(?:\/([^?#]*))?(?:\?([^#]*))?$/;
3884
+ function _parseConnectionHost(segment) {
3885
+ const ipv6Match = segment.match(/^\[([^\]]+)\](?::(\d+))?$/);
3886
+ if (ipv6Match) {
3887
+ return { host: ipv6Match[1], port: ipv6Match[2] ? parseInt(ipv6Match[2], 10) : null };
3888
+ }
3889
+ const lastColon = segment.lastIndexOf(":");
3890
+ if (lastColon === -1) return { host: segment, port: null };
3891
+ const portPart = segment.slice(lastColon + 1);
3892
+ if (!/^\d+$/.test(portPart)) return { host: segment, port: null };
3893
+ return { host: segment.slice(0, lastColon), port: parseInt(portPart, 10) };
3894
+ }
3895
+ function parseConnectionString(input) {
3896
+ if (!input || typeof input !== "string" || !input.trim()) {
3897
+ return { error: "Connection string is empty" };
3898
+ }
3899
+ const trimmed = input.trim();
3900
+ const match = trimmed.match(CONNECTION_STRING_RE);
3901
+ if (!match) {
3902
+ return { error: "Not a valid scheme://[user[:password]@]host[,host2][/database][?params] connection string" };
3903
+ }
3904
+ const [, scheme, userinfo, hostPart, databasePart, queryPart] = match;
3905
+ let user = null;
3906
+ let password = null;
3907
+ if (userinfo) {
3908
+ const sepIndex = userinfo.indexOf(":");
3909
+ if (sepIndex === -1) {
3910
+ user = decodeURIComponent(userinfo);
3911
+ } else {
3912
+ user = decodeURIComponent(userinfo.slice(0, sepIndex));
3913
+ password = decodeURIComponent(userinfo.slice(sepIndex + 1));
3914
+ }
3915
+ }
3916
+ const hosts = hostPart ? hostPart.split(",").filter(Boolean).map(_parseConnectionHost) : [];
3917
+ const database = databasePart ? decodeURIComponent(databasePart) : null;
3918
+ const params = {};
3919
+ if (queryPart) {
3920
+ for (const pair of queryPart.split("&")) {
3921
+ if (!pair) continue;
3922
+ const eqIndex = pair.indexOf("=");
3923
+ const key = eqIndex === -1 ? pair : pair.slice(0, eqIndex);
3924
+ const value = eqIndex === -1 ? "" : pair.slice(eqIndex + 1);
3925
+ params[decodeURIComponent(key)] = decodeURIComponent(value);
3926
+ }
3927
+ }
3928
+ return { scheme, user, password, hosts, database, params };
3929
+ }
3930
+ var KNOWN_LANGUAGE_TAGS = /* @__PURE__ */ new Set([
3931
+ "js",
3932
+ "jsx",
3933
+ "mjs",
3934
+ "cjs",
3935
+ "javascript",
3936
+ "ts",
3937
+ "tsx",
3938
+ "typescript",
3939
+ "json",
3940
+ "json5",
3941
+ "jsonc",
3942
+ "jsonl",
3943
+ "ndjson",
3944
+ "yaml",
3945
+ "yml",
3946
+ "toml",
3947
+ "ini",
3948
+ "cfg",
3949
+ "conf",
3950
+ "properties",
3951
+ "env",
3952
+ "dotenv",
3953
+ "html",
3954
+ "htm",
3955
+ "xml",
3956
+ "svg",
3957
+ "xhtml",
3958
+ "css",
3959
+ "scss",
3960
+ "sass",
3961
+ "less",
3962
+ "stylus",
3963
+ "styl",
3964
+ "markdown",
3965
+ "md",
3966
+ "mdx",
3967
+ "bash",
3968
+ "sh",
3969
+ "shell",
3970
+ "zsh",
3971
+ "fish",
3972
+ "console",
3973
+ "powershell",
3974
+ "ps1",
3975
+ "bat",
3976
+ "cmd",
3977
+ "python",
3978
+ "py",
3979
+ "py3",
3980
+ "ipynb",
3981
+ "ruby",
3982
+ "rb",
3983
+ "erb",
3984
+ "php",
3985
+ "java",
3986
+ "kotlin",
3987
+ "kt",
3988
+ "kts",
3989
+ "groovy",
3990
+ "gradle",
3991
+ "swift",
3992
+ "objectivec",
3993
+ "objc",
3994
+ "go",
3995
+ "golang",
3996
+ "rust",
3997
+ "rs",
3998
+ "c",
3999
+ "h",
4000
+ "cpp",
4001
+ "cc",
4002
+ "cxx",
4003
+ "hpp",
4004
+ "c++",
4005
+ "csharp",
4006
+ "cs",
4007
+ "fsharp",
4008
+ "fs",
4009
+ "perl",
4010
+ "pl",
4011
+ "lua",
4012
+ "r",
4013
+ "matlab",
4014
+ "m",
4015
+ "scala",
4016
+ "nim",
4017
+ "zig",
4018
+ "julia",
4019
+ "jl",
4020
+ "haskell",
4021
+ "hs",
4022
+ "elixir",
4023
+ "ex",
4024
+ "exs",
4025
+ "erlang",
4026
+ "erl",
4027
+ "clojure",
4028
+ "clj",
4029
+ "cljs",
4030
+ "dart",
4031
+ "ocaml",
4032
+ "ml",
4033
+ "sql",
4034
+ "mysql",
4035
+ "postgresql",
4036
+ "plsql",
4037
+ "sqlite",
4038
+ "graphql",
4039
+ "gql",
4040
+ "dockerfile",
4041
+ "docker",
4042
+ "makefile",
4043
+ "make",
4044
+ "cmake",
4045
+ "nginx",
4046
+ "apache",
4047
+ "diff",
4048
+ "patch",
4049
+ "git",
4050
+ "http",
4051
+ "proto",
4052
+ "protobuf",
4053
+ "csv",
4054
+ "tsv",
4055
+ "vim",
4056
+ "viml",
4057
+ "tex",
4058
+ "latex",
4059
+ "bibtex",
4060
+ "plaintext",
4061
+ "text",
4062
+ "txt",
4063
+ "nohighlight",
4064
+ "none",
4065
+ "asciidoc",
4066
+ "adoc",
4067
+ "rst",
4068
+ "vue",
4069
+ "svelte",
4070
+ "astro",
4071
+ "wasm",
4072
+ "wat",
4073
+ "handlebars",
4074
+ "hbs",
4075
+ "pug",
4076
+ "jade",
4077
+ "coffee",
4078
+ "coffeescript",
4079
+ "actionscript",
4080
+ "terraform",
4081
+ "tf",
4082
+ "hcl",
4083
+ "puppet",
4084
+ "ansible"
4085
+ ]);
4086
+ function _parseFenceLine(line) {
4087
+ const match = line.match(/^( {0,3})(`{3,}|~{3,})(.*)$/);
4088
+ if (!match) return null;
4089
+ const [, indent, marker, rest] = match;
4090
+ const char = marker[0];
4091
+ if (char === "`" && rest.includes("`")) return null;
4092
+ return { indent: indent.length, char, length: marker.length, infoString: rest.trim() };
4093
+ }
4094
+ function _isClosingFence(line, fenceChar, fenceLength) {
4095
+ const match = line.match(/^ {0,3}(`+|~+)\s*$/);
4096
+ if (!match) return false;
4097
+ const marker = match[1];
4098
+ return marker[0] === fenceChar && marker.length >= fenceLength;
4099
+ }
4100
+ function validateFenceLanguageTags(markdown) {
4101
+ const lines = markdown.split("\n");
4102
+ const issues = [];
4103
+ let totalFences = 0;
4104
+ let open = null;
4105
+ for (let i = 0; i < lines.length; i++) {
4106
+ const lineNumber = i + 1;
4107
+ const line = lines[i];
4108
+ if (open) {
4109
+ if (_isClosingFence(line, open.char, open.length)) open = null;
4110
+ continue;
4111
+ }
4112
+ const fence = _parseFenceLine(line);
4113
+ if (!fence) continue;
4114
+ totalFences++;
4115
+ const tag = fence.infoString.split(/\s+/)[0] ?? "";
4116
+ open = { char: fence.char, length: fence.length };
4117
+ if (tag === "") {
4118
+ issues.push({ line: lineNumber, tag, issue: "missing" });
4119
+ continue;
4120
+ }
4121
+ const lower = tag.toLowerCase();
4122
+ if (!KNOWN_LANGUAGE_TAGS.has(lower)) {
4123
+ issues.push({ line: lineNumber, tag, issue: "unknown" });
4124
+ continue;
4125
+ }
4126
+ if (tag !== lower) {
4127
+ issues.push({ line: lineNumber, tag, issue: "inconsistent-case", suggestion: lower });
4128
+ }
4129
+ }
4130
+ return { totalFences, issues };
4131
+ }
4132
+ var _IDENT_START = /[A-Za-z_]/;
4133
+ var _IDENT_CHAR = /[A-Za-z0-9_]/;
4134
+ function _readIdentifier(sql, start) {
4135
+ let end = start;
4136
+ while (end < sql.length && _IDENT_CHAR.test(sql[end])) end++;
4137
+ return sql.slice(start, end);
4138
+ }
4139
+ function countSqlPlaceholders(sql) {
4140
+ const placeholders = [];
4141
+ let state = "normal";
4142
+ let i = 0;
4143
+ while (i < sql.length) {
4144
+ const ch = sql[i];
4145
+ const next = sql[i + 1];
4146
+ if (state === "single") {
4147
+ if (ch === "'" && next === "'") {
4148
+ i += 2;
4149
+ continue;
4150
+ }
4151
+ if (ch === "'") {
4152
+ state = "normal";
4153
+ i++;
4154
+ continue;
4155
+ }
4156
+ i++;
4157
+ continue;
4158
+ }
4159
+ if (state === "double") {
4160
+ if (ch === '"' && next === '"') {
4161
+ i += 2;
4162
+ continue;
4163
+ }
4164
+ if (ch === '"') {
4165
+ state = "normal";
4166
+ i++;
4167
+ continue;
4168
+ }
4169
+ i++;
4170
+ continue;
4171
+ }
4172
+ if (state === "line-comment") {
4173
+ if (ch === "\n") state = "normal";
4174
+ i++;
4175
+ continue;
4176
+ }
4177
+ if (state === "block-comment") {
4178
+ if (ch === "*" && next === "/") {
4179
+ state = "normal";
4180
+ i += 2;
4181
+ continue;
4182
+ }
4183
+ i++;
4184
+ continue;
4185
+ }
4186
+ if (ch === "'") {
4187
+ state = "single";
4188
+ i++;
4189
+ continue;
4190
+ }
4191
+ if (ch === '"') {
4192
+ state = "double";
4193
+ i++;
4194
+ continue;
4195
+ }
4196
+ if (ch === "-" && next === "-") {
4197
+ state = "line-comment";
4198
+ i += 2;
4199
+ continue;
4200
+ }
4201
+ if (ch === "/" && next === "*") {
4202
+ state = "block-comment";
4203
+ i += 2;
4204
+ continue;
4205
+ }
4206
+ if (ch === "?") {
4207
+ placeholders.push({ style: "anonymous", raw: "?", index: i });
4208
+ i++;
4209
+ continue;
4210
+ }
4211
+ if (ch === "$" && /[0-9]/.test(next ?? "")) {
4212
+ let end = i + 1;
4213
+ while (end < sql.length && /[0-9]/.test(sql[end])) end++;
4214
+ const raw = sql.slice(i, end);
4215
+ placeholders.push({ style: "numbered", raw, index: i, number: parseInt(raw.slice(1), 10) });
4216
+ i = end;
4217
+ continue;
4218
+ }
4219
+ if (ch === ":") {
4220
+ if (next === ":") {
4221
+ i += 2;
4222
+ continue;
4223
+ }
4224
+ if (next && _IDENT_START.test(next)) {
4225
+ const name = _readIdentifier(sql, i + 1);
4226
+ placeholders.push({ style: "named-colon", raw: `:${name}`, index: i, name });
4227
+ i += 1 + name.length;
4228
+ continue;
4229
+ }
4230
+ i++;
4231
+ continue;
4232
+ }
4233
+ if (ch === "@") {
4234
+ if (next === "@") {
4235
+ i += 2;
4236
+ continue;
4237
+ }
4238
+ if (next && _IDENT_START.test(next)) {
4239
+ const name = _readIdentifier(sql, i + 1);
4240
+ placeholders.push({ style: "named-at", raw: `@${name}`, index: i, name });
4241
+ i += 1 + name.length;
4242
+ continue;
4243
+ }
4244
+ i++;
4245
+ continue;
4246
+ }
4247
+ i++;
4248
+ }
4249
+ const counts = { anonymous: 0, numbered: 0, "named-colon": 0, "named-at": 0 };
4250
+ for (const p of placeholders) counts[p.style]++;
4251
+ const stylesUsed = ["anonymous", "numbered", "named-colon", "named-at"].filter((s) => counts[s] > 0);
4252
+ const numbers = placeholders.filter((p) => p.style === "numbered").map((p) => p.number);
4253
+ const maxNumber = numbers.length > 0 ? Math.max(...numbers) : 0;
4254
+ const seen = new Set(numbers);
4255
+ const numberedGaps = [];
4256
+ for (let n = 1; n < maxNumber; n++) {
4257
+ if (!seen.has(n)) numberedGaps.push(n);
4258
+ }
4259
+ const countedMap = /* @__PURE__ */ new Map();
4260
+ for (const n of numbers) countedMap.set(n, (countedMap.get(n) ?? 0) + 1);
4261
+ const numberedDuplicates = [...countedMap.entries()].filter(([, c]) => c > 1).map(([n]) => n).sort((a, b) => a - b);
4262
+ return {
4263
+ placeholders,
4264
+ counts,
4265
+ stylesUsed,
4266
+ mixedStyles: stylesUsed.length > 1,
4267
+ numberedGaps,
4268
+ numberedDuplicates
4269
+ };
4270
+ }
3879
4271
 
3880
- export { ALL_FLAGS, CHEATSHEET, DEFAULT_COMMIT_TYPES, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, analyzeRegexGroups, auditDependencyRanges, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, extractMarkdownFootnotes, extractSvgSpriteSymbols, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lintConventionalCommit, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseCommitTrailers, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testGitignorePatterns, testPattern, testRegex, validateAria, validateEditorConfig, validateEnvKey, validatePackageExports, validateTsconfig };
4272
+ export { ALL_FLAGS, CHEATSHEET, DEFAULT_COMMIT_TYPES, KNOWN_LANGUAGE_TAGS, MIME_TABLE, OPERATIONS, SQL_DEFAULT_OPTIONS, analyzeRegexGroups, auditDependencyRanges, buildDockerPull, buildDockerRun, buildGitCommand, code_exports, collapseHtmlWhitespace, collapseJsWhitespace, countProperties, countRules, countSqlPlaceholders, detectDialect, detectOperationType, detectRedos, envToExport, envToJson, extractMarkdownFootnotes, extractSvgSpriteSymbols, formatCss, formatGql, formatHtml, formatSql, getEndpointsByTag, getHtmlStats, getJsStats, getMethodColor, isValidTag, lintConventionalCommit, lookupMime, minifyCss, minifyGql, minifyHtml, minifyJs, minifySql, normalizeRef, parseCommitTrailers, parseConnectionString, parseDockerImage, parseEnv, parseGitCommand, parseGithubUrl, parseSpec, removeConsoleCalls, removeEmptyAttributes, removeHtmlComments, removeJsComments, removeRedundantAttributes, searchCheatsheet, searchEndpoints, splitStatements, testGitignorePatterns, testPattern, testRegex, validateAria, validateEditorConfig, validateEnvKey, validateFenceLanguageTags, validatePackageExports, validateTsconfig };