From 09b2c9c6807367e6810c9a34d6cc0ad47f230a9b Mon Sep 17 00:00:00 2001 From: SRC-Saif Ullah Jafri <131288625+Saifjafri212213@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:25:09 +0530 Subject: [PATCH 01/15] fix(invoice): count on from a number of more than 15 digits without losing digits (#445) (#448) nextInvoiceNumber() read the trailing digits with parseInt and wrote value + 1 back as a JS number, so a number of more than 15 digits lost precision (a 17-digit number came back one lower) and one of 22 digits or more was printed as 1e+22. It now uses BigInt, and keeps the prefix and the zero-padding. Co-authored-by: Saif Ullah jafri --- lib/__tests__/invoice-types.test.ts | 26 ++++++++++++++++++++++++++ lib/invoice-types.ts | 7 ++++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/__tests__/invoice-types.test.ts b/lib/__tests__/invoice-types.test.ts index 89f75ff..f046794 100644 --- a/lib/__tests__/invoice-types.test.ts +++ b/lib/__tests__/invoice-types.test.ts @@ -152,6 +152,32 @@ describe("nextInvoiceNumber", () => { expect(nextInvoiceNumber([base("1001 "), base("draft"), base("")])).toBe("1002"); }); + // A JavaScript number holds 15 digits exactly, so these used to come back lower than the number they follow. + it.each([ + ["20261031143012345", "20261031143012346"], + ["INV-9007199254740993", "INV-9007199254740994"], + ["INV-9007199254740992", "INV-9007199254740993"], + ["INV-99999999999999999999", "INV-100000000000000000000"], + ["INV-9999999999999999999999", "INV-10000000000000000000000"], + ["INV-0000000000000000000007", "INV-0000000000000000000008"], + ["INV-12345678901234567890123456789", "INV-12345678901234567890123456790"], + ["2026-00000000000000000099", "2026-00000000000000000100"], + ])("counts on from %s without losing digits, giving %s", (highest, expected) => { + expect(nextInvoiceNumber([base(highest)])).toBe(expected); + }); + + it("picks the highest of long numbers that a JavaScript number cannot tell apart", () => { + expect(nextInvoiceNumber([base("INV-9007199254740993"), base("INV-9007199254740992")])).toBe("INV-9007199254740994"); + expect(nextInvoiceNumber([base("INV-9007199254740992"), base("INV-9007199254740993")])).toBe("INV-9007199254740994"); + expect(nextInvoiceNumber([base("INV-100000000000000000001"), base("INV-100000000000000000002"), base("INV-099999999999999999999")])).toBe( + "INV-100000000000000000003", + ); + }); + + it("never gives a number that is not made of digits", () => { + expect(nextInvoiceNumber([base(`INV-${"9".repeat(40)}`)])).toBe(`INV-1${"0".repeat(40)}`); + }); + it("starts at the fallback prefix when no invoice has a number", () => { expect(nextInvoiceNumber([base("draft"), base("")])).toBe("INV0001"); expect(nextInvoiceNumber([], "BILL", 6)).toBe("BILL000001"); diff --git a/lib/invoice-types.ts b/lib/invoice-types.ts index 039563f..90d9f06 100644 --- a/lib/invoice-types.ts +++ b/lib/invoice-types.ts @@ -161,17 +161,18 @@ export function todayISO(): string { * "INV0001". */ export function nextInvoiceNumber(invoices: Invoice[], fallbackPrefix = "INV", pad = 4): string { - let highest: { prefix: string; digits: string; value: number } | undefined; + // BigInt, because a number of more than 15 digits (a timestamp with milliseconds, say) is not exact as a JS number. + let highest: { prefix: string; digits: string; value: bigint } | undefined; for (const inv of invoices) { const match = (inv.number || "").match(/^(.*?)(\d+)\s*$/); if (!match) continue; - const value = parseInt(match[2], 10); + const value = BigInt(match[2]); if (!highest || value >= highest.value) { highest = { prefix: match[1], digits: match[2], value }; } } if (!highest) return `${fallbackPrefix}${String(1).padStart(pad, "0")}`; - return `${highest.prefix}${String(highest.value + 1).padStart(highest.digits.length, "0")}`; + return `${highest.prefix}${String(highest.value + BigInt(1)).padStart(highest.digits.length, "0")}`; } export const DEFAULT_SENDER: SenderProfile = { From df8d81834ff3ce1757a1ae1663591b5e070cf3d3 Mon Sep 17 00:00:00 2001 From: SRC-Saif Ullah Jafri <131288625+Saifjafri212213@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:50:36 +0530 Subject: [PATCH 02/15] fix(csv-formatter): write control characters in the YAML output as escapes (#446) (#449) yamlString() only escaped backslash, double quote, newline and carriage return, so every other control character went into the double-quoted scalar raw, and js-yaml refused to load the output. It now escapes tab, the C0 and C1 control characters, U+2028, U+2029, U+FFFE and U+FFFF the way the JSON formatter's YAML conversion does (#234). Co-authored-by: Saif Ullah jafri --- lib/__tests__/csv-utils.test.ts | 49 +++++++++++++++++++++++++++++++++ lib/csv-utils.ts | 18 +++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/lib/__tests__/csv-utils.test.ts b/lib/__tests__/csv-utils.test.ts index 997e63b..f46de6b 100644 --- a/lib/__tests__/csv-utils.test.ts +++ b/lib/__tests__/csv-utils.test.ts @@ -217,6 +217,55 @@ describe("csvToYaml", () => { expect(csvToYaml(["note"], [['a "b" \\ c\nd']])).toBe('- note: "a \\"b\\" \\\\ c\\nd"'); }); + // A raw control character makes the document invalid YAML, and js-yaml refuses to load it. Escaped the way the + // JSON formatter's YAML conversion does (#234). + describe("control characters", () => { + const yamlOf = (value: string) => csvToYaml(["note"], [[value]]); + + it.each([ + ["NUL", "\u0000", "\\x00"], + ["BEL", "\u0007", "\\x07"], + ["backspace", "\u0008", "\\x08"], + ["vertical tab", "\u000b", "\\x0B"], + ["form feed", "\u000c", "\\x0C"], + ["shift out", "\u000e", "\\x0E"], + ["ESC", "\u001b", "\\x1B"], + ["unit separator", "\u001f", "\\x1F"], + ["DEL", "\u007f", "\\x7F"], + ["next line", "\u0085", "\\x85"], + ["C1 control", "\u009f", "\\x9F"], + ["line separator", "\u2028", "\\u2028"], + ["paragraph separator", "\u2029", "\\u2029"], + ["noncharacter FFFE", "\ufffe", "\\uFFFE"], + ["noncharacter FFFF", "\uffff", "\\uFFFF"], + ])("writes %s as an escape", (_name, character, escape) => { + expect(yamlOf(`a${character}b`)).toBe(`- note: "a${escape}b"`); + }); + + it("writes a tab as \\t", () => { + expect(yamlOf("a\tb")).toBe('- note: "a\\tb"'); + }); + + it("keeps the escapes it already had", () => { + expect(yamlOf('q"\\\n\r')).toBe('- note: "q\\"\\\\\\n\\r"'); + }); + + it("leaves no raw control character anywhere in the output", () => { + const every = Array.from({ length: 0xa0 }, (_, code) => String.fromCharCode(code)).join(""); + const output = csvToYaml([`k${every}`], [[every, `x${every}y`]]); + // Only the line breaks between the lines of the document are left. + expect(output.replace(/\n/g, "")).not.toMatch(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]/); + }); + + it("escapes them in a column name too", () => { + expect(csvToYaml(["a\u0007b"], [["x"]])).toBe('- "a\\x07b": "x"'); + }); + + it("leaves other text, accents and emoji as they were", () => { + expect(yamlOf("café \u00e9 \u{1f600} \u00a0")).toBe('- note: "café \u00e9 \u{1f600} \u00a0"'); + }); + }); + it("quotes non-identifier keys", () => { expect(csvToYaml(["first name"], [["Ada"]])).toBe('- "first name": "Ada"'); }); diff --git a/lib/csv-utils.ts b/lib/csv-utils.ts index 2f60eae..dfc74b1 100644 --- a/lib/csv-utils.ts +++ b/lib/csv-utils.ts @@ -551,8 +551,24 @@ export function csvToTypeScript(header: string[], typeName = "Row"): string { return `export interface ${name} {\n${fields.join("\n")}\n}`; } +// Control characters, the C1 range, the Unicode line and paragraph separators and the noncharacters FFFE and FFFF +// (tab, \n and \r have their own escapes). A raw one in a double-quoted scalar is invalid YAML: js-yaml won't load it. +const YAML_CONTROL_CHARS = /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f\u2028\u2029\ufffe\uffff]/g; + function yamlString(value: string): string { - return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`; + const escaped = value + .replace(/\\/g, "\\\\") + .replace(/"/g, '\\"') + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/\t/g, "\\t") + .replace(YAML_CONTROL_CHARS, (char) => { + const code = char.charCodeAt(0); + return code < 0x100 + ? `\\x${code.toString(16).toUpperCase().padStart(2, "0")}` + : `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`; + }); + return `"${escaped}"`; } function yamlKey(key: string): string { From 765d266e202b15493cfc09139f7b00d9935be949 Mon Sep 17 00:00:00 2001 From: Anurag Verma <78868769+anurag629@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:53:17 +0530 Subject: [PATCH 03/15] chore: list the GitHub copy of funding.json in the well-known files (#450) --- .well-known/funding-manifest-urls | 1 + public/.well-known/funding-manifest-urls | 1 + 2 files changed, 2 insertions(+) diff --git a/.well-known/funding-manifest-urls b/.well-known/funding-manifest-urls index 42647b8..8cd6afd 100644 --- a/.well-known/funding-manifest-urls +++ b/.well-known/funding-manifest-urls @@ -1 +1,2 @@ https://oss.codercops.com/funding.json +https://raw.githubusercontent.com/codercops/.github/main/funding.json diff --git a/public/.well-known/funding-manifest-urls b/public/.well-known/funding-manifest-urls index 42647b8..8cd6afd 100644 --- a/public/.well-known/funding-manifest-urls +++ b/public/.well-known/funding-manifest-urls @@ -1 +1,2 @@ https://oss.codercops.com/funding.json +https://raw.githubusercontent.com/codercops/.github/main/funding.json From b5fa017d5c8024dce88651a9d61ce339d650cd7b Mon Sep 17 00:00:00 2001 From: SRC-Saif Ullah Jafri <131288625+Saifjafri212213@users.noreply.github.com> Date: Sun, 1 Nov 2026 01:20:52 +0530 Subject: [PATCH 04/15] fix(csv-formatter): fall back to Row when the Type name is a reserved word (#447) (#451) csvToTypeScript() fell back to Row for a name that is not an identifier, but a reserved word passes the identifier check, so a Type name of class, default or enum gave an interface TypeScript rejects. The words TypeScript will not take as an interface name (the reserved words, those reserved in strict mode, await and the built-in type names) now fall back to Row too. Co-authored-by: Saif Ullah jafri --- lib/__tests__/csv-utils.test.ts | 72 +++++++++++++++++++++++++++++++++ lib/csv-utils.ts | 12 +++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/lib/__tests__/csv-utils.test.ts b/lib/__tests__/csv-utils.test.ts index f46de6b..1bda98b 100644 --- a/lib/__tests__/csv-utils.test.ts +++ b/lib/__tests__/csv-utils.test.ts @@ -1,3 +1,4 @@ +import ts from "typescript"; import { describe, expect, it } from "vitest"; import { csvToJson, @@ -206,6 +207,77 @@ describe("csvToTypeScript", () => { it("handles no columns", () => { expect(csvToTypeScript([])).toBe("export interface Row {}"); }); + + describe("the type name", () => { + // Words TypeScript will not take as the name of an interface: reserved words, the ones reserved in strict mode + // (which a module always is), `await`, and the names of the built-in types. + const reserved = [ + "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else", "enum", + "export", "extends", "false", "finally", "for", "function", "if", "import", "in", "instanceof", "new", "null", + "return", "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", + "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield", "await", + "any", "unknown", "never", "number", "bigint", "boolean", "string", "symbol", "object", "undefined", + ]; + // Words that look like they might be, and are fine. + const allowed = [ + "Class", "Default", "String", "Number", "Object", "Array", "Date", "Row", "Person", "type", "of", "as", "async", + "from", "get", "set", "constructor", "declare", "module", "namespace", "readonly", "keyof", "abstract", "is", + "global", "require", "infer", "asserts", "arguments", "eval", "_", "$", "class_", "$class", "classy", + ]; + + /** The diagnostics the TypeScript compiler gives each source text, all checked in one program. */ + function diagnose(texts: string[]): string[][] { + const files = new Map(texts.map((text, index) => [`/probe/f${index}.ts`, text])); + const options: ts.CompilerOptions = { noEmit: true, strict: true, skipLibCheck: true, target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.ESNext }; + const host = ts.createCompilerHost(options); + const getSourceFile = host.getSourceFile.bind(host); + host.getSourceFile = (name, languageVersion, ...rest) => + files.has(name) ? ts.createSourceFile(name, files.get(name) as string, languageVersion) : getSourceFile(name, languageVersion, ...rest); + host.fileExists = (name) => files.has(name) || ts.sys.fileExists(name); + host.readFile = (name) => files.get(name) ?? ts.sys.readFile(name); + const program = ts.createProgram([...files.keys()], options, host); + return [...files.keys()].map((name) => { + const file = program.getSourceFile(name) as ts.SourceFile; + return [...program.getSyntacticDiagnostics(file), ...program.getSemanticDiagnostics(file)].map((d) => ts.flattenDiagnosticMessageText(d.messageText, " ")); + }); + } + + it.each(reserved)("falls back to Row for the reserved name %s", (name) => { + expect(csvToTypeScript(["a"], name)).toBe("export interface Row {\n a: string;\n}"); + }); + + it("keeps a name that only looks reserved, or differs in case", () => { + for (const name of allowed) expect(csvToTypeScript(["a"], name), name).toBe(`export interface ${name} {\n a: string;\n}`); + }); + + it("still falls back for a name that is not an identifier", () => { + for (const name of ["", " ", "my type", "1st", "a-b", "a.b", "é", "class Row"]) { + expect(csvToTypeScript(["a"], name), JSON.stringify(name)).toBe("export interface Row {\n a: string;\n}"); + } + }); + + it("uses the default name for no name at all", () => { + expect(csvToTypeScript(["a"])).toBe("export interface Row {\n a: string;\n}"); + }); + + it("writes TypeScript that compiles, for every name here, and the list is what the compiler rejects", () => { + const names = [...reserved, ...allowed]; + const outputs = names.map((name) => csvToTypeScript(["a", "class", "first name"], name)); + const written = diagnose(outputs); + expect(written.map((found, index) => (found.length ? `${names[index]}: ${found[0]}` : null)).filter(Boolean)).toEqual([]); + + // The same names used as they were typed: the compiler rejects exactly the reserved ones. + const raw = diagnose(names.map((name) => `export interface ${name} {\n a: string;\n}\n`)); + const rejected = names.filter((_, index) => raw[index].length > 0); + expect(rejected.sort()).toEqual([...reserved].sort()); + }); + + it("leaves property names alone, since a reserved word is fine as a property", () => { + expect(csvToTypeScript(["class", "default", "string"], "Row")).toBe( + "export interface Row {\n class: string;\n default: string;\n string: string;\n}", + ); + }); + }); }); describe("csvToYaml", () => { diff --git a/lib/csv-utils.ts b/lib/csv-utils.ts index dfc74b1..3e71193 100644 --- a/lib/csv-utils.ts +++ b/lib/csv-utils.ts @@ -70,6 +70,16 @@ export const MAX_REPORTED_ROW_ERRORS = 25; const BOM = "\uFEFF"; const PLAIN_NUMBER_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/; const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/; +// Words TypeScript will not take as an interface name: reserved words, those reserved in strict mode (a module is +// strict), `await`, and the names of the built-in types. A property can still be called any of them. +const RESERVED_TYPE_NAMES = new Set( + ( + "break case catch class const continue debugger default delete do else enum export extends false finally for " + + "function if import in instanceof new null return super switch this throw true try typeof var void while with " + + "implements interface let package private protected public static yield await " + + "any unknown never number bigint boolean string symbol object undefined" + ).split(" "), +); const YAML_KEY_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/; // Plain YAML keys that a 1.1 parser reads as null or a boolean. They must be // quoted, or `null: x` becomes `{None: "x"}` and `on: y` becomes `{True: "y"}`. @@ -542,7 +552,7 @@ function tsProperty(name: string): string { */ export function csvToTypeScript(header: string[], typeName = "Row"): string { const keys = uniqueHeaders(header); - const name = IDENTIFIER_RE.test(typeName) ? typeName : "Row"; + const name = IDENTIFIER_RE.test(typeName) && !RESERVED_TYPE_NAMES.has(typeName) ? typeName : "Row"; if (keys.length === 0) return `export interface ${name} {}`; From 47385f8665c33b962228d17373cf3ef9f3aa3fff Mon Sep 17 00:00:00 2001 From: SRC-Saif Ullah Jafri <131288625+Saifjafri212213@users.noreply.github.com> Date: Sun, 1 Nov 2026 01:50:32 +0530 Subject: [PATCH 05/15] fix(unicode-inspector): flag DELETE, the Hangul Filler and the blank Braille pattern as invisible (#452) (#453) The control check stopped at 0x1f and started again at 0x80, so DELETE (U+007F) got no label, and U+3164 and U+2800, which are letters and symbols and not spaces or format characters, were in no table. DELETE is now a control, and U+3164 (intentional, like the other Hangul fillers) and U+2800 are listed. Co-authored-by: Saif Ullah jafri --- lib/__tests__/unicode-inspector.test.ts | 43 +++++++++++++++++++++++++ lib/unicode-inspector.ts | 4 ++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/__tests__/unicode-inspector.test.ts b/lib/__tests__/unicode-inspector.test.ts index cae4029..9186bfa 100644 --- a/lib/__tests__/unicode-inspector.test.ts +++ b/lib/__tests__/unicode-inspector.test.ts @@ -110,6 +110,49 @@ describe("inspectUnicode", () => { expect(inspectUnicode("\ufeff").rows[0].invisibleLabel).toBe("BYTE ORDER MARK (BOM)"); }); + it("labels DELETE as a control character, like the other controls", () => { + expect(inspectUnicode("\u007f").rows[0]).toMatchObject({ + invisibleLabel: "CONTROL (U+007F)", + intentional: false, + }); + expect(inspectUnicode("a\u007fb").rows.map((row) => row.invisibleLabel)).toEqual([null, "CONTROL (U+007F)", null]); + }); + + it("labels the Hangul filler and the blank Braille pattern, which show nothing on screen", () => { + expect(inspectUnicode("\u3164").rows[0]).toMatchObject({ + invisibleLabel: "HANGUL FILLER", + intentional: true, + }); + expect(inspectUnicode("\u2800").rows[0]).toMatchObject({ + invisibleLabel: "BRAILLE PATTERN BLANK", + intentional: false, + }); + }); + + it("keeps the other Hangul fillers as they were", () => { + expect(inspectUnicode("\u115f").rows[0]).toMatchObject({ invisibleLabel: "HANGUL CHOSEONG FILLER", intentional: true }); + expect(inspectUnicode("\u1160").rows[0]).toMatchObject({ invisibleLabel: "HANGUL JUNGSEONG FILLER", intentional: true }); + expect(inspectUnicode("\uffa0").rows[0]).toMatchObject({ invisibleLabel: "HALFWIDTH HANGUL FILLER", intentional: true }); + }); + + it("does not label the characters next to them, which are visible", () => { + for (const visible of ["~", "\u00a1", "\u2801", "\u28ff", "\u3163", "\u3165", "\u3001", "\u4e2d", "a", "0"]) { + expect(inspectUnicode(visible).rows[0].invisibleLabel, visible.codePointAt(0)?.toString(16)).toBeNull(); + } + }); + + it("labels every control, space separator and format character in the BMP", () => { + const unlabelled: string[] = []; + for (let code = 0; code <= 0xffff; code++) { + if (code >= 0xd800 && code <= 0xdfff) continue; + const char = String.fromCharCode(code); + if (/[\p{Cc}\p{Zs}\p{Cf}]/u.test(char) && inspectUnicode(char).rows[0].invisibleLabel === null) { + unlabelled.push(`U+${code.toString(16).toUpperCase().padStart(4, "0")}`); + } + } + expect(unlabelled).toEqual([]); + }); + it("falls back to category labels for unlisted Zs or Cf characters", () => { expect(inspectUnicode("\u0600").rows[0]).toMatchObject({ invisibleLabel: "FORMAT CHARACTER", diff --git a/lib/unicode-inspector.ts b/lib/unicode-inspector.ts index 4e54ef3..39afdd7 100644 --- a/lib/unicode-inspector.ts +++ b/lib/unicode-inspector.ts @@ -62,7 +62,9 @@ const INVISIBLE: Record = { 0x206d: { label: "ACTIVATE ARABIC FORM SHAPING", intentional: true }, 0x206e: { label: "NATIONAL DIGIT SHAPES", intentional: true }, 0x206f: { label: "NOMINAL DIGIT SHAPES", intentional: true }, + 0x2800: { label: "BRAILLE PATTERN BLANK" }, 0x3000: { label: "IDEOGRAPHIC SPACE" }, + 0x3164: { label: "HANGUL FILLER", intentional: true }, 0xfeff: { label: "BYTE ORDER MARK (BOM)" }, 0xffa0: { label: "HALFWIDTH HANGUL FILLER", intentional: true }, 0x1d159: { label: "MUSICAL SYMBOL NULL NOTEHEAD" }, @@ -199,7 +201,7 @@ function getInvisibleInfo(cp: number, char: string): { label: string | null; int } const isControl = (cp <= 0x1f && cp !== 0x09 && cp !== 0x0a && cp !== 0x0d) || - (cp >= 0x80 && cp <= 0x9f); + (cp >= 0x7f && cp <= 0x9f); if (isControl) { return { label: `CONTROL (U+${hex4(cp)})`, intentional: false }; } From 403c5af04a6bafa1c008a59a5f3895ade1aa6c14 Mon Sep 17 00:00:00 2001 From: Nalla Prakash <81852531+prakashNalla@users.noreply.github.com> Date: Mon, 2 Nov 2026 14:06:19 +0530 Subject: [PATCH 06/15] fix(uuid): accept a UUID pasted with its quotes (#455) --- lib/__tests__/uuid.test.ts | 3 +++ lib/uuid.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/__tests__/uuid.test.ts b/lib/__tests__/uuid.test.ts index 1a537d6..16637c3 100644 --- a/lib/__tests__/uuid.test.ts +++ b/lib/__tests__/uuid.test.ts @@ -105,6 +105,8 @@ describe("inspectUuid", () => { ["a urn:uuid: prefix", "urn:uuid:3f2504e0-4f89-41d3-9a0c-0305e82c3301"], ["an upper case URN:UUID: prefix", "URN:UUID:3F2504E0-4F89-41D3-9A0C-0305E82C3301"], ["surrounding whitespace", " {3f2504e0-4f89-41d3-9a0c-0305e82c3301}\n"], + ["double quotes", "\"3f2504e0-4f89-41d3-9a0c-0305e82c3301\""], + ["single quotes", "'3f2504e0-4f89-41d3-9a0c-0305e82c3301'"], ])("inspects a UUID with %s the same as the bare value", (_label, text) => { expect(inspectUuid(text)).toEqual(inspectUuid("3f2504e0-4f89-41d3-9a0c-0305e82c3301")); expect(inspectUuid(text)).toMatchObject({ ok: true, version: 4, variant: "rfc" }); @@ -116,6 +118,7 @@ describe("inspectUuid", () => { "{{3f2504e0-4f89-41d3-9a0c-0305e82c3301}}", "urn:uuid:{3f2504e0-4f89-41d3-9a0c-0305e82c3301}", "uuid:3f2504e0-4f89-41d3-9a0c-0305e82c3301", + "\"3f2504e0-4f89-41d3-9a0c-0305e82c3301'", ])("rejects %s", (text) => { expect(inspectUuid(text)).toEqual({ ok: false }); }); diff --git a/lib/uuid.ts b/lib/uuid.ts index a942dc3..f6a36fe 100644 --- a/lib/uuid.ts +++ b/lib/uuid.ts @@ -75,7 +75,7 @@ export function formatUuid( } function unwrapUuid(text: string): string { - const trimmed = text.trim(); + const trimmed = text.trim().replace(/^(["'])([\s\S]*)\1$/, "$2").trim(); if (URN_PREFIX.test(trimmed)) return trimmed.replace(URN_PREFIX, ""); return trimmed.startsWith("{") && trimmed.endsWith("}") ? trimmed.slice(1, -1) : trimmed; } From 876d89e87adab96682c2d573c290c350b7a97689 Mon Sep 17 00:00:00 2001 From: Nalla Prakash <81852531+prakashNalla@users.noreply.github.com> Date: Mon, 2 Nov 2026 17:22:07 +0530 Subject: [PATCH 07/15] fix(csv-formatter): keep every column when a renamed duplicate header is taken (#458) --- lib/__tests__/csv-utils.test.ts | 6 ++++++ lib/csv-utils.ts | 14 ++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/__tests__/csv-utils.test.ts b/lib/__tests__/csv-utils.test.ts index 1bda98b..8b10dc2 100644 --- a/lib/__tests__/csv-utils.test.ts +++ b/lib/__tests__/csv-utils.test.ts @@ -149,6 +149,12 @@ describe("uniqueHeaders", () => { it("suffixes duplicates and fills empty names", () => { expect(uniqueHeaders(["a", "a", "", "b", "a"])).toEqual(["a", "a_2", "column_3", "b", "a_3"]); }); + + it("skips a suffix that another column already uses", () => { + expect(uniqueHeaders(["id", "id", "id_2"])).toEqual(["id", "id_3", "id_2"]); + expect(uniqueHeaders(["a", "", "column_2"])).toEqual(["a", "column_2_2", "column_2"]); + expect(csvToJson(["id", "id", "id_2"], [["1", "2", "3"]])).toEqual([{ id: "1", id_3: "2", id_2: "3" }]); + }); }); describe("formatCsv", () => { diff --git a/lib/csv-utils.ts b/lib/csv-utils.ts index 3e71193..4acb9cc 100644 --- a/lib/csv-utils.ts +++ b/lib/csv-utils.ts @@ -308,12 +308,18 @@ function generatedHeader(columns: number): string[] { /** Make column names unique without changing the first occurrence. */ export function uniqueHeaders(header: string[]): string[] { - const seen = new Map(); + const taken = new Set(header.filter((name) => name.trim() !== "")); + const kept = new Set(); return header.map((name, index) => { + if (name.trim() !== "" && !kept.has(name)) { + kept.add(name); + return name; + } const base = name.trim() === "" ? `column_${index + 1}` : name; - const count = seen.get(base) ?? 0; - seen.set(base, count + 1); - return count === 0 ? base : `${base}_${count + 1}`; + let unique = base; + for (let n = 2; taken.has(unique); n += 1) unique = `${base}_${n}`; + taken.add(unique); + return unique; }); } From d48b25a1231385a74f87f119de138f3261124031 Mon Sep 17 00:00:00 2001 From: Nalla Prakash <81852531+prakashNalla@users.noreply.github.com> Date: Mon, 2 Nov 2026 17:22:19 +0530 Subject: [PATCH 08/15] fix(markdown-preview): put H2 and Task markers at the start of the line (#459) --- .../MarkdownPreviewClient.test.tsx | 14 +++++++++++++ .../MarkdownPreviewClient.tsx | 19 ++++++++++++------ app/guides/make-a-table-in-markdown/page.tsx | 2 +- lib/__tests__/markdown-preview.test.ts | 20 ++++++++++++++++++- lib/markdown-preview.ts | 13 ++++++++++++ 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/app/(tools)/markdown-preview/MarkdownPreviewClient.test.tsx b/app/(tools)/markdown-preview/MarkdownPreviewClient.test.tsx index 98dc1d6..33e7b87 100644 --- a/app/(tools)/markdown-preview/MarkdownPreviewClient.test.tsx +++ b/app/(tools)/markdown-preview/MarkdownPreviewClient.test.tsx @@ -68,4 +68,18 @@ describe("MarkdownPreviewClient", () => { expect(preview?.querySelector("[onerror]")).toBeNull(); expect(preview?.querySelector('a[href^="javascript:"]')).toBeNull(); }); + + it.each([ + ["H2", "My title", "## My title"], + ["Task", "Shopping list", "- [ ] Shopping list"], + ])("%s turns the line under the cursor into its block", (label, text, expected) => { + render(); + const editor = screen.getByLabelText("Markdown input") as HTMLTextAreaElement; + fireEvent.change(editor, { target: { value: text } }); + editor.setSelectionRange(text.length, text.length); + + fireEvent.click(screen.getByRole("button", { name: label })); + + expect(editor).toHaveValue(expected); + }); }); diff --git a/app/(tools)/markdown-preview/MarkdownPreviewClient.tsx b/app/(tools)/markdown-preview/MarkdownPreviewClient.tsx index fb11b17..d17eaf6 100644 --- a/app/(tools)/markdown-preview/MarkdownPreviewClient.tsx +++ b/app/(tools)/markdown-preview/MarkdownPreviewClient.tsx @@ -3,7 +3,7 @@ import { useMemo, useRef, useState, useSyncExternalStore } from "react"; import DOMPurify from "dompurify"; import { CopyButton } from "@/components/shared/CopyButton"; -import { createStandaloneHtml, getMarkdownStats, insertMarkdown, insertTableMarkdown, renderMarkdown } from "@/lib/markdown-preview"; +import { createStandaloneHtml, getMarkdownStats, insertLinePrefix, insertMarkdown, insertTableMarkdown, renderMarkdown } from "@/lib/markdown-preview"; const starter = `# Hello, Markdown!\n\nWrite on the left and see **GitHub-flavored Markdown** rendered here.\n\n- [x] Live preview\n- [ ] Copy sanitized HTML\n\n| Feature | Status |\n| --- | --- |\n| Tables | Ready |\n| Code | \`const answer = 42\` |\n\n~~~ts\nconst greeting = "Hello, world!";\n~~~`; const MAX_IMPORT_BYTES = 1024 * 1024; @@ -42,11 +42,17 @@ export function MarkdownPreviewClient() { const { words, chars, lines, readingMinutes } = getMarkdownStats(markdown); function insert(before: string, after = before, placeholder = "text") { + apply((start, end) => insertMarkdown(markdown, start, end, before, after, placeholder)); + } + + function insertLine(prefix: string, placeholder = "text") { + apply((start, end) => insertLinePrefix(markdown, start, end, prefix, placeholder)); + } + + function apply(edit: (start: number, end: number) => ReturnType) { const input = textareaRef.current; if (!input) return; - const start = input.selectionStart; - const end = input.selectionEnd; - const result = insertMarkdown(markdown, start, end, before, after, placeholder); + const result = edit(input.selectionStart, input.selectionEnd); setMarkdown(result.value); requestAnimationFrame(() => { input.focus(); input.setSelectionRange(result.selectionStart, result.selectionEnd); }); } @@ -101,10 +107,11 @@ export function MarkdownPreviewClient() { return
- {[['Bold','**','**'],['Italic','*','*'],['Code','`','`'],['Link','[','](https://example.com)'],['H2','## ','']].map(([label, before, after]) => )} + {[['Bold','**','**'],['Italic','*','*'],['Code','`','`'],['Link','[','](https://example.com)']].map(([label, before, after]) => )} + - + diff --git a/app/guides/make-a-table-in-markdown/page.tsx b/app/guides/make-a-table-in-markdown/page.tsx index 282ec13..b8a9ea5 100644 --- a/app/guides/make-a-table-in-markdown/page.tsx +++ b/app/guides/make-a-table-in-markdown/page.tsx @@ -186,7 +186,7 @@ export default function MakeATableInMarkdownGuidePage() {
  • A single line break becomes a <br>. The preview is set to turn every newline inside a paragraph into a line break. GitHub does the same in issues, pull requests, comments and discussions, but not in a .md file such as a README, where a single newline does not break the line. So text that looks right in the preview can run together in a README. To force a break in a .md file, end the line with two spaces or a backslash, or use <br>.
  • The HTML is sanitized. Scripts and inline event handlers such as onerror are removed before the preview is shown or the HTML is copied.
  • -
  • There is a toolbar. Bold, Italic, Code, Link, H2, Image, Table and Task add the syntax at the cursor or around the selected text.
  • +
  • There is a toolbar. Bold, Italic, Code, Link, Image and Table add the syntax at the cursor or around the selected text, and H2 and Task turn the current line into a heading or a task.
  • You can take the result with you. Copy HTML copies the sanitized HTML, Copy Markdown copies the source, and Download .md saves it. A switch changes the preview between light and dark, and the bar below shows the word, character and line counts and the reading time.
  • Everything runs in your browser. Nothing you type is uploaded.
diff --git a/lib/__tests__/markdown-preview.test.ts b/lib/__tests__/markdown-preview.test.ts index 95f3454..76cee5c 100644 --- a/lib/__tests__/markdown-preview.test.ts +++ b/lib/__tests__/markdown-preview.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { createStandaloneHtml, getMarkdownStats, insertMarkdown, insertTableMarkdown, renderMarkdown } from "@/lib/markdown-preview"; +import { createStandaloneHtml, getMarkdownStats, insertLinePrefix, insertMarkdown, insertTableMarkdown, renderMarkdown } from "@/lib/markdown-preview"; describe("markdown preview", () => { it("wraps provided content in a complete light HTML document", () => { @@ -65,6 +65,24 @@ describe("markdown preview", () => { it("inserts formatting around the selection", () => expect(insertMarkdown("hello", 0, 5, "**")).toEqual({ value: "**hello**", selectionStart: 2, selectionEnd: 7 })); }); +describe("insertLinePrefix", () => { + it("turns a line with text into a heading or task wherever the cursor is", () => { + expect(insertLinePrefix("My title", 8, 8, "## ")).toEqual({ value: "## My title", selectionStart: 11, selectionEnd: 11 }); + expect(insertLinePrefix("My title", 3, 8, "## ")).toEqual({ value: "## My title", selectionStart: 6, selectionEnd: 11 }); + expect(insertLinePrefix("Intro\nShopping list\nEnd", 10, 10, "- [ ] ", "task").value).toBe("Intro\n- [ ] Shopping list\nEnd"); + expect(renderMarkdown(insertLinePrefix("My title", 8, 8, "## ").value)).toContain(" { + expect(insertLinePrefix("", 0, 0, "## ")).toEqual({ value: "## text", selectionStart: 3, selectionEnd: 7 }); + expect(insertLinePrefix("Intro\n\nEnd", 6, 6, "- [ ] ", "task").value).toBe("Intro\n- [ ] task\nEnd"); + expect(insertLinePrefix("\nEnd", 0, 0, "## ").value).toBe("## text\nEnd"); + expect(insertLinePrefix("A\n\n ", 7, 7, "## ").value).toBe("A\n\n## text"); + expect(insertLinePrefix("A\n\n\t", 4, 4, "## ").value).toBe("A\n\n## text"); + expect(renderMarkdown(insertLinePrefix("A\n\n ", 7, 7, "## ").value)).toContain(" { const starter = "| Header | Header |\n| --- | --- |\n| Cell | Cell |\n"; const count = (text: string, part: string) => text.split(part).length - 1; diff --git a/lib/markdown-preview.ts b/lib/markdown-preview.ts index 6bd679c..cfd45bb 100644 --- a/lib/markdown-preview.ts +++ b/lib/markdown-preview.ts @@ -66,6 +66,19 @@ export function insertMarkdown(markdown: string, start: number, end: number, bef return { value: markdown.slice(0, start) + before + selected + after + markdown.slice(end), selectionStart: start + before.length, selectionEnd: start + before.length + selected.length }; } +/** Puts a line marker such as `## ` at the start of the cursor's line, so a line that has text becomes a heading or task. */ +export function insertLinePrefix(markdown: string, start: number, end: number, prefix: string, placeholder = "text") { + const lineStart = markdown.slice(0, start).lastIndexOf("\n") + 1; + const newline = markdown.indexOf("\n", start); + const lineEnd = newline === -1 ? markdown.length : newline; + if (markdown.slice(lineStart, lineEnd).trim() === "") { + // A blank line: drop its white space, since four spaces or a tab before the marker can make a code block. + const at = lineStart + prefix.length; + return { value: markdown.slice(0, lineStart) + prefix + placeholder + markdown.slice(lineEnd), selectionStart: at, selectionEnd: at + placeholder.length }; + } + return { value: markdown.slice(0, lineStart) + prefix + markdown.slice(lineStart), selectionStart: start + prefix.length, selectionEnd: end + prefix.length }; +} + const STARTER_TABLE = "| Header | Header |\n| --- | --- |\n| Cell | Cell |\n"; /** From 355c7c97902a33b103f1231630eadf6491be9d0a Mon Sep 17 00:00:00 2001 From: Nalla Prakash <81852531+prakashNalla@users.noreply.github.com> Date: Mon, 2 Nov 2026 22:58:50 +0530 Subject: [PATCH 09/15] fix(json-formatter): name XML array items with the same singular as JSON to TypeScript (#461) --- lib/__tests__/json-utils.test.ts | 10 ++++++++++ lib/json-utils.ts | 4 +--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/__tests__/json-utils.test.ts b/lib/__tests__/json-utils.test.ts index c16604f..73e0c1e 100644 --- a/lib/__tests__/json-utils.test.ts +++ b/lib/__tests__/json-utils.test.ts @@ -282,6 +282,16 @@ describe("toXML", () => { expect(result.value).toContain(" x\n y"); }); + it("names the items of common plurals the way JSON to TypeScript does", () => { + const result = toXML('{"categories":["books"],"addresses":["Paris"],"address":["x"]}'); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain(" books"); + expect(result.value).toContain("
Paris
"); + expect(result.value).toContain(" x"); + }); + it("wraps a top-level array of arrays, each sub-array in its own element", () => { const result = toXML("[[1,2],[3]]"); expect(result.ok).toBe(true); diff --git a/lib/json-utils.ts b/lib/json-utils.ts index fd4d598..b34bafc 100644 --- a/lib/json-utils.ts +++ b/lib/json-utils.ts @@ -684,9 +684,7 @@ export function toXML(raw: string, rootTag = "root"): Result { if (node === null) return `${pad}<${tag} nil="true"/>`; if (isLosslessNumber(node)) return `${pad}<${tag}>${node.toString()}`; if (Array.isArray(node)) { - const itemTag = sanitizeTag( - tag.endsWith("s") && tag.length > 1 ? tag.slice(0, -1) : `${tag}Item` - ); + const itemTag = sanitizeTag(singularize(tag)); if (node.length === 0) return `${pad}<${tag}/>`; if (parent === "object") { return node From f4f3ab8a0dfcc1dcae31bba4824527cee2f55cc7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 03:10:35 +0530 Subject: [PATCH 10/15] chore(deps): bump marked from 18.1.1 to 18.1.2 (#465) Bumps [marked](https://github.com/markedjs/marked) from 18.1.1 to 18.1.2. - [Release notes](https://github.com/markedjs/marked/releases) - [Commits](https://github.com/markedjs/marked/compare/v18.1.1...v18.1.2) --- updated-dependencies: - dependency-name: marked dependency-version: 18.1.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 748dcd1..1baed01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "jspdf": "^4.2.1", "lossless-json": "^4.3.3", "lucide-react": "^0.468.0", - "marked": "^18.1.1", + "marked": "^18.1.2", "next": "^16.4.3", "prismjs": "^1.30.0", "react": "^19.3.1", @@ -9258,9 +9258,9 @@ } }, "node_modules/marked": { - "version": "18.1.1", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.1.tgz", - "integrity": "sha512-zEGgrtUG8crk+TrMacJSVABU4DYDDkS8uqgU3ohrn/2fW9C2AEfUrO3m6IhpIpQpl7YcVpiXXC9cSMWDANT3dg==", + "version": "18.1.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.2.tgz", + "integrity": "sha512-DfOnvLAQ6WYeWxWO1u4RyFOeqyHup6dks5PPqnoFiooU7ipRvidO/2j8plhAHIiZg574E9ZlzSjUiPMLAzFkgg==", "license": "MIT", "bin": { "marked": "bin/marked.js" diff --git a/package.json b/package.json index 70a6755..a757c51 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "jspdf": "^4.2.1", "lossless-json": "^4.3.3", "lucide-react": "^0.468.0", - "marked": "^18.1.1", + "marked": "^18.1.2", "next": "^16.4.3", "prismjs": "^1.30.0", "react": "^19.3.1", From 85096207a7b963511f8a12a2778f60304967c559 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:00:00 +0530 Subject: [PATCH 11/15] chore(deps): bump @cfworker/json-schema from 4.1.1 to 4.1.4 (#466) Bumps [@cfworker/json-schema](https://github.com/cfworker/cfworker) from 4.1.1 to 4.1.4. - [Release notes](https://github.com/cfworker/cfworker/releases) - [Commits](https://github.com/cfworker/cfworker/compare/@cfworker/json-schema@4.1.1...@cfworker/json-schema@4.1.4) --- updated-dependencies: - dependency-name: "@cfworker/json-schema" dependency-version: 4.1.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1baed01..ee52dfd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.11.0", "license": "MIT", "dependencies": { - "@cfworker/json-schema": "^4.1.1", + "@cfworker/json-schema": "^4.1.4", "@opennextjs/cloudflare": "^1.20.13", "clsx": "^2.1.1", "dompurify": "^3.4.17", @@ -1451,9 +1451,9 @@ } }, "node_modules/@cfworker/json-schema": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", - "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.4.tgz", + "integrity": "sha512-20rPE8EX0dSg6JGOVe31HpqsLYZTDSlcFJZA5mpZqQxcZ71vh/SXCMX+UuHO1mMcZBDKeb752z+3L8IWa+0Svg==", "license": "MIT" }, "node_modules/@cloudflare/kv-asset-handler": { diff --git a/package.json b/package.json index a757c51..e4ac035 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "lighthouse": "npx --yes @lhci/cli@0.14.x collect && (npx --yes @lhci/cli@0.14.x assert || true) && node scripts/lighthouse-summary.mjs" }, "dependencies": { - "@cfworker/json-schema": "^4.1.1", + "@cfworker/json-schema": "^4.1.4", "@opennextjs/cloudflare": "^1.20.13", "clsx": "^2.1.1", "dompurify": "^3.4.17", From 9c139d5315511e12b58af864d4df8784bb9340d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:00:15 +0530 Subject: [PATCH 12/15] chore(deps-dev): bump the dev-dependencies group with 2 updates (#464) Bumps the dev-dependencies group with 2 updates: [postcss](https://github.com/postcss/postcss) and [wrangler](https://github.com/cloudflare/workers-sdk/tree/HEAD/packages/wrangler). Updates `postcss` from 8.5.31 to 8.5.32 - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.5.31...8.5.32) Updates `wrangler` from 4.152.0 to 4.156.0 - [Release notes](https://github.com/cloudflare/workers-sdk/releases) - [Commits](https://github.com/cloudflare/workers-sdk/commits/wrangler@4.156.0/packages/wrangler) --- updated-dependencies: - dependency-name: postcss dependency-version: 8.5.32 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-dependencies - dependency-name: wrangler dependency-version: 4.156.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 76 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index ee52dfd..e212b11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,7 +41,7 @@ "tailwindcss": "^3.4.1", "typescript": "^5", "vitest": "^4.1.12", - "wrangler": "^4.152.0" + "wrangler": "^4.156.0" }, "engines": { "node": ">=20" @@ -1481,9 +1481,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261019.1.tgz", - "integrity": "sha512-5ZILbAta/fBQvhmAYeCE7dKMMYIr7N9wJEX50fEGLKSIwGtvT0HzSfHNIK/O7cBQ4gHQ2WfIiPClvwcye6E0fA==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20261023.1.tgz", + "integrity": "sha512-lsEx0i7nR0PNG/erUFFHmQmiidEhLfDE5uQmAL9421/y4/ycuxDDOSaxu3VGGYFFPK5sduyUeMmjIlX4CwOBFg==", "cpu": [ "x64" ], @@ -1497,9 +1497,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261019.1.tgz", - "integrity": "sha512-cww2Ab6lVMxpTiEEjKmPI58p5EMo0vYTZSH+3WkqXJdQgc3ES66GuL247+U2B2YWajhhu2tqj7jySrXneU749g==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20261023.1.tgz", + "integrity": "sha512-5e79fHgabdDD8EgmjOhJ1S+xMsf6DbC7fL7IgTBizMnzOp0P2isDn7pYmUxI9AJZE9FVQ4RUilH/I36YNg+GQQ==", "cpu": [ "arm64" ], @@ -1513,9 +1513,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261019.1.tgz", - "integrity": "sha512-4++VRi9vkQROYqrpZYOixIJuVDmujkwrT4kszsxNFlvcmsX6Mr2GumvxIN3gIixS3vJUh3Umdtkthqj5X0S3Xw==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20261023.1.tgz", + "integrity": "sha512-VHQ6jKFSihjR2+x1buby8XTdBLLJztVnYiB7OLOTYO8aDXB+c1Rd2410PdgFuFYc4AgFEk6PCJLyCLL5L1KBnw==", "cpu": [ "x64" ], @@ -1529,9 +1529,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261019.1.tgz", - "integrity": "sha512-75PVtJOwfjScQ53sbLtQXI7D8yP1c21t+WEUPaYbqkzaz4ZqfR5wAd+zuWs8G5Q7kOj2SQIvEfY5Sn8Y+UVrnA==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20261023.1.tgz", + "integrity": "sha512-ilpRHzfDN8YFRM5CLRKuFw40x/LJBS89yVdhdQmxehlVDuBqjKpJB++zPFbTgKRRFaNupgnXc8RHktXAIhgCmQ==", "cpu": [ "arm64" ], @@ -1545,9 +1545,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261019.1.tgz", - "integrity": "sha512-z49fvKTETMCnG6wnMcQwxx9CpryFceiKz04FRjq26EkhCyzRNJ6c3UXhyZz3r5f1CXnnBZc5DcU8ZouPkUJCqw==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20261023.1.tgz", + "integrity": "sha512-279WLVjNsMycAwr0EZNiNEf0l2SY6NUtsN80NwZzbTHzakmiN+WSx4w38muxPm+PAU0oSPPAPXKVD+H6Hya8xw==", "cpu": [ "x64" ], @@ -9363,15 +9363,15 @@ } }, "node_modules/miniflare": { - "version": "5.20261019.2-beta", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261019.2-beta.tgz", - "integrity": "sha512-QNgCA/axizZ7kv7RV3nwbRoz+w4watBlwIsyXjwgYHw4eSw5BrR1F2152s3Kz+Bzy/YxjKFHrZl3LzBRbPgYoQ==", + "version": "5.20261023.3-beta", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20261023.3-beta.tgz", + "integrity": "sha512-thdXzlktu5GE1o/BlmMAPeJ0Cu6Tu5LpMbRlyfRrpVvMRrhd6p52TMqcITCZZLVzt1UO92VGBkLDhLOpryUAyQ==", "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.35.5", - "undici": "7.29.1", - "workerd": "1.20261019.1", + "undici": "7.30.1", + "workerd": "1.20261023.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, @@ -10098,9 +10098,9 @@ } }, "node_modules/postcss": { - "version": "8.5.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", - "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, "funding": [ { @@ -12417,9 +12417,9 @@ } }, "node_modules/workerd": { - "version": "1.20261019.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261019.1.tgz", - "integrity": "sha512-UUYRUq/7DmeCl11YriNbcVbjUZVLtt8uuCXFu+nz77hGFf8d4DEVXoCYtnA+MlBatE4+wxoj47DVqzK3xGBAiw==", + "version": "1.20261023.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20261023.1.tgz", + "integrity": "sha512-NeofJFyaYNpqKpFRJW3ApuonaerPd6/JWlfTtLDYf2A6ygqrDEFnYgEXmc0hhOxqnJsSM+kXJN6VebPojzRrsg==", "hasInstallScript": true, "license": "Apache-2.0", "bin": { @@ -12429,27 +12429,27 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20261019.1", - "@cloudflare/workerd-darwin-arm64": "1.20261019.1", - "@cloudflare/workerd-linux-64": "1.20261019.1", - "@cloudflare/workerd-linux-arm64": "1.20261019.1", - "@cloudflare/workerd-windows-64": "1.20261019.1" + "@cloudflare/workerd-darwin-64": "1.20261023.1", + "@cloudflare/workerd-darwin-arm64": "1.20261023.1", + "@cloudflare/workerd-linux-64": "1.20261023.1", + "@cloudflare/workerd-linux-arm64": "1.20261023.1", + "@cloudflare/workerd-windows-64": "1.20261023.1" } }, "node_modules/wrangler": { - "version": "4.152.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.152.0.tgz", - "integrity": "sha512-lmXTF1sCDEqBaEMpreGczvg/x3AIh1d/qxc4BN7utQ4jEDwdA4YG0L6kr3ev0TtVLHRb6QLySNAOAunwn+dHeA==", + "version": "4.156.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.156.0.tgz", + "integrity": "sha512-kVLAShuLiNgE3jcrmXcymDf/iPKDPrSxACe5ueugW/33HeNB9E9CkKYchudCeBwgmsnl4k+1zt4NO7vqb/fRuQ==", "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.2", "blake3-wasm": "2.1.5", "esbuild": "0.28.2", - "miniflare": "5.20261019.2-beta", + "miniflare": "5.20261023.3-beta", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20261019.1" + "workerd": "1.20261023.1" }, "bin": { "cf-wrangler": "bin/cf-wrangler.js", @@ -12463,7 +12463,7 @@ "fsevents": "2.3.3" }, "peerDependencies": { - "@cloudflare/workers-types": "^5.20261019.1" + "@cloudflare/workers-types": "^5.20261023.1" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { diff --git a/package.json b/package.json index e4ac035..c9029ef 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "tailwindcss": "^3.4.1", "typescript": "^5", "vitest": "^4.1.12", - "wrangler": "^4.152.0" + "wrangler": "^4.156.0" }, "overrides": { "@node-minify/core": { From 24cac50e4a4be67eff60438139c68f20af784793 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2026 11:06:26 +0530 Subject: [PATCH 13/15] chore(deps): bump @opennextjs/cloudflare (#463) Bumps the next group with 1 update in the / directory: [@opennextjs/cloudflare](https://github.com/opennextjs/opennextjs-cloudflare/tree/HEAD/packages/cloudflare). Updates `@opennextjs/cloudflare` from 1.20.13 to 1.20.15 - [Release notes](https://github.com/opennextjs/opennextjs-cloudflare/releases) - [Changelog](https://github.com/opennextjs/opennextjs-cloudflare/blob/main/packages/cloudflare/CHANGELOG.md) - [Commits](https://github.com/opennextjs/opennextjs-cloudflare/commits/@opennextjs/cloudflare@1.20.15/packages/cloudflare) --- updated-dependencies: - dependency-name: "@opennextjs/cloudflare" dependency-version: 1.20.15 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: next ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 396 ++++++++++++++++++++++++---------------------- package.json | 2 +- 2 files changed, 204 insertions(+), 194 deletions(-) diff --git a/package-lock.json b/package-lock.json index e212b11..f83abe3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@cfworker/json-schema": "^4.1.4", - "@opennextjs/cloudflare": "^1.20.13", + "@opennextjs/cloudflare": "^1.20.15", "clsx": "^2.1.1", "dompurify": "^3.4.17", "json5": "^2.2.3", @@ -346,14 +346,14 @@ } }, "node_modules/@aws-sdk/checksums": { - "version": "3.1001.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1001.5.tgz", - "integrity": "sha512-6erto9nK2OZO/woEgmFIaPh5gChgdNd7nL6jgAf8TAXz3/QHC8Pan2TFe9GYp3IxOIKCCGWPylSjtOHKfNzQlw==", + "version": "3.1002.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1002.1.tgz", + "integrity": "sha512-CXNESwkQC9jdrUG6cKXBfudVt1frGcYQykcCYbNclVrsewuGMbo3PlFjRteMe4OftNAqhyc3xC2jTz7SUqLasQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -640,16 +640,16 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.978.5", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.978.5.tgz", - "integrity": "sha512-ruI0AWqcrfL20XDS6SwZIJFPOBZ6N0ADa9nLzWlBhxk1aoUjbUX3JkTR9vEmk7o35XpAHi68ahZ4T6RE2U9XyA==", + "version": "3.979.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.979.1.tgz", + "integrity": "sha512-ayOlV1VhLNMiq8PW9ZkVobG4gkldnUoHjG69yy2b8j+VYFlhz/uAL7ZdYQAoQ+eShtGUdNDnnG5rxDg6yUJrOg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.975.0", - "@aws-sdk/xml-builder": "^3.972.41", + "@aws-sdk/xml-builder": "^3.972.42", "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.36.1", - "@smithy/signature-v4": "^5.7.4", + "@smithy/core": "^3.38.0", + "@smithy/signature-v4": "^5.7.5", "@smithy/types": "^4.19.0", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -659,14 +659,14 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.76.tgz", - "integrity": "sha512-YxmL1Ar7bV9ZkJvtv+lj+MF6le76NnZnbmgySyZuxpZaCv0/1lZ3KxKy6RIj1h9ui2hWnVjZxbmTMeO0y/NrQA==", + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.80.tgz", + "integrity": "sha512-uO5TiSvb6j1w9RTe5z28OaOmsrOxVQUPaC5+wZl50RfOXcrYD/KgnS8I9lfyGXc7sGbkCopK6VnhNIAoLqfewQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -675,16 +675,16 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.78", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.78.tgz", - "integrity": "sha512-3VHknDRngoebsaqyT1iR75b5cBT2zfEtODK6yYmYltljkK1HEAQLs2FEd1IpWqAeOYdMGaG9wikngWLTufY7RA==", + "version": "3.972.82", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.82.tgz", + "integrity": "sha512-vQzlDHlC24Yz42p+rT4NBoP/5VdetkBoujbOLq9h6QYWC8THuZSPYnXIR9OP3XcOrdMgPBBipOrdssfxnIiWUg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/fetch-http-handler": "^5.8.0", - "@smithy/node-http-handler": "^4.12.1", + "@smithy/node-http-handler": "^4.14.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -693,21 +693,21 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.21", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.21.tgz", - "integrity": "sha512-aLx9hbCX6mGAyv4ZZd8UagJ4RASHVJw6BFDSIyuKmLkqNL6H47EjBPK6qaJ4B5TDV6mc+vMa0SJFYyo4mkvVQg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/credential-provider-env": "^3.972.76", - "@aws-sdk/credential-provider-http": "^3.972.78", - "@aws-sdk/credential-provider-login": "^3.972.83", - "@aws-sdk/credential-provider-process": "^3.972.76", - "@aws-sdk/credential-provider-sso": "^3.973.20", - "@aws-sdk/credential-provider-web-identity": "^3.972.82", - "@aws-sdk/nested-clients": "^3.997.50", + "version": "3.973.25", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.25.tgz", + "integrity": "sha512-b5xl71uCRdWsu7P1VeGpiGuCf6zfYETsSNOv8AmYVTrsgbvTJRXdmmOPVfxkvGGVNsiKkGctClQW3jAn0SZLug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-login": "^3.972.87", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", + "@aws-sdk/nested-clients": "^3.997.54", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" @@ -717,15 +717,15 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.83", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.83.tgz", - "integrity": "sha512-9bJU6FjRacF4FK/QHf8Dyxhur7B1grRj8C1Yh4IkpoFW2exQflPdCv0VrbemLW9ZctLNle+AE7TETs9KNvuLYg==", + "version": "3.972.87", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.87.tgz", + "integrity": "sha512-C4ZFmcwM+Zj14Z8huVUk54Gv4uzT1ZdzG9cY3Gdh8FePakq7Bss1OFsgZjAhWFOkFOGpBvFBeu0OWjWf8HNKRQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -734,19 +734,19 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.88", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.88.tgz", - "integrity": "sha512-AG0KID5qhQPzrFkh5b0Cws1Amytvzo0L0Xj6hMgJXEwD6ZXAqninDhSNAWJiN5zU4YVVZnuidnqwPwYJ04IamA==", + "version": "3.972.92", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.92.tgz", + "integrity": "sha512-jC3zmOo7Qxlk+oBivvSsS1Ph7sFGm0QfuhqDGbqgRXi4cxUYMG+dWtBwS2XYYXgc8TIVXJiwgQ65sbNpbYupTA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.76", - "@aws-sdk/credential-provider-http": "^3.972.78", - "@aws-sdk/credential-provider-ini": "^3.973.21", - "@aws-sdk/credential-provider-process": "^3.972.76", - "@aws-sdk/credential-provider-sso": "^3.973.20", - "@aws-sdk/credential-provider-web-identity": "^3.972.82", + "@aws-sdk/credential-provider-env": "^3.972.80", + "@aws-sdk/credential-provider-http": "^3.972.82", + "@aws-sdk/credential-provider-ini": "^3.973.25", + "@aws-sdk/credential-provider-process": "^3.972.80", + "@aws-sdk/credential-provider-sso": "^3.973.24", + "@aws-sdk/credential-provider-web-identity": "^3.972.86", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" @@ -756,14 +756,14 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.76", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.76.tgz", - "integrity": "sha512-pdhKFTXv1zPYhh92MTlp0jVbLWvUEV9v+rziuEkoskGGQ2iJskxHTO38nA8UAbT/N4IM3n8OZJ9rIY5IBO15/A==", + "version": "3.972.80", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.80.tgz", + "integrity": "sha512-xqfixO5aclje7tbQJDias+iNND5wp/3QZixCdZcVQpgWPTZ0V5DfTW9A804A5Z7ezu7sH3WRO+vzUjtiJBA8yg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -772,16 +772,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.20.tgz", - "integrity": "sha512-DlxrAB3MnjNLx/da7b89W+3DyJQYso/q7Ns15MsST2IV2nkAYQOYTUCaffNSjgrpMP99Dm2mV2jxzcbyF94ZfA==", + "version": "3.973.24", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.24.tgz", + "integrity": "sha512-nlg3BKG03Cgzvv/dMnhxvDJPhAhQSpaN0RUdwgU7ziwkasoev7iI8el8Xy4Rd5sqzgaDUbV269fOXck5SargqA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/nested-clients": "^3.997.50", - "@aws-sdk/token-providers": "3.1155.0", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", + "@aws-sdk/token-providers": "3.1161.0", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -790,15 +790,15 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.82", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.82.tgz", - "integrity": "sha512-LUo46IqPTJ4xKBsrLuoaxiv/D2GTBCcFDMD1y3jKIqPR/p7Rw14RJFZ0iRWftqZtyLPQX9JMGnSi3mBu4y/pNA==", + "version": "3.972.86", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.86.tgz", + "integrity": "sha512-zKTyLJBEyfQBVlYqT6Jt+mYV4r8kGbWM7dn6v4Y4MRARizhYafjDXP/iMu76UMRhToSPNmIKX1XAb24gtpBFIA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -807,13 +807,13 @@ } }, "node_modules/@aws-sdk/dynamodb-codec": { - "version": "3.973.50", - "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.973.50.tgz", - "integrity": "sha512-1jn3q2Ik/qUjPw1su5Abu6nzeDutzYbNUYKt+AqUh+45rb+MVWhgP1QWarTuxrm4MJKcSLVHLCrvi0rY5jmJZw==", + "version": "3.973.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.973.54.tgz", + "integrity": "sha512-+Z4tyUabT/ATvXw7Bpo2j8Sh5ot5XqhvJYwNqHaKEib1KIgrVByZKSw7ndeAMOHjMAhygZnH09N9D1ygD/uTRg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@smithy/core": "^3.36.1", + "@aws-sdk/core": "^3.979.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -835,12 +835,12 @@ } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.54.tgz", - "integrity": "sha512-/865W4kY+ZENpo4ZELvNNVGjjrDW5YFtY7+QdvtoKpzRWmC0LFNuXJb9BHpPVsqeUosZ7QZOLZx1WkElPK4QIA==", + "version": "3.972.58", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.58.tgz", + "integrity": "sha512-pwD0JKJncGoOLAxHKAGFd6LrUSqodhN0CGUfHvQKpVFklylDcA27BkoGO+A/n8UTMbcDMRg1K/E003W49LdGHQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.81", + "@aws-sdk/middleware-sdk-s3": "^3.972.85", "tslib": "^2.6.2" }, "engines": { @@ -848,14 +848,14 @@ } }, "node_modules/@aws-sdk/middleware-endpoint-discovery": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.34.tgz", - "integrity": "sha512-hwPWSvttg+bEpcM9e8HdVJF/oz79xInjnou23+GdsbDv75DVDLGhn2XhIbuNghp5SqyjtngXEI+K22Vq3VvSxw==", + "version": "3.972.37", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.972.37.tgz", + "integrity": "sha512-+KAK020JY95Q7Swwo5b9pl9S11VPuNLqecCW5bzkksWiU5pmI85lAloOD4qlIKIA/P8FlrRJ60XJ6+lnDQK/DQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/endpoint-cache": "^3.972.11", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -864,12 +864,12 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.972.50", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.50.tgz", - "integrity": "sha512-Z7qRC0/wzsUdkZh7nGJahnX558jL/jG45fZb1Mql/w/10qcDY2AxnEoCC78l6n5aQbMC1x7j4TF0fd5OwpZIDQ==", + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.54.tgz", + "integrity": "sha512-zj8L/3ukRBMI9+49BZ53shhVc9L47GIGBZlZu4zwmDc0mGBmtOom/ewIkzHHNgSLxW5q5KEOfpSf7HDKn8yXXA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.81", + "@aws-sdk/middleware-sdk-s3": "^3.972.85", "tslib": "^2.6.2" }, "engines": { @@ -877,12 +877,12 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.974.60", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.60.tgz", - "integrity": "sha512-V9rcPj4Qh8OYfO3RwNTfJVfAlxlnFxE71GkWxl++8yEMS7dRViKQaQm56P79Z97el2xmFzet78E+r9XxyuyD0A==", + "version": "3.974.64", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.974.64.tgz", + "integrity": "sha512-DjDQSetkeXiRyOB6RaR/FFTElcIIG6v45tiY6pd4gGLDem4c44EGKWYFc1SQ7Xq4c2xPuuAv3lPzz2zrOoXlYg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/checksums": "^3.1001.5", + "@aws-sdk/checksums": "^3.1002.1", "tslib": "^2.6.2" }, "engines": { @@ -890,12 +890,12 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.51.tgz", - "integrity": "sha512-s1Orgn9zi4H9hKO6iLmF9vOd3rJZ013aCcp5pfhOpqNoX6PdD0Z2GMIKQLcRzqjynpi64PI0UQXmQDiqWtK4Nw==", + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.55.tgz", + "integrity": "sha512-/zR8YmRi9OO6voQ1sxs4t5hkmi28m8wHvQ6dYJY1c6f1cEACvpoGE3+xIwnjZVjuiy3i7vPilXFleqA46llCaQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -903,12 +903,12 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.972.47", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.47.tgz", - "integrity": "sha512-CYep3qdXorbWNtblXPt6hGuRjrn2ZQVAOdgjyw43/jgiD0nVGB5lPs/HsCf2Ex1XexXtFfrXqDELomFb75k1fA==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.51.tgz", + "integrity": "sha512-3mJ9F4ttZvrPoGVeT9NvwQ//TRcX3iCB0caZZ0M72OD1/owT6LlCiRdqppNIWN7a/jS5RcNY57wDbQg43hY+Yw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.81", + "@aws-sdk/middleware-sdk-s3": "^3.972.85", "tslib": "^2.6.2" }, "engines": { @@ -916,12 +916,12 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.50", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.50.tgz", - "integrity": "sha512-CBF3yNK3aKSU606aM83P5DsiTiNcVwnA1ujV6OP17BOHrwO6/P2rJZjV3hNDbqMCqVTHtBakGkJfL2JSMwbpDQ==", + "version": "3.972.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.54.tgz", + "integrity": "sha512-23Ptwj16CUYpJElGYh+svuV3q8h8ho9spejhjrgK+oQgb4yvqH/W/QxAejR1omBELb3zzx3QY9yEbR/7B/kYTg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -929,12 +929,12 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.52", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.52.tgz", - "integrity": "sha512-PL8AcO1mqmPiq4XYcqcZ9fN49AMLU8a72YOtmyXLOMb4B1zFqAifNH8qjO6IJdY6rF3l6dbAT/Wa84LS2ISvlA==", + "version": "3.972.56", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.56.tgz", + "integrity": "sha512-yd3G90KqbzFkoaST0KtGU2uMPUNReGUrLGxfwWVKOUdC5R+vyp17DX7h1B6sI9cIgzpnjDsWU2mStkTJKI7jTA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -942,15 +942,15 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.81", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.81.tgz", - "integrity": "sha512-jfanv1pV0sJFZw6/DPiNjvK7B478god0zOum5yIWo+/+1Gy+CSXWpGBWx+3nBJyDvjcu/2kUMr4ADUp9BHH34Q==", + "version": "3.972.85", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.85.tgz", + "integrity": "sha512-Kyl2G6zhIS9qFoeLdlxvXFrWyc6g9D9ZCIRJnGkkfX6JZhxCZB5ebXBc7GKEeetrnhXcail6ALZT0lKbojcaNA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/signature-v4-multi-region": "^3.996.49", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -959,13 +959,13 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.49", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.49.tgz", - "integrity": "sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==", + "version": "3.996.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.51.tgz", + "integrity": "sha512-+aHvUugVBuMPXxgkPfWnTMyGdE/yWS8GHi5+qQ37HowS0YqzrbuaDLznj+Dtmtu5jgCdqz36gXmujOToUhNNWw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.975.0", - "@smithy/signature-v4": "^5.7.4", + "@smithy/signature-v4": "^5.7.5", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -974,13 +974,13 @@ } }, "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.46.tgz", - "integrity": "sha512-xXUZj3pl5rc2F2cf/n00WZRF4eI19gL5xr1cfqN882CJ6j53RAOt3GRLGOuFWz1iRgGm2OOVgpHqBDz/MXAP8Q==", + "version": "3.972.48", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.48.tgz", + "integrity": "sha512-GNzuBLwvUUV++svohxeAj1sf4AgA2gOevKTYHRKIDPpVD30YoBiNBb+X0L28b9KazqHhG6WilyiOE4XDmZeHeg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -989,12 +989,12 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.972.47", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.47.tgz", - "integrity": "sha512-RwYHFMX63Nmuot+tuIEqf6AQVC5w0l9c6eznLI6MO31zXYWmvvqv/rRX/1nUn0m6Ip4OX5qRCa1ZHxyHwlm04g==", + "version": "3.972.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.51.tgz", + "integrity": "sha512-/W0UjJQDI1XF2k0Z8hkdds7/WoGRfcjFVVCKjSXosf9nBLfREAX8zlmpEM/8AAJeTvvvAi3SHeueMejbbDAhhA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "^3.972.81", + "@aws-sdk/middleware-sdk-s3": "^3.972.85", "tslib": "^2.6.2" }, "engines": { @@ -1002,12 +1002,12 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.80", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.80.tgz", - "integrity": "sha512-PvETLhb+u7hWRTZOt1ZyRnwyzGU0v6abk+69uFbcZ8wpqsoAtudd/wXbUg/8Yu09fD8kS6VqWPHFiZy3ai3cuA==", + "version": "3.972.84", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.84.tgz", + "integrity": "sha512-XvF9GVEEOAHyo8zlDQBt1fyfX61W3daprMS1kJrFpuri4Lpvd6WDlE7wK3LUrUCS85hIu83sTC8RRx/2TpdjwA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -1015,17 +1015,17 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.50", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.50.tgz", - "integrity": "sha512-nKb8qu4QsnpZTq/6JIXc72mzuzfh7BK4sUQkpQznPec2z4W7Ob2xYkcM4i5e1yGTBnbJVeFv/0sf3jcz85hclA==", + "version": "3.997.54", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.54.tgz", + "integrity": "sha512-/OczlavsS7U5sOodRO10Ug4R/AKFVZiXTstKipWe68EfjPkmdDFNXNl9FCJyXWkg1DG6RBm3qAuVB8FNW9O8KQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/signature-v4-multi-region": "^3.996.49", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.51", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/fetch-http-handler": "^5.8.0", - "@smithy/node-http-handler": "^4.12.1", + "@smithy/node-http-handler": "^4.14.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -1034,13 +1034,13 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.49", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.49.tgz", - "integrity": "sha512-nvuAZNRrRRVCiARRKsYlQ0VnXiEQiq7vXPH+UA+LSo+kN3Djl9eo/V2r0udkw85PnsNshmv2Em1clld5M3j67w==", + "version": "3.996.51", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.51.tgz", + "integrity": "sha512-+aHvUugVBuMPXxgkPfWnTMyGdE/yWS8GHi5+qQ37HowS0YqzrbuaDLznj+Dtmtu5jgCdqz36gXmujOToUhNNWw==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.975.0", - "@smithy/signature-v4": "^5.7.4", + "@smithy/signature-v4": "^5.7.5", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -1049,12 +1049,12 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.54.tgz", - "integrity": "sha512-W1fP3XL+Wu9Cd3hBxFiin9x1ufPOzBMIk+ggd1uNLVm7IguJS+SE6gkm53JgF5o4c/APtk9wv4QgS70r8O5ELA==", + "version": "3.972.58", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.58.tgz", + "integrity": "sha512-J6sdO8NybNf+mziF4PNskFOnMPY8dy4SA0hofHb8p3hOIrmyiwXtXHFjHkCKhIBuQPeFA9zAsfcNGLoITQJrxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -1079,15 +1079,15 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.1155.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1155.0.tgz", - "integrity": "sha512-iIcQnYLhWuq9qOLQbIWFrxF3PF0svVeZvHnUS4DUnO4zUFmFHIyGyjqrntXG+e/5PUgfavV0pYfDIKHxTzN2LA==", + "version": "3.1161.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1161.0.tgz", + "integrity": "sha512-ypbXSofNcSHwf/KA40bFCXJb0MLaAKrfT7sot4dFaA+2lsuXNah4zLRJIAU7Qmvs9PFJ6rnY0bZFEC+mE7attA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", - "@aws-sdk/nested-clients": "^3.997.50", + "@aws-sdk/core": "^3.979.1", + "@aws-sdk/nested-clients": "^3.997.54", "@aws-sdk/types": "^3.975.0", - "@smithy/core": "^3.36.1", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -1137,22 +1137,22 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.51.tgz", - "integrity": "sha512-TJCBtOq0sUXWOCzxk8NAI5T2GIzZ8I2juAVvNi1MT8kAAGAXQyyCbvl0kAehHG0gS+P44wTn/ajxfFmf3QQ+3g==", + "version": "3.972.55", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.55.tgz", + "integrity": "sha512-S/h10N8J7nVEA2g5YNbc0F/sAQV7tKFkELqR9pLu6HAe9VcvkSlbzZxJwoMM5fSvoMhox+LYpHB+Hw5DbODE+g==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.973.66", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.66.tgz", - "integrity": "sha512-mVHJnyJsyqVLI7iq3d5+uux27RtH/auRwVDb+26XRkN/k/TW3wfbxDi2i8904Gnvf7YZlg9yljM1kFVFqx2CWw==", + "version": "3.973.70", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.70.tgz", + "integrity": "sha512-z6YNhIWKxySFzVv0mvtUTZ9cMbrJ16PpHupNUYMCcqU4oEoGKoK4j+hHPEVmQS84xngKbbD8JiI6kjIXJuiCnw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.978.5", + "@aws-sdk/core": "^3.979.1", "tslib": "^2.6.2" }, "engines": { @@ -1160,9 +1160,9 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.41", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.41.tgz", - "integrity": "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ==", + "version": "3.972.42", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.42.tgz", + "integrity": "sha512-smX8/gNP8Kjq+CWTBMZQLQ912vOQjiWExzwzvMO+eujPB5RY8IbtF0AGdUUXHKN7KoDWK9dIcuW0WG269kiP6w==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.19.0", @@ -2791,9 +2791,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.4.0.tgz", - "integrity": "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", "license": "MIT", "engines": { "node": ">=12" @@ -3304,9 +3304,9 @@ } }, "node_modules/@opennextjs/aws": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@opennextjs/aws/-/aws-4.1.12.tgz", - "integrity": "sha512-tZm1rBTRY6MVOOPCxvx0oZIg3b2Z5u0TzDm9D4Wy1cZpMp8Rjfex20gRQeEn0o6dsXpVQCE8q65TjsCFJA5Fpw==", + "version": "4.1.14", + "resolved": "https://registry.npmjs.org/@opennextjs/aws/-/aws-4.1.14.tgz", + "integrity": "sha512-jV+rxEi5dNxnQbKBftWuIppVvemMrbfaUj0tXINJMEzX7crk1pkmsMwudgk2kgyak1T03/GSMSEpE8nzOKrkDA==", "license": "MIT", "dependencies": { "@ast-grep/napi": "^0.45.3", @@ -3331,7 +3331,13 @@ "open-next": "dist/index.js" }, "peerDependencies": { + "diff": "^9.0.0", "next": ">=15.5.28 <16 || >=16.4.1" + }, + "peerDependenciesMeta": { + "diff": { + "optional": true + } } }, "node_modules/@opennextjs/aws/node_modules/chalk": { @@ -3347,14 +3353,14 @@ } }, "node_modules/@opennextjs/cloudflare": { - "version": "1.20.13", - "resolved": "https://registry.npmjs.org/@opennextjs/cloudflare/-/cloudflare-1.20.13.tgz", - "integrity": "sha512-Rhcvsx/PlQzOslleCBttG+r2o16lOFazKbULxiN33CYexm8S1Fd00mn3tU2w76PIaqO9evzgVKInOpR/fTMc0Q==", + "version": "1.20.15", + "resolved": "https://registry.npmjs.org/@opennextjs/cloudflare/-/cloudflare-1.20.15.tgz", + "integrity": "sha512-Ft7GdS3oDnZ5cZpAowSqpFsjHuZCZ9zBcTjJwYeJGa7Uq32JG3DOlLXh8GW+gsMVWXTz8YTL20sMxG1u+k4NwQ==", "license": "MIT", "dependencies": { "@ast-grep/napi": "^0.45.3", "@dotenvx/dotenvx": "^2.33.0", - "@opennextjs/aws": "4.1.12", + "@opennextjs/aws": "4.1.14", "ci-info": "^4.2.0", "cloudflare": "^4.4.1", "comment-json": "^4.5.1", @@ -3730,9 +3736,9 @@ } }, "node_modules/@smithy/core": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.36.2.tgz", - "integrity": "sha512-oRw0rB9QSUiCZfEs23G5F4Af+b2rQt1lItIICWu5IejFNd+uPM8SWqiKLMpQ6bX1Q7slsLbjuoE2niErFtkVPQ==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.38.1.tgz", + "integrity": "sha512-U/mB2XiwTLaf+NSgLSWwJfptsMT/H8aBi0aTAA/E5e2/TagFeGCqAyhKGG0w0A9IWTkm4qYEmLoD/HB6aYfnRA==", "license": "Apache-2.0", "dependencies": { "@smithy/types": "^4.19.0", @@ -3965,13 +3971,13 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.12.1.tgz", - "integrity": "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.14.1.tgz", + "integrity": "sha512-/T6E1jzCL0fErS9uq8JqES/5yYQQ4ppVmmYHwXw2nlov8KFzLb+3QqHR5iAun1BsuAB50IPxZliV8YMgghLwhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.33.3", - "@smithy/types": "^4.18.0", + "@smithy/core": "^3.38.1", + "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, "engines": { @@ -3992,12 +3998,12 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.4.tgz", - "integrity": "sha512-tHy0K0VtqNd5Y7Y41h0a0Lhh0L1GzC08dTWg0F7vRJWFtTENg7IZikf3wQkanYIRdb7ngoIPMTmqgUi401fEeQ==", + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.7.5.tgz", + "integrity": "sha512-WPjdr5oWOk1LArpI4pavu+fSo+/evi0Re1olxGvqwuZopd4gYR1UGCi+Soyn/IjmafqbWs+ZC+3AKxJS3ZpwUg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.35.0", + "@smithy/core": "^3.38.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" }, @@ -5682,9 +5688,9 @@ } }, "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.15.0.tgz", + "integrity": "sha512-xic6KSZglg/I4ymp45fdM4GWfYpBK79Fxd/TOvJktlaPgQqqDxvmGwOPZl2V0dyeS3Lj5c180bLhXF43iyAjZQ==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -6482,12 +6488,16 @@ "license": "MIT" }, "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.1.tgz", + "integrity": "sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/enquirer": { @@ -10376,9 +10386,9 @@ } }, "node_modules/qs": { - "version": "6.16.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.1.tgz", - "integrity": "sha512-5Px6J7rfHBAGUhNQ3es4YzcOdBBw0SDcVtThJml4RdnKw4tKs2epA0kPZ+K8X+WiHktT93RHVcQ/3ym5aTCgtg==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", diff --git a/package.json b/package.json index c9029ef..aa15714 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "@cfworker/json-schema": "^4.1.4", - "@opennextjs/cloudflare": "^1.20.13", + "@opennextjs/cloudflare": "^1.20.15", "clsx": "^2.1.1", "dompurify": "^3.4.17", "json5": "^2.2.3", From aa31a81cf85d2a7318369e4294aa0b8a7d3152dc Mon Sep 17 00:00:00 2001 From: Bhushan Agrawal Date: Wed, 4 Nov 2026 11:21:55 +0530 Subject: [PATCH 14/15] fix(json-formatter): singularize sibilant and ambiguous -ses plurals in xml and typescript (#467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(json-formatter): singularize sibilant and ambiguous -ses plurals in XML and TypeScript (#462) singularize() handled -ies and generic -ses but mis-singularized sibilant plurals (matches → matche, boxes → boxe, quizzes → quizz) and ambiguous -ses words (cases → cas). Add rules for -ches, -shes, -xes, -zes and doubled-consonant -zzes, and make the -ses case strip "es" only when the stem is long enough to be a real singular ending in s (addresses → address, statuses → status), otherwise strip just "s" (cases → case). Preserves categories → category, users → user, boss → bossItem, root → rootItem. * fix(json-formatter): refine singularization for -zes and ambiguous -ses plurals (#462) - Only strip "es" on -zzes (e.g. quizzes -> quiz), keeping sizes -> size, prizes -> prize, mazes -> maze intact - For ambiguous -ses, remove "es" only when the resulting stem ends in ss, tus, rus, nus, pus, or bus; otherwise strip only trailing "s" - Keep -ches, -shes, and -xes rules (caches -> cach) - Add regression tests covering direct singularize(), toXML(), and toTypeScript() conversions --- lib/__tests__/json-utils.test.ts | 224 +++++++++++++++++++++++++++++++ lib/json-utils.ts | 18 ++- 2 files changed, 238 insertions(+), 4 deletions(-) diff --git a/lib/__tests__/json-utils.test.ts b/lib/__tests__/json-utils.test.ts index 73e0c1e..1f889c4 100644 --- a/lib/__tests__/json-utils.test.ts +++ b/lib/__tests__/json-utils.test.ts @@ -12,6 +12,7 @@ import { parseJson5Compat, diffLines, validate, + singularize, SAMPLE_JSON, } from "../json-utils"; @@ -292,6 +293,90 @@ describe("toXML", () => { expect(result.value).toContain(" x"); }); + it("singularizes sibilant plurals (-ches, -shes, -xes, -zes)", () => { + const result = toXML( + '{"matches":["a"],"branches":["b"],"hashes":["c"],"boxes":["d"],"fixes":["e"],"quizzes":["f"]}', + ); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("a"); + expect(result.value).toContain("b"); + expect(result.value).toContain("c"); + expect(result.value).toContain("d"); + expect(result.value).toContain("e"); + expect(result.value).toContain("f"); + }); + + it("singularizes ambiguous -ses cases correctly", () => { + const result = toXML('{"cases":["x"],"addresses":["y"],"statuses":["z"]}'); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("x"); + expect(result.value).toContain("
y
"); + expect(result.value).toContain("z"); + }); + + it("singularizes ambiguous -ses words based on stem endings", () => { + const stems = toXML( + '{"addresses":["a"],"classes":["b"],"statuses":["c"],"viruses":["d"],"bonuses":["e"],"campuses":["f"],"buses":["g"]}', + ); + expect(stems.ok).toBe(true); + if (stems.ok) { + expect(stems.value).toContain("
a
"); + expect(stems.value).toContain("b"); + expect(stems.value).toContain("c"); + expect(stems.value).toContain("d"); + expect(stems.value).toContain("e"); + expect(stems.value).toContain("f"); + expect(stems.value).toContain("g"); + } + + const nonStems = toXML( + '{"cases":["a"],"causes":["b"],"houses":["c"],"responses":["d"],"courses":["e"],"releases":["f"],"licenses":["g"],"databases":["h"]}', + ); + expect(nonStems.ok).toBe(true); + if (nonStems.ok) { + expect(nonStems.value).toContain("a"); + expect(nonStems.value).toContain("b"); + expect(nonStems.value).toContain("c"); + expect(nonStems.value).toContain("d"); + expect(nonStems.value).toContain("e"); + expect(nonStems.value).toContain("f"); + expect(nonStems.value).toContain("g"); + expect(nonStems.value).toContain("h"); + } + }); + + it("handles -zes plurals and caches appropriately in XML", () => { + const result = toXML( + '{"sizes":["s"],"prizes":["p"],"mazes":["m"],"quizzes":["q"],"caches":["c"]}', + ); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("s"); + expect(result.value).toContain("p"); + expect(result.value).toContain("m"); + expect(result.value).toContain("q"); + expect(result.value).toContain("c"); + }); + + it("preserves existing singularization for -ies, simple -s, and non-plural keys", () => { + const result = toXML( + '{"categories":["a"],"users":["b"],"boss":["c"],"root":["d"],"tags":["e"]}', + ); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("a"); + expect(result.value).toContain("b"); + expect(result.value).toContain("c"); + expect(result.value).toContain("d"); + expect(result.value).toContain("e"); + }); + it("wraps a top-level array of arrays, each sub-array in its own element", () => { const result = toXML("[[1,2],[3]]"); expect(result.ok).toBe(true); @@ -765,6 +850,145 @@ describe("toTypeScript array items", () => { expect(transpile.diagnostics ?? []).toHaveLength(0); } }); + + it("singularizes sibilant plural array names in interfaces", () => { + const result = toTypeScript('{"matches":[{"x":1}],"branches":[{"y":2}],"boxes":[{"z":3}]}'); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("export interface Match {"); + expect(result.value).toContain("matches: Match[];"); + expect(result.value).toContain("export interface Branch {"); + expect(result.value).toContain("branches: Branch[];"); + expect(result.value).toContain("export interface Box {"); + expect(result.value).toContain("boxes: Box[];"); + }); + + it("singularizes ambiguous -ses array names in interfaces", () => { + const result = toTypeScript('{"cases":[{"a":1}],"addresses":[{"b":2}],"statuses":[{"c":3}]}'); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("export interface Case {"); + expect(result.value).toContain("cases: Case[];"); + expect(result.value).toContain("export interface Address {"); + expect(result.value).toContain("addresses: Address[];"); + expect(result.value).toContain("export interface Status {"); + expect(result.value).toContain("statuses: Status[];"); + }); + + it("singularizes -ses and -zes array names in interfaces", () => { + const result = toTypeScript( + JSON.stringify({ + classes: [{ id: 1 }], + viruses: [{ id: 2 }], + bonuses: [{ id: 3 }], + campuses: [{ id: 4 }], + buses: [{ id: 5 }], + causes: [{ id: 6 }], + houses: [{ id: 7 }], + responses: [{ id: 8 }], + courses: [{ id: 9 }], + releases: [{ id: 10 }], + licenses: [{ id: 11 }], + databases: [{ id: 12 }], + sizes: [{ id: 13 }], + prizes: [{ id: 14 }], + mazes: [{ id: 15 }], + quizzes: [{ id: 16 }], + }), + ); + expect(result.ok).toBe(true); + if (!result.ok) return; + + expect(result.value).toContain("export interface Class {"); + expect(result.value).toContain("classes: Class[];"); + expect(result.value).toContain("export interface Virus {"); + expect(result.value).toContain("viruses: Virus[];"); + expect(result.value).toContain("export interface Bonus {"); + expect(result.value).toContain("bonuses: Bonus[];"); + expect(result.value).toContain("export interface Campus {"); + expect(result.value).toContain("campuses: Campus[];"); + expect(result.value).toContain("export interface Bus {"); + expect(result.value).toContain("buses: Bus[];"); + expect(result.value).toContain("export interface Cause {"); + expect(result.value).toContain("causes: Cause[];"); + expect(result.value).toContain("export interface House {"); + expect(result.value).toContain("houses: House[];"); + expect(result.value).toContain("export interface Response {"); + expect(result.value).toContain("responses: Response[];"); + expect(result.value).toContain("export interface Course {"); + expect(result.value).toContain("courses: Course[];"); + expect(result.value).toContain("export interface Release {"); + expect(result.value).toContain("releases: Release[];"); + expect(result.value).toContain("export interface License {"); + expect(result.value).toContain("licenses: License[];"); + expect(result.value).toContain("export interface Database {"); + expect(result.value).toContain("databases: Database[];"); + expect(result.value).toContain("export interface Size {"); + expect(result.value).toContain("sizes: Size[];"); + expect(result.value).toContain("export interface Prize {"); + expect(result.value).toContain("prizes: Prize[];"); + expect(result.value).toContain("export interface Maze {"); + expect(result.value).toContain("mazes: Maze[];"); + expect(result.value).toContain("export interface Quiz {"); + expect(result.value).toContain("quizzes: Quiz[];"); + }); +}); + +describe("singularize", () => { + it("singularizes all required maintainer corrections and preserves existing rules", () => { + // Sibilant plurals: -ches, -shes, -xes + expect(singularize("matches")).toBe("match"); + expect(singularize("branches")).toBe("branch"); + expect(singularize("hashes")).toBe("hash"); + expect(singularize("boxes")).toBe("box"); + expect(singularize("fixes")).toBe("fix"); + expect(singularize("caches")).toBe("cach"); + + // -zes handling: -zzes vs non-doubled + expect(singularize("quizzes")).toBe("quiz"); + expect(singularize("sizes")).toBe("size"); + expect(singularize("prizes")).toBe("prize"); + expect(singularize("mazes")).toBe("maze"); + + // Ambiguous -ses: stem ends in ss, tus, rus, nus, pus, bus -> remove es + expect(singularize("addresses")).toBe("address"); + expect(singularize("classes")).toBe("class"); + expect(singularize("statuses")).toBe("status"); + expect(singularize("viruses")).toBe("virus"); + expect(singularize("bonuses")).toBe("bonus"); + expect(singularize("campuses")).toBe("campus"); + expect(singularize("buses")).toBe("bus"); + + // Ambiguous -ses: stem does not end in those endings -> remove trailing s + expect(singularize("cases")).toBe("case"); + expect(singularize("causes")).toBe("cause"); + expect(singularize("houses")).toBe("house"); + expect(singularize("responses")).toBe("response"); + expect(singularize("courses")).toBe("course"); + expect(singularize("releases")).toBe("release"); + expect(singularize("licenses")).toBe("license"); + expect(singularize("databases")).toBe("database"); + + // Preserved rules + expect(singularize("categories")).toBe("category"); + expect(singularize("users")).toBe("user"); + expect(singularize("boss")).toBe("bossItem"); + expect(singularize("root")).toBe("rootItem"); + + // PascalCase support for TypeScript interface naming + expect(singularize("Addresses")).toBe("Address"); + expect(singularize("Classes")).toBe("Class"); + expect(singularize("Statuses")).toBe("Status"); + expect(singularize("Viruses")).toBe("Virus"); + expect(singularize("Bonuses")).toBe("Bonus"); + expect(singularize("Campuses")).toBe("Campus"); + expect(singularize("Buses")).toBe("Bus"); + expect(singularize("Cases")).toBe("Case"); + expect(singularize("Sizes")).toBe("Size"); + expect(singularize("Quizzes")).toBe("Quiz"); + }); }); /* ----------------------------------------------------------------------- diff --git a/lib/json-utils.ts b/lib/json-utils.ts index b34bafc..83ffa3f 100644 --- a/lib/json-utils.ts +++ b/lib/json-utils.ts @@ -237,10 +237,20 @@ function pascalCase(s: string): string { return /^[0-9]/.test(result) ? `_${result}` : result; } -function singularize(name: string): string { - if (/ies$/.test(name)) return name.replace(/ies$/, "y"); - if (/ses$/.test(name)) return name.replace(/es$/, ""); - if (/s$/.test(name) && !/ss$/.test(name)) return name.replace(/s$/, ""); +export function singularize(name: string): string { + if (/ies$/i.test(name)) return name.replace(/ies$/i, "y"); + // Sibilant plurals with a doubled consonant: quizzes → quiz + if (/zzes$/i.test(name)) return name.replace(/zes$/i, ""); + // Sibilant plurals: matches → match, branches → branch, hashes → hash, boxes → box, fixes → fix + if (/(ches|shes|xes)$/i.test(name)) return name.replace(/es$/i, ""); + // Ambiguous -ses: remove "es" only when the stem ends in ss, tus, rus, nus, pus, or bus; + // otherwise remove only the trailing "s". + if (/ses$/i.test(name)) { + const stem = name.slice(0, -2); + if (/(ss|tus|rus|nus|pus|bus)$/i.test(stem)) return stem; + return name.slice(0, -1); + } + if (/s$/i.test(name) && !/ss$/i.test(name)) return name.replace(/s$/i, ""); return name + "Item"; } From 998dcbaf6fb65ab8112aa68fe3a13fbd60a89609 Mon Sep 17 00:00:00 2001 From: Anurag Verma <78868769+anurag629@users.noreply.github.com> Date: Fri, 6 Nov 2026 14:22:11 +0530 Subject: [PATCH 15/15] chore: version 0.11.1 (#468) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f83abe3..2cd9e1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "toolbelt", - "version": "0.11.0", + "version": "0.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "toolbelt", - "version": "0.11.0", + "version": "0.11.1", "license": "MIT", "dependencies": { "@cfworker/json-schema": "^4.1.4", diff --git a/package.json b/package.json index aa15714..8c1828f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "toolbelt", - "version": "0.11.0", + "version": "0.11.1", "private": true, "description": "A developer's toolbelt: fast, privacy-first browser tools — JSON formatter, JWT decoder, Base64, URL encoder, invoice generator, unicode inspector. By CODERCOPS.", "license": "MIT",