| {"version":3,"file":"index.js","names":["value: string","node: any","state: Record<string, any>","value","indexes: Array<number>","parts: Array<string>","data: Partial<DnszDnsData>","headerLines: Array<string>","valid: boolean","recordsByType: Record<string, Array<DnszDnsRecord>>","vars: Array<string>"],"sources":["../index.ts"],"sourcesContent":["export type DnszDnsRecord = {\n /**The lowercase DNS name without a trailing dot, e.g. `\"example.com\"`. */\n name: string;\n /** The TTL in seconds, e.g. `60`. */\n ttl: number;\n /** The DNS class, e.g. `\"IN\"`. */\n class: string;\n /** The record type, e.g. `\"A\"`. */\n type: string;\n /** The record content, e.g. `\"2001:db8::1\"` or `\"example.com.\"`. */\n content: string;\n /** A comment, e.g. `\"a comment\"`, `null` if absent. */\n comment: string | null;\n};\n\nexport type DnszDnsData = {\n /** Array of `record` */\n records: Array<DnszDnsRecord>;\n /** The value of `$ORIGIN` in the zone file. */\n origin?: string;\n /** The value of `$TTL` in the zone file. */\n ttl?: number;\n /** An optional header at the start of the file. Can be multiline. Does not include comment markers. */\n header?: string;\n};\n\nexport type DnszParseOptions = {\n /** When specified, replaces any `@` in `name` or `content` with it. */\n replaceOrigin?: string | null;\n /** When true, emit `\\r\\n` instead of `\\n` in `header`. */\n crlf?: boolean;\n /** Default class when absent. */\n defaultClass?: string;\n /** Default TTL when absent and `$TTL` is not present. */\n defaultTTL?: number;\n /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. */\n dots?: boolean;\n};\n\nexport type DnszStringifyOptions = {\n /** Whether to group records into sections. */\n sections?: boolean;\n /** When `true`, emit `\\r\\n` instead of `\\n` for the resulting zone file. */\n crlf?: boolean;\n /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. Default: `false`. */\n dots?: boolean;\n};\n\n// List of types and places where they have name-like content, used on the `dot` option.\nconst nameLike = {\n ALIAS: [0],\n ANAME: [0],\n CNAME: [0],\n DNAME: [0],\n MX: [1],\n NAPTR: [5],\n NS: [0],\n NSEC: [0],\n PTR: [0],\n RP: [0, 1],\n RRSIG: [7],\n SIG: [7],\n SOA: [0, 1],\n SRV: [3],\n TKEY: [0],\n TSIG: [0],\n};\n\nfunction normalize(name: string) {\n name = (name || \"\").toLowerCase();\n if (name.endsWith(\".\") && name.length > 1) {\n name = name.substring(0, name.length - 1);\n }\n return name.replace(/\\.{2,}/g, \".\").replace(/@\\./, \"@\");\n}\n\nfunction splitString(input: string, {separator = \" \", quotes = []}: {separator?: string, quotes?: Array<string>} = {}) {\n const ast = {type: \"root\", nodes: [], stash: [\"\"]};\n const stack = [ast];\n const string = input;\n let value: string;\n let node: any;\n let i = -1;\n const state: Record<string, any> = {\n input,\n separator,\n stack,\n prev: () => string[i - 1],\n next: () => string[i + 1],\n };\n\n const block = () => (state.block = stack[stack.length - 1]);\n const peek = () => string[i + 1];\n const next = () => string[++i];\n const append = (value: string) => {\n state.value = value;\n if (value) {\n state.block.stash[state.block.stash.length - 1] += value;\n }\n };\n\n const closeIndex = (value: string, startIdx: number) => {\n let idx = string.indexOf(value, startIdx);\n if (idx > -1 && string[idx - 1] === \"\\\\\") {\n idx = closeIndex(value, idx + 1);\n }\n return idx;\n };\n\n while (i < string.length - 1) {\n state.value = value = next();\n state.index = i;\n block();\n\n if (value === \"\\\\\") {\n if (peek() === \"\\\\\") {\n append(value + next());\n } else {\n append(value);\n append(next());\n }\n continue;\n }\n\n if (quotes.includes(value)) {\n const pos = i + 1;\n const idx = closeIndex(value, pos);\n\n if (idx > -1) {\n append(value);\n append(string.slice(pos, idx));\n append(string[idx]);\n i = idx;\n continue;\n }\n\n append(value);\n continue;\n }\n\n if (value === separator && state.block.type === \"root\") {\n state.block.stash.push(\"\");\n continue;\n }\n\n append(value);\n }\n\n node = stack.pop();\n while (node !== ast) {\n value = (node.parent.stash.pop() + node.stash.join(\".\"));\n node.parent.stash = node.parent.stash.concat(value.split(\".\"));\n node = stack.pop();\n }\n\n return node.stash;\n}\n\nfunction denormalize(name: string) {\n if (!name.endsWith(\".\") && name.length > 1) {\n name = `${name}.`;\n }\n return name.replace(/\\.{2,}/g, \".\").replace(/@\\./, \"@\");\n}\n\nfunction esc(str: string) {\n return str.replace(/[|\\\\{}()[\\]^$+*?.-]/g, \"\\\\$&\");\n}\n\nfunction addDots(content: string, indexes: Array<number>): string {\n const parts = splitString(content, {\n quotes: [`\"`],\n separator: \" \",\n }).map((s: string) => s.trim()).filter(Boolean);\n for (const index of indexes) {\n if (!parts[index].endsWith(\".\")) {\n parts[index] += \".\";\n }\n }\n return parts.join(\" \");\n}\n\nfunction parseTTL(ttl: string | number, def?: number): number {\n if (typeof ttl === \"number\") {\n return ttl;\n }\n\n if (def && !ttl) {\n return def;\n }\n\n if (/s$/i.test(ttl)) {\n ttl = parseInt(ttl);\n } else if (/m$/i.test(ttl)) {\n ttl = parseInt(ttl) * 60;\n } else if (/h$/i.test(ttl)) {\n ttl = parseInt(ttl) * 60 * 60;\n } else if (/d$/i.test(ttl)) {\n ttl = parseInt(ttl) * 60 * 60 * 24;\n } else if (/w$/i.test(ttl)) {\n ttl = parseInt(ttl) * 60 * 60 * 24 * 7;\n } else {\n ttl = parseInt(ttl);\n }\n\n return ttl;\n}\n\ntype FormatOpts = {\n origin: string,\n newline: string,\n sections: boolean,\n dots: boolean,\n};\n\nfunction format(records: Array<DnszDnsRecord | undefined>, type: string | null, {origin, newline, sections, dots}: FormatOpts) {\n let str = ``;\n\n if (sections && type) {\n str += `;; ${type} Records${newline}`;\n }\n\n for (const record of records || []) {\n if (!record) continue;\n let name = normalize(record.name || \"\");\n\n if (origin) {\n if (name === origin) {\n name = \"@\";\n } else if (name.endsWith(origin)) {\n // subdomain, remove origin and trailing dots\n name = normalize(name.replace(new RegExp(`${esc(`${origin}.`)}?$`, \"gm\"), \"\"));\n } else {\n // assume it's a subdomain, remove trailing dots\n name = normalize(name);\n }\n } else {\n if (name.includes(\".\")) {\n // assume it's a fqdn, add trailing dots\n name = denormalize(name);\n } else {\n name = normalize(name);\n }\n }\n\n let content = record.content;\n if (dots && Object.keys(nameLike).includes(record.type)) {\n const indexes: Array<number> = nameLike[record.type as keyof typeof nameLike];\n content = addDots(content, indexes);\n }\n\n const fields = [\n name,\n record.ttl,\n record.class,\n record.type,\n content,\n ];\n\n if (record.comment) {\n fields.push(`; ${record.comment}`);\n }\n\n str += `${fields.join(\"\\t\")}${newline}`;\n }\n return `${str}${sections ? newline : \"\"}`;\n}\n\nfunction splitContentAndComment(str?: string): [content: string | null, comment: string | null | undefined] {\n if (!str) return [null, null];\n const splitted = splitString(str, {\n quotes: [`\"`],\n separator: \";\",\n });\n\n let parts: Array<string>;\n if (splitted.length > 2) { // more than one semicolon\n parts = [splitted[0], splitted.slice(1).join(\";\")];\n } else {\n parts = splitted;\n }\n\n parts = parts.map((part: string) => (part || \"\").trim()).filter(Boolean);\n\n if (parts.length <= 2) {\n return [parts[0] || null, parts[1] || null];\n } else {\n const comment = parts.pop();\n const content = parts.join(\"; \");\n return [content, comment];\n }\n}\n\n/** Parse a string of a DNS zone file and returns a `data` object. */\nexport function parseZone(str: string, {replaceOrigin = null, crlf = false, defaultTTL = 60, defaultClass = \"IN\", dots = false}: DnszParseOptions = {}): DnszDnsData {\n const data: Partial<DnszDnsData> = {};\n const rawLines = str.split(/\\r?\\n/).map(l => l.trim());\n const lines = rawLines.filter(l => Boolean(l) && !l.startsWith(\";\"));\n const newline = crlf ? \"\\r\\n\" : \"\\n\";\n\n // search for header\n const headerLines: Array<string> = [];\n let valid: boolean = false;\n for (const [index, line] of rawLines.entries()) {\n if (line.startsWith(\";;\")) {\n headerLines.push(line.substring(2).trim());\n } else {\n const prev = rawLines[index - 1];\n if (line === \"\" && index > 1 && prev.startsWith(\";;\")) {\n valid = true;\n break;\n }\n }\n }\n if (valid && headerLines.length) {\n data.header = headerLines.join(newline);\n }\n\n // https://regex101.com/r/aKuGyZ/3\n const reLine = /^([a-z0-9_.\\-@*]+)?\\s*([0-9]+[smhdw]?)?\\s*([a-z]+)?\\s+([a-z]+[0-9]*)?\\s+(.+)$/i;\n\n // create records\n data.records = [];\n for (const line of lines) {\n const parsedOrigin = (/\\$ORIGIN\\s+(\\S+)/.exec(line) || [])[1];\n if (parsedOrigin && !data.origin) {\n data.origin = normalize(parsedOrigin);\n }\n\n const parsedTtl = (/\\$TTL\\s+(\\S+)/.exec(line) || [])[1];\n if (line.startsWith(\"$TTL \") && !data.ttl) {\n data.ttl = parseTTL(normalize(parsedTtl));\n }\n\n let [, name, ttl, cls, type, contentAndComment] = reLine.exec(line) || [];\n if (!ttl && name && /[0-9]+/.test(name)) { // no name\n ttl = name;\n name = \"\";\n }\n if (cls && !type) { // class is optional\n type = cls;\n cls = \"\";\n }\n if (!cls) {\n cls = defaultClass;\n }\n let [content, comment] = splitContentAndComment(contentAndComment);\n\n if (!name) {\n name = \"\";\n }\n\n if (!cls || !type || !content) {\n continue;\n }\n\n type = type.toUpperCase();\n content = (content || \"\").trim();\n if (dots && Object.keys(nameLike).includes(type)) {\n content = addDots(content, nameLike[type as keyof typeof nameLike]);\n }\n\n data.records.push({\n name: normalize(([\"\", \"@\"].includes(name) && data.origin) ? data.origin : name),\n ttl: parseTTL(ttl, data.ttl !== undefined ? data.ttl : defaultTTL),\n class: cls.toUpperCase(),\n type,\n content,\n comment: (comment || \"\").trim() || null,\n });\n }\n\n if (replaceOrigin) {\n data.origin = replaceOrigin;\n }\n\n return data as DnszDnsData;\n}\n\n/** Parse a `data` object and return a string with the zone file contents. */\nexport function stringifyZone(data: DnszDnsData, {crlf = false, sections = true, dots = false}: DnszStringifyOptions = {}): string {\n const recordsByType: Record<string, Array<DnszDnsRecord>> = {};\n const newline = crlf ? \"\\r\\n\" : \"\\n\";\n\n if (sections) {\n for (const record of data.records) {\n if (!recordsByType[record.type]) recordsByType[record.type] = [];\n recordsByType[record.type].push(record);\n }\n }\n\n let output = \"\";\n if (data.header) {\n output += `${data.header\n .split(/\\r?\\n/)\n .map(l => l.trim())\n .map(l => l ? `;; ${l}` : \";;\")\n .join(newline)\n .trim()}${newline}${newline}`;\n }\n\n const vars: Array<string> = [];\n if (data.origin) vars.push(`$ORIGIN ${denormalize(data.origin)}`);\n if (data.ttl) vars.push(`$TTL ${data.ttl}`);\n if (vars.length) output += `${vars.join(newline)}${newline}${newline}`;\n\n const origin = normalize(data.origin || \"\");\n if (sections) {\n if (recordsByType.SOA) {\n output += format(recordsByType.SOA, \"SOA\", {origin, newline, sections, dots});\n delete recordsByType.SOA;\n }\n\n for (const type of Object.keys(recordsByType).sort()) {\n output += format(recordsByType[type], type, {origin, newline, sections, dots});\n }\n } else {\n const recordsSOA = data.records.filter(r => r.type === \"SOA\");\n const recordsMinusSOA = data.records.filter(r => r.type !== \"SOA\");\n\n output += format(recordsSOA, null, {origin, newline, sections, dots});\n output += format(recordsMinusSOA, null, {origin, newline, sections, dots});\n }\n\n return `${output.trim()}${newline}`;\n}\n"],"mappings":";AAiDA,MAAM,WAAW;CACf,OAAO,CAAC,EAAE;CACV,OAAO,CAAC,EAAE;CACV,OAAO,CAAC,EAAE;CACV,OAAO,CAAC,EAAE;CACV,IAAI,CAAC,EAAE;CACP,OAAO,CAAC,EAAE;CACV,IAAI,CAAC,EAAE;CACP,MAAM,CAAC,EAAE;CACT,KAAK,CAAC,EAAE;CACR,IAAI,CAAC,GAAG,EAAE;CACV,OAAO,CAAC,EAAE;CACV,KAAK,CAAC,EAAE;CACR,KAAK,CAAC,GAAG,EAAE;CACX,KAAK,CAAC,EAAE;CACR,MAAM,CAAC,EAAE;CACT,MAAM,CAAC,EAAE;CACV;AAED,SAAS,UAAU,MAAc;AAC/B,SAAQ,QAAQ,IAAI,aAAa;AACjC,KAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EACtC,QAAO,KAAK,UAAU,GAAG,KAAK,SAAS,EAAE;AAE3C,QAAO,KAAK,QAAQ,WAAW,IAAI,CAAC,QAAQ,OAAO,IAAI;;AAGzD,SAAS,YAAY,OAAe,EAAC,YAAY,KAAK,SAAS,EAAE,KAAkD,EAAE,EAAE;CACrH,MAAM,MAAM;EAAC,MAAM;EAAQ,OAAO,EAAE;EAAE,OAAO,CAAC,GAAG;EAAC;CAClD,MAAM,QAAQ,CAAC,IAAI;CACnB,MAAM,SAAS;CACf,IAAIA;CACJ,IAAIC;CACJ,IAAI,IAAI;CACR,MAAMC,QAA6B;EACjC;EACA;EACA;EACA,YAAY,OAAO,IAAI;EACvB,YAAY,OAAO,IAAI;EACxB;CAED,MAAM,cAAe,MAAM,QAAQ,MAAM,MAAM,SAAS;CACxD,MAAM,aAAa,OAAO,IAAI;CAC9B,MAAM,aAAa,OAAO,EAAE;CAC5B,MAAM,UAAU,YAAkB;AAChC,QAAM,QAAQC;AACd,MAAIA,QACF,OAAM,MAAM,MAAM,MAAM,MAAM,MAAM,SAAS,MAAMA;;CAIvD,MAAM,cAAc,SAAe,aAAqB;EACtD,IAAI,MAAM,OAAO,QAAQA,SAAO,SAAS;AACzC,MAAI,MAAM,MAAM,OAAO,MAAM,OAAO,KAClC,OAAM,WAAWA,SAAO,MAAM,EAAE;AAElC,SAAO;;AAGT,QAAO,IAAI,OAAO,SAAS,GAAG;AAC5B,QAAM,QAAQ,QAAQ,MAAM;AAC5B,QAAM,QAAQ;AACd,SAAO;AAEP,MAAI,UAAU,MAAM;AAClB,OAAI,MAAM,KAAK,KACb,QAAO,QAAQ,MAAM,CAAC;QACjB;AACL,WAAO,MAAM;AACb,WAAO,MAAM,CAAC;;AAEhB;;AAGF,MAAI,OAAO,SAAS,MAAM,EAAE;GAC1B,MAAM,MAAM,IAAI;GAChB,MAAM,MAAM,WAAW,OAAO,IAAI;AAElC,OAAI,MAAM,IAAI;AACZ,WAAO,MAAM;AACb,WAAO,OAAO,MAAM,KAAK,IAAI,CAAC;AAC9B,WAAO,OAAO,KAAK;AACnB,QAAI;AACJ;;AAGF,UAAO,MAAM;AACb;;AAGF,MAAI,UAAU,aAAa,MAAM,MAAM,SAAS,QAAQ;AACtD,SAAM,MAAM,MAAM,KAAK,GAAG;AAC1B;;AAGF,SAAO,MAAM;;AAGf,QAAO,MAAM,KAAK;AAClB,QAAO,SAAS,KAAK;AACnB,UAAS,KAAK,OAAO,MAAM,KAAK,GAAG,KAAK,MAAM,KAAK,IAAI;AACvD,OAAK,OAAO,QAAQ,KAAK,OAAO,MAAM,OAAO,MAAM,MAAM,IAAI,CAAC;AAC9D,SAAO,MAAM,KAAK;;AAGpB,QAAO,KAAK;;AAGd,SAAS,YAAY,MAAc;AACjC,KAAI,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EACvC,QAAO,GAAG,KAAK;AAEjB,QAAO,KAAK,QAAQ,WAAW,IAAI,CAAC,QAAQ,OAAO,IAAI;;AAGzD,SAAS,IAAI,KAAa;AACxB,QAAO,IAAI,QAAQ,wBAAwB,OAAO;;AAGpD,SAAS,QAAQ,SAAiB,SAAgC;CAChE,MAAM,QAAQ,YAAY,SAAS;EACjC,QAAQ,CAAC,IAAI;EACb,WAAW;EACZ,CAAC,CAAC,KAAK,MAAc,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;AAC/C,MAAK,MAAM,SAAS,QAClB,KAAI,CAAC,MAAM,OAAO,SAAS,IAAI,CAC7B,OAAM,UAAU;AAGpB,QAAO,MAAM,KAAK,IAAI;;AAGxB,SAAS,SAAS,KAAsB,KAAsB;AAC5D,KAAI,OAAO,QAAQ,SACjB,QAAO;AAGT,KAAI,OAAO,CAAC,IACV,QAAO;AAGT,KAAI,MAAM,KAAK,IAAI,CACjB,OAAM,SAAS,IAAI;UACV,MAAM,KAAK,IAAI,CACxB,OAAM,SAAS,IAAI,GAAG;UACb,MAAM,KAAK,IAAI,CACxB,OAAM,SAAS,IAAI,GAAG,KAAK;UAClB,MAAM,KAAK,IAAI,CACxB,OAAM,SAAS,IAAI,GAAG,KAAK,KAAK;UACvB,MAAM,KAAK,IAAI,CACxB,OAAM,SAAS,IAAI,GAAG,KAAK,KAAK,KAAK;KAErC,OAAM,SAAS,IAAI;AAGrB,QAAO;;AAUT,SAAS,OAAO,SAA2C,MAAqB,EAAC,QAAQ,SAAS,UAAU,QAAmB;CAC7H,IAAI,MAAM;AAEV,KAAI,YAAY,KACd,QAAO,MAAM,KAAK,UAAU;AAG9B,MAAK,MAAM,UAAU,WAAW,EAAE,EAAE;AAClC,MAAI,CAAC,OAAQ;EACb,IAAI,OAAO,UAAU,OAAO,QAAQ,GAAG;AAEvC,MAAI,OACF,KAAI,SAAS,OACX,QAAO;WACE,KAAK,SAAS,OAAO,CAE9B,QAAO,UAAU,KAAK,QAAQ,IAAI,OAAO,GAAG,IAAI,GAAG,OAAO,GAAG,CAAC,KAAK,KAAK,EAAE,GAAG,CAAC;MAG9E,QAAO,UAAU,KAAK;WAGpB,KAAK,SAAS,IAAI,CAEpB,QAAO,YAAY,KAAK;MAExB,QAAO,UAAU,KAAK;EAI1B,IAAI,UAAU,OAAO;AACrB,MAAI,QAAQ,OAAO,KAAK,SAAS,CAAC,SAAS,OAAO,KAAK,EAAE;GACvD,MAAMC,UAAyB,SAAS,OAAO;AAC/C,aAAU,QAAQ,SAAS,QAAQ;;EAGrC,MAAM,SAAS;GACb;GACA,OAAO;GACP,OAAO;GACP,OAAO;GACP;GACD;AAED,MAAI,OAAO,QACT,QAAO,KAAK,KAAK,OAAO,UAAU;AAGpC,SAAO,GAAG,OAAO,KAAK,IAAK,GAAG;;AAEhC,QAAO,GAAG,MAAM,WAAW,UAAU;;AAGvC,SAAS,uBAAuB,KAA4E;AAC1G,KAAI,CAAC,IAAK,QAAO,CAAC,MAAM,KAAK;CAC7B,MAAM,WAAW,YAAY,KAAK;EAChC,QAAQ,CAAC,IAAI;EACb,WAAW;EACZ,CAAC;CAEF,IAAIC;AACJ,KAAI,SAAS,SAAS,EACpB,SAAQ,CAAC,SAAS,IAAI,SAAS,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;KAElD,SAAQ;AAGV,SAAQ,MAAM,KAAK,UAAkB,QAAQ,IAAI,MAAM,CAAC,CAAC,OAAO,QAAQ;AAExE,KAAI,MAAM,UAAU,EAClB,QAAO,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK;MACtC;EACL,MAAM,UAAU,MAAM,KAAK;AAE3B,SAAO,CADS,MAAM,KAAK,KAAK,EACf,QAAQ;;;;AAK7B,SAAgB,UAAU,KAAa,EAAC,gBAAgB,MAAM,OAAO,OAAO,aAAa,IAAI,eAAe,MAAM,OAAO,UAA2B,EAAE,EAAe;CACnK,MAAMC,OAA6B,EAAE;CACrC,MAAM,WAAW,IAAI,MAAM,QAAQ,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC;CACtD,MAAM,QAAQ,SAAS,QAAO,MAAK,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,IAAI,CAAC;CACpE,MAAM,UAAU,OAAO,SAAS;CAGhC,MAAMC,cAA6B,EAAE;CACrC,IAAIC,QAAiB;AACrB,MAAK,MAAM,CAAC,OAAO,SAAS,SAAS,SAAS,CAC5C,KAAI,KAAK,WAAW,KAAK,CACvB,aAAY,KAAK,KAAK,UAAU,EAAE,CAAC,MAAM,CAAC;MACrC;EACL,MAAM,OAAO,SAAS,QAAQ;AAC9B,MAAI,SAAS,MAAM,QAAQ,KAAK,KAAK,WAAW,KAAK,EAAE;AACrD,WAAQ;AACR;;;AAIN,KAAI,SAAS,YAAY,OACvB,MAAK,SAAS,YAAY,KAAK,QAAQ;CAIzC,MAAM,SAAS;AAGf,MAAK,UAAU,EAAE;AACjB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,gBAAgB,mBAAmB,KAAK,KAAK,IAAI,EAAE,EAAE;AAC3D,MAAI,gBAAgB,CAAC,KAAK,OACxB,MAAK,SAAS,UAAU,aAAa;EAGvC,MAAM,aAAa,gBAAgB,KAAK,KAAK,IAAI,EAAE,EAAE;AACrD,MAAI,KAAK,WAAW,QAAQ,IAAI,CAAC,KAAK,IACpC,MAAK,MAAM,SAAS,UAAU,UAAU,CAAC;EAG3C,IAAI,GAAG,MAAM,KAAK,KAAK,MAAM,qBAAqB,OAAO,KAAK,KAAK,IAAI,EAAE;AACzE,MAAI,CAAC,OAAO,QAAQ,SAAS,KAAK,KAAK,EAAE;AACvC,SAAM;AACN,UAAO;;AAET,MAAI,OAAO,CAAC,MAAM;AAChB,UAAO;AACP,SAAM;;AAER,MAAI,CAAC,IACH,OAAM;EAER,IAAI,CAAC,SAAS,WAAW,uBAAuB,kBAAkB;AAElE,MAAI,CAAC,KACH,QAAO;AAGT,MAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QACpB;AAGF,SAAO,KAAK,aAAa;AACzB,aAAW,WAAW,IAAI,MAAM;AAChC,MAAI,QAAQ,OAAO,KAAK,SAAS,CAAC,SAAS,KAAK,CAC9C,WAAU,QAAQ,SAAS,SAAS,MAA+B;AAGrE,OAAK,QAAQ,KAAK;GAChB,MAAM,UAAW,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,KAAK,SAAU,KAAK,SAAS,KAAK;GAC/E,KAAK,SAAS,KAAK,KAAK,QAAQ,SAAY,KAAK,MAAM,WAAW;GAClE,OAAO,IAAI,aAAa;GACxB;GACA;GACA,UAAU,WAAW,IAAI,MAAM,IAAI;GACpC,CAAC;;AAGJ,KAAI,cACF,MAAK,SAAS;AAGhB,QAAO;;;AAIT,SAAgB,cAAc,MAAmB,EAAC,OAAO,OAAO,WAAW,MAAM,OAAO,UAA+B,EAAE,EAAU;CACjI,MAAMC,gBAAsD,EAAE;CAC9D,MAAM,UAAU,OAAO,SAAS;AAEhC,KAAI,SACF,MAAK,MAAM,UAAU,KAAK,SAAS;AACjC,MAAI,CAAC,cAAc,OAAO,MAAO,eAAc,OAAO,QAAQ,EAAE;AAChE,gBAAc,OAAO,MAAM,KAAK,OAAO;;CAI3C,IAAI,SAAS;AACb,KAAI,KAAK,OACP,WAAU,GAAG,KAAK,OACf,MAAM,QAAQ,CACd,KAAI,MAAK,EAAE,MAAM,CAAC,CAClB,KAAI,MAAK,IAAI,MAAM,MAAM,KAAK,CAC9B,KAAK,QAAQ,CACb,MAAM,GAAG,UAAU;CAGxB,MAAMC,OAAsB,EAAE;AAC9B,KAAI,KAAK,OAAQ,MAAK,KAAK,WAAW,YAAY,KAAK,OAAO,GAAG;AACjE,KAAI,KAAK,IAAK,MAAK,KAAK,QAAQ,KAAK,MAAM;AAC3C,KAAI,KAAK,OAAQ,WAAU,GAAG,KAAK,KAAK,QAAQ,GAAG,UAAU;CAE7D,MAAM,SAAS,UAAU,KAAK,UAAU,GAAG;AAC3C,KAAI,UAAU;AACZ,MAAI,cAAc,KAAK;AACrB,aAAU,OAAO,cAAc,KAAK,OAAO;IAAC;IAAQ;IAAS;IAAU;IAAK,CAAC;AAC7E,UAAO,cAAc;;AAGvB,OAAK,MAAM,QAAQ,OAAO,KAAK,cAAc,CAAC,MAAM,CAClD,WAAU,OAAO,cAAc,OAAO,MAAM;GAAC;GAAQ;GAAS;GAAU;GAAK,CAAC;QAE3E;EACL,MAAM,aAAa,KAAK,QAAQ,QAAO,MAAK,EAAE,SAAS,MAAM;EAC7D,MAAM,kBAAkB,KAAK,QAAQ,QAAO,MAAK,EAAE,SAAS,MAAM;AAElE,YAAU,OAAO,YAAY,MAAM;GAAC;GAAQ;GAAS;GAAU;GAAK,CAAC;AACrE,YAAU,OAAO,iBAAiB,MAAM;GAAC;GAAQ;GAAS;GAAU;GAAK,CAAC;;AAG5E,QAAO,GAAG,OAAO,MAAM,GAAG"} |
+55
-42
@@ -1,49 +0,62 @@ | ||
| export type DnszDnsRecord = { | ||
| /**The lowercase DNS name without a trailing dot, e.g. `"example.com"`. */ | ||
| name: string; | ||
| /** The TTL in seconds, e.g. `60`. */ | ||
| ttl: number; | ||
| /** The DNS class, e.g. `"IN"`. */ | ||
| class: string; | ||
| /** The record type, e.g. `"A"`. */ | ||
| type: string; | ||
| /** The record content, e.g. `"2001:db8::1"` or `"example.com."`. */ | ||
| content: string; | ||
| /** A comment, e.g. `"a comment"`, `null` if absent. */ | ||
| comment: string | null; | ||
| //#region index.d.ts | ||
| type DnszDnsRecord = { | ||
| /**The lowercase DNS name without a trailing dot, e.g. `"example.com"`. */ | ||
| name: string; | ||
| /** The TTL in seconds, e.g. `60`. */ | ||
| ttl: number; | ||
| /** The DNS class, e.g. `"IN"`. */ | ||
| class: string; | ||
| /** The record type, e.g. `"A"`. */ | ||
| type: string; | ||
| /** The record content, e.g. `"2001:db8::1"` or `"example.com."`. */ | ||
| content: string; | ||
| /** A comment, e.g. `"a comment"`, `null` if absent. */ | ||
| comment: string | null; | ||
| }; | ||
| export type DnszDnsData = { | ||
| /** Array of `record` */ | ||
| records: DnszDnsRecord[]; | ||
| /** The value of `$ORIGIN` in the zone file. */ | ||
| origin?: string; | ||
| /** The value of `$TTL` in the zone file. */ | ||
| ttl?: number; | ||
| /** An optional header at the start of the file. Can be multiline. Does not include comment markers. */ | ||
| header?: string; | ||
| type DnszDnsData = { | ||
| /** Array of `record` */ | ||
| records: Array<DnszDnsRecord>; | ||
| /** The value of `$ORIGIN` in the zone file. */ | ||
| origin?: string; | ||
| /** The value of `$TTL` in the zone file. */ | ||
| ttl?: number; | ||
| /** An optional header at the start of the file. Can be multiline. Does not include comment markers. */ | ||
| header?: string; | ||
| }; | ||
| export type DnszParseOptions = { | ||
| /** When specified, replaces any `@` in `name` or `content` with it. */ | ||
| replaceOrigin?: string | null; | ||
| /** When true, emit `\r\n` instead of `\n` in `header`. */ | ||
| crlf?: boolean; | ||
| /** Default class when absent. */ | ||
| defaultClass?: string; | ||
| /** Default TTL when absent and `$TTL` is not present. */ | ||
| defaultTTL?: number; | ||
| /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. */ | ||
| dots?: boolean; | ||
| type DnszParseOptions = { | ||
| /** When specified, replaces any `@` in `name` or `content` with it. */ | ||
| replaceOrigin?: string | null; | ||
| /** When true, emit `\r\n` instead of `\n` in `header`. */ | ||
| crlf?: boolean; | ||
| /** Default class when absent. */ | ||
| defaultClass?: string; | ||
| /** Default TTL when absent and `$TTL` is not present. */ | ||
| defaultTTL?: number; | ||
| /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. */ | ||
| dots?: boolean; | ||
| }; | ||
| export type DnszStringifyOptions = { | ||
| /** Whether to group records into sections. */ | ||
| sections?: boolean; | ||
| /** When `true`, emit `\r\n` instead of `\n` for the resulting zone file. */ | ||
| crlf?: boolean; | ||
| /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. Default: `false`. */ | ||
| dots?: boolean; | ||
| type DnszStringifyOptions = { | ||
| /** Whether to group records into sections. */ | ||
| sections?: boolean; | ||
| /** When `true`, emit `\r\n` instead of `\n` for the resulting zone file. */ | ||
| crlf?: boolean; | ||
| /** Ensure trailing dots on FQDNs in content. Supports a limited amount of record types. Default: `false`. */ | ||
| dots?: boolean; | ||
| }; | ||
| /** Parse a string of a DNS zone file and returns a `data` object. */ | ||
| export declare function parseZone(str: string, { replaceOrigin, crlf, defaultTTL, defaultClass, dots }?: DnszParseOptions): DnszDnsData; | ||
| declare function parseZone(str: string, { | ||
| replaceOrigin, | ||
| crlf, | ||
| defaultTTL, | ||
| defaultClass, | ||
| dots | ||
| }?: DnszParseOptions): DnszDnsData; | ||
| /** Parse a `data` object and return a string with the zone file contents. */ | ||
| export declare function stringifyZone(data: DnszDnsData, { crlf, sections, dots }?: DnszStringifyOptions): string; | ||
| declare function stringifyZone(data: DnszDnsData, { | ||
| crlf, | ||
| sections, | ||
| dots | ||
| }?: DnszStringifyOptions): string; | ||
| //#endregion | ||
| export { DnszDnsData, DnszDnsRecord, DnszParseOptions, DnszStringifyOptions, parseZone, stringifyZone }; | ||
| //# sourceMappingURL=index.d.ts.map |
+246
-281
@@ -0,303 +1,268 @@ | ||
| //#region index.ts | ||
| const nameLike = { | ||
| ALIAS: [0], | ||
| ANAME: [0], | ||
| CNAME: [0], | ||
| DNAME: [0], | ||
| MX: [1], | ||
| NAPTR: [5], | ||
| NS: [0], | ||
| NSEC: [0], | ||
| PTR: [0], | ||
| RP: [0, 1], | ||
| RRSIG: [7], | ||
| SIG: [7], | ||
| SOA: [0, 1], | ||
| SRV: [3], | ||
| TKEY: [0], | ||
| TSIG: [0] | ||
| ALIAS: [0], | ||
| ANAME: [0], | ||
| CNAME: [0], | ||
| DNAME: [0], | ||
| MX: [1], | ||
| NAPTR: [5], | ||
| NS: [0], | ||
| NSEC: [0], | ||
| PTR: [0], | ||
| RP: [0, 1], | ||
| RRSIG: [7], | ||
| SIG: [7], | ||
| SOA: [0, 1], | ||
| SRV: [3], | ||
| TKEY: [0], | ||
| TSIG: [0] | ||
| }; | ||
| function normalize(name) { | ||
| name = (name || "").toLowerCase(); | ||
| if (name.endsWith(".") && name.length > 1) { | ||
| name = name.substring(0, name.length - 1); | ||
| } | ||
| return name.replace(/\.{2,}/g, ".").replace(/@\./, "@"); | ||
| name = (name || "").toLowerCase(); | ||
| if (name.endsWith(".") && name.length > 1) name = name.substring(0, name.length - 1); | ||
| return name.replace(/\.{2,}/g, ".").replace(/@\./, "@"); | ||
| } | ||
| function splitString(input, { separator = " ", quotes = [] } = {}) { | ||
| const ast = { type: "root", nodes: [], stash: [""] }; | ||
| const stack = [ast]; | ||
| const string = input; | ||
| let value; | ||
| let node; | ||
| let i = -1; | ||
| const state = {}; | ||
| const block = () => state.block = stack[stack.length - 1]; | ||
| const peek = () => string[i + 1]; | ||
| const next = () => string[++i]; | ||
| const append = (value2) => { | ||
| state.value = value2; | ||
| if (value2) { | ||
| state.block.stash[state.block.stash.length - 1] += value2; | ||
| } | ||
| }; | ||
| const closeIndex = (value2, startIdx) => { | ||
| let idx = string.indexOf(value2, startIdx); | ||
| if (idx > -1 && string[idx - 1] === "\\") { | ||
| idx = closeIndex(value2, idx + 1); | ||
| } | ||
| return idx; | ||
| }; | ||
| while (i < string.length - 1) { | ||
| state.value = value = next(); | ||
| state.index = i; | ||
| block(); | ||
| if (value === "\\") { | ||
| if (peek() === "\\") { | ||
| append(value + next()); | ||
| } else { | ||
| append(value); | ||
| append(next()); | ||
| } | ||
| continue; | ||
| } | ||
| if (quotes.includes(value)) { | ||
| const pos = i + 1; | ||
| const idx = closeIndex(value, pos); | ||
| if (idx > -1) { | ||
| append(value); | ||
| append(string.slice(pos, idx)); | ||
| append(string[idx]); | ||
| i = idx; | ||
| continue; | ||
| } | ||
| append(value); | ||
| continue; | ||
| } | ||
| if (value === separator && state.block.type === "root") { | ||
| state.block.stash.push(""); | ||
| continue; | ||
| } | ||
| append(value); | ||
| } | ||
| node = stack.pop(); | ||
| while (node !== ast) { | ||
| value = node.parent.stash.pop() + node.stash.join("."); | ||
| node.parent.stash = node.parent.stash.concat(value.split(".")); | ||
| node = stack.pop(); | ||
| } | ||
| return node.stash; | ||
| const ast = { | ||
| type: "root", | ||
| nodes: [], | ||
| stash: [""] | ||
| }; | ||
| const stack = [ast]; | ||
| const string = input; | ||
| let value; | ||
| let node; | ||
| let i = -1; | ||
| const state = { | ||
| input, | ||
| separator, | ||
| stack, | ||
| prev: () => string[i - 1], | ||
| next: () => string[i + 1] | ||
| }; | ||
| const block = () => state.block = stack[stack.length - 1]; | ||
| const peek = () => string[i + 1]; | ||
| const next = () => string[++i]; | ||
| const append = (value$1) => { | ||
| state.value = value$1; | ||
| if (value$1) state.block.stash[state.block.stash.length - 1] += value$1; | ||
| }; | ||
| const closeIndex = (value$1, startIdx) => { | ||
| let idx = string.indexOf(value$1, startIdx); | ||
| if (idx > -1 && string[idx - 1] === "\\") idx = closeIndex(value$1, idx + 1); | ||
| return idx; | ||
| }; | ||
| while (i < string.length - 1) { | ||
| state.value = value = next(); | ||
| state.index = i; | ||
| block(); | ||
| if (value === "\\") { | ||
| if (peek() === "\\") append(value + next()); | ||
| else { | ||
| append(value); | ||
| append(next()); | ||
| } | ||
| continue; | ||
| } | ||
| if (quotes.includes(value)) { | ||
| const pos = i + 1; | ||
| const idx = closeIndex(value, pos); | ||
| if (idx > -1) { | ||
| append(value); | ||
| append(string.slice(pos, idx)); | ||
| append(string[idx]); | ||
| i = idx; | ||
| continue; | ||
| } | ||
| append(value); | ||
| continue; | ||
| } | ||
| if (value === separator && state.block.type === "root") { | ||
| state.block.stash.push(""); | ||
| continue; | ||
| } | ||
| append(value); | ||
| } | ||
| node = stack.pop(); | ||
| while (node !== ast) { | ||
| value = node.parent.stash.pop() + node.stash.join("."); | ||
| node.parent.stash = node.parent.stash.concat(value.split(".")); | ||
| node = stack.pop(); | ||
| } | ||
| return node.stash; | ||
| } | ||
| function denormalize(name) { | ||
| if (!name.endsWith(".") && name.length > 1) { | ||
| name = `${name}.`; | ||
| } | ||
| return name.replace(/\.{2,}/g, ".").replace(/@\./, "@"); | ||
| if (!name.endsWith(".") && name.length > 1) name = `${name}.`; | ||
| return name.replace(/\.{2,}/g, ".").replace(/@\./, "@"); | ||
| } | ||
| function esc(str) { | ||
| return str.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&"); | ||
| return str.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&"); | ||
| } | ||
| function addDots(content, indexes) { | ||
| const parts = splitString(content, { | ||
| quotes: [`"`], | ||
| separator: " " | ||
| }).map((s) => s.trim()).filter(Boolean); | ||
| for (const index of indexes) { | ||
| if (!parts[index].endsWith(".")) { | ||
| parts[index] += "."; | ||
| } | ||
| } | ||
| return parts.join(" "); | ||
| const parts = splitString(content, { | ||
| quotes: [`"`], | ||
| separator: " " | ||
| }).map((s) => s.trim()).filter(Boolean); | ||
| for (const index of indexes) if (!parts[index].endsWith(".")) parts[index] += "."; | ||
| return parts.join(" "); | ||
| } | ||
| function parseTTL(ttl, def) { | ||
| if (typeof ttl === "number") { | ||
| return ttl; | ||
| } | ||
| if (def && !ttl) { | ||
| return def; | ||
| } | ||
| if (/s$/i.test(ttl)) { | ||
| ttl = parseInt(ttl); | ||
| } else if (/m$/i.test(ttl)) { | ||
| ttl = parseInt(ttl) * 60; | ||
| } else if (/h$/i.test(ttl)) { | ||
| ttl = parseInt(ttl) * 60 * 60; | ||
| } else if (/d$/i.test(ttl)) { | ||
| ttl = parseInt(ttl) * 60 * 60 * 24; | ||
| } else if (/w$/i.test(ttl)) { | ||
| ttl = parseInt(ttl) * 60 * 60 * 24 * 7; | ||
| } else { | ||
| ttl = parseInt(ttl); | ||
| } | ||
| return ttl; | ||
| if (typeof ttl === "number") return ttl; | ||
| if (def && !ttl) return def; | ||
| if (/s$/i.test(ttl)) ttl = parseInt(ttl); | ||
| else if (/m$/i.test(ttl)) ttl = parseInt(ttl) * 60; | ||
| else if (/h$/i.test(ttl)) ttl = parseInt(ttl) * 60 * 60; | ||
| else if (/d$/i.test(ttl)) ttl = parseInt(ttl) * 60 * 60 * 24; | ||
| else if (/w$/i.test(ttl)) ttl = parseInt(ttl) * 60 * 60 * 24 * 7; | ||
| else ttl = parseInt(ttl); | ||
| return ttl; | ||
| } | ||
| function format(records, type, { origin, newline, sections, dots }) { | ||
| let str = ``; | ||
| if (sections && type) { | ||
| str += `;; ${type} Records${newline}`; | ||
| } | ||
| for (const record of records || []) { | ||
| if (!record) continue; | ||
| let name = normalize(record.name || ""); | ||
| if (origin) { | ||
| if (name === origin) { | ||
| name = "@"; | ||
| } else if (name.endsWith(origin)) { | ||
| name = normalize(name.replace(new RegExp(`${esc(`${origin}.`)}?$`, "gm"), "")); | ||
| } else { | ||
| name = normalize(name); | ||
| } | ||
| } else { | ||
| if (name.includes(".")) { | ||
| name = denormalize(name); | ||
| } else { | ||
| name = normalize(name); | ||
| } | ||
| } | ||
| let content = record.content; | ||
| if (dots && Object.keys(nameLike).includes(record.type)) { | ||
| const indexes = nameLike[record.type]; | ||
| content = addDots(content, indexes); | ||
| } | ||
| const fields = [ | ||
| name, | ||
| record.ttl, | ||
| record.class, | ||
| record.type, | ||
| content | ||
| ]; | ||
| if (record.comment) { | ||
| fields.push(`; ${record.comment}`); | ||
| } | ||
| str += `${fields.join(" ")}${newline}`; | ||
| } | ||
| return `${str}${sections ? newline : ""}`; | ||
| let str = ``; | ||
| if (sections && type) str += `;; ${type} Records${newline}`; | ||
| for (const record of records || []) { | ||
| if (!record) continue; | ||
| let name = normalize(record.name || ""); | ||
| if (origin) if (name === origin) name = "@"; | ||
| else if (name.endsWith(origin)) name = normalize(name.replace(new RegExp(`${esc(`${origin}.`)}?$`, "gm"), "")); | ||
| else name = normalize(name); | ||
| else if (name.includes(".")) name = denormalize(name); | ||
| else name = normalize(name); | ||
| let content = record.content; | ||
| if (dots && Object.keys(nameLike).includes(record.type)) { | ||
| const indexes = nameLike[record.type]; | ||
| content = addDots(content, indexes); | ||
| } | ||
| const fields = [ | ||
| name, | ||
| record.ttl, | ||
| record.class, | ||
| record.type, | ||
| content | ||
| ]; | ||
| if (record.comment) fields.push(`; ${record.comment}`); | ||
| str += `${fields.join(" ")}${newline}`; | ||
| } | ||
| return `${str}${sections ? newline : ""}`; | ||
| } | ||
| function splitContentAndComment(str) { | ||
| if (!str) return [null, null]; | ||
| const splitted = splitString(str, { | ||
| quotes: [`"`], | ||
| separator: ";" | ||
| }); | ||
| let parts; | ||
| if (splitted.length > 2) { | ||
| parts = [splitted[0], splitted.slice(1).join(";")]; | ||
| } else { | ||
| parts = splitted; | ||
| } | ||
| parts = parts.map((part) => (part || "").trim()).filter(Boolean); | ||
| if (parts.length <= 2) { | ||
| return [parts[0] || null, parts[1] || null]; | ||
| } else { | ||
| const comment = parts.pop(); | ||
| const content = parts.join("; "); | ||
| return [content, comment]; | ||
| } | ||
| if (!str) return [null, null]; | ||
| const splitted = splitString(str, { | ||
| quotes: [`"`], | ||
| separator: ";" | ||
| }); | ||
| let parts; | ||
| if (splitted.length > 2) parts = [splitted[0], splitted.slice(1).join(";")]; | ||
| else parts = splitted; | ||
| parts = parts.map((part) => (part || "").trim()).filter(Boolean); | ||
| if (parts.length <= 2) return [parts[0] || null, parts[1] || null]; | ||
| else { | ||
| const comment = parts.pop(); | ||
| return [parts.join("; "), comment]; | ||
| } | ||
| } | ||
| /** Parse a string of a DNS zone file and returns a `data` object. */ | ||
| function parseZone(str, { replaceOrigin = null, crlf = false, defaultTTL = 60, defaultClass = "IN", dots = false } = {}) { | ||
| const data = {}; | ||
| const rawLines = str.split(/\r?\n/).map((l) => l.trim()); | ||
| const lines = rawLines.filter((l) => Boolean(l) && !l.startsWith(";")); | ||
| const newline = crlf ? "\r\n" : "\n"; | ||
| const headerLines = []; | ||
| let valid; | ||
| for (const [index, line] of rawLines.entries()) { | ||
| if (line.startsWith(";;")) { | ||
| headerLines.push(line.substring(2).trim()); | ||
| } else { | ||
| const prev = rawLines[index - 1]; | ||
| if (line === "" && index > 1 && prev.startsWith(";;")) { | ||
| valid = true; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (valid && headerLines.length) { | ||
| data.header = headerLines.join(newline); | ||
| } | ||
| const reLine = /^([a-z0-9_.\-@*]+)?\s*([0-9]+[smhdw]?)?\s*([a-z]+)?\s+([a-z]+[0-9]*)?\s+(.+)$/i; | ||
| data.records = []; | ||
| for (const line of lines) { | ||
| const parsedOrigin = (/\$ORIGIN\s+(\S+)/.exec(line) || [])[1]; | ||
| if (parsedOrigin && !data.origin) { | ||
| data.origin = normalize(parsedOrigin); | ||
| } | ||
| const parsedTtl = (/\$TTL\s+(\S+)/.exec(line) || [])[1]; | ||
| if (line.startsWith("$TTL ") && !data.ttl) { | ||
| data.ttl = parseTTL(normalize(parsedTtl)); | ||
| } | ||
| let [, name, ttl, cls, type, contentAndComment] = reLine.exec(line) || []; | ||
| if (!ttl && name && /[0-9]+/.test(name)) { | ||
| ttl = name; | ||
| name = ""; | ||
| } | ||
| if (cls && !type) { | ||
| type = cls; | ||
| cls = ""; | ||
| } | ||
| if (!cls) { | ||
| cls = defaultClass; | ||
| } | ||
| let [content, comment] = splitContentAndComment(contentAndComment); | ||
| if (!name) { | ||
| name = ""; | ||
| } | ||
| if (!cls || !type || !content) { | ||
| continue; | ||
| } | ||
| type = type.toUpperCase(); | ||
| content = (content || "").trim(); | ||
| if (dots && Object.keys(nameLike).includes(type)) { | ||
| content = addDots(content, nameLike[type]); | ||
| } | ||
| data.records.push({ | ||
| name: normalize(["", "@"].includes(name) && data.origin ? data.origin : name), | ||
| ttl: parseTTL(ttl, data.ttl !== void 0 ? data.ttl : defaultTTL), | ||
| class: cls.toUpperCase(), | ||
| type, | ||
| content, | ||
| comment: (comment || "").trim() || null | ||
| }); | ||
| } | ||
| if (replaceOrigin) { | ||
| data.origin = replaceOrigin; | ||
| } | ||
| return data; | ||
| const data = {}; | ||
| const rawLines = str.split(/\r?\n/).map((l) => l.trim()); | ||
| const lines = rawLines.filter((l) => Boolean(l) && !l.startsWith(";")); | ||
| const newline = crlf ? "\r\n" : "\n"; | ||
| const headerLines = []; | ||
| let valid = false; | ||
| for (const [index, line] of rawLines.entries()) if (line.startsWith(";;")) headerLines.push(line.substring(2).trim()); | ||
| else { | ||
| const prev = rawLines[index - 1]; | ||
| if (line === "" && index > 1 && prev.startsWith(";;")) { | ||
| valid = true; | ||
| break; | ||
| } | ||
| } | ||
| if (valid && headerLines.length) data.header = headerLines.join(newline); | ||
| const reLine = /^([a-z0-9_.\-@*]+)?\s*([0-9]+[smhdw]?)?\s*([a-z]+)?\s+([a-z]+[0-9]*)?\s+(.+)$/i; | ||
| data.records = []; | ||
| for (const line of lines) { | ||
| const parsedOrigin = (/\$ORIGIN\s+(\S+)/.exec(line) || [])[1]; | ||
| if (parsedOrigin && !data.origin) data.origin = normalize(parsedOrigin); | ||
| const parsedTtl = (/\$TTL\s+(\S+)/.exec(line) || [])[1]; | ||
| if (line.startsWith("$TTL ") && !data.ttl) data.ttl = parseTTL(normalize(parsedTtl)); | ||
| let [, name, ttl, cls, type, contentAndComment] = reLine.exec(line) || []; | ||
| if (!ttl && name && /[0-9]+/.test(name)) { | ||
| ttl = name; | ||
| name = ""; | ||
| } | ||
| if (cls && !type) { | ||
| type = cls; | ||
| cls = ""; | ||
| } | ||
| if (!cls) cls = defaultClass; | ||
| let [content, comment] = splitContentAndComment(contentAndComment); | ||
| if (!name) name = ""; | ||
| if (!cls || !type || !content) continue; | ||
| type = type.toUpperCase(); | ||
| content = (content || "").trim(); | ||
| if (dots && Object.keys(nameLike).includes(type)) content = addDots(content, nameLike[type]); | ||
| data.records.push({ | ||
| name: normalize(["", "@"].includes(name) && data.origin ? data.origin : name), | ||
| ttl: parseTTL(ttl, data.ttl !== void 0 ? data.ttl : defaultTTL), | ||
| class: cls.toUpperCase(), | ||
| type, | ||
| content, | ||
| comment: (comment || "").trim() || null | ||
| }); | ||
| } | ||
| if (replaceOrigin) data.origin = replaceOrigin; | ||
| return data; | ||
| } | ||
| /** Parse a `data` object and return a string with the zone file contents. */ | ||
| function stringifyZone(data, { crlf = false, sections = true, dots = false } = {}) { | ||
| const recordsByType = {}; | ||
| const newline = crlf ? "\r\n" : "\n"; | ||
| if (sections) { | ||
| for (const record of data.records) { | ||
| if (!recordsByType[record.type]) recordsByType[record.type] = []; | ||
| recordsByType[record.type].push(record); | ||
| } | ||
| } | ||
| let output = ""; | ||
| if (data.header) { | ||
| output += `${data.header.split(/\r?\n/).map((l) => l.trim()).map((l) => l ? `;; ${l}` : ";;").join(newline).trim()}${newline}${newline}`; | ||
| } | ||
| const vars = []; | ||
| if (data.origin) vars.push(`$ORIGIN ${denormalize(data.origin)}`); | ||
| if (data.ttl) vars.push(`$TTL ${data.ttl}`); | ||
| if (vars.length) output += `${vars.join(newline)}${newline}${newline}`; | ||
| const origin = normalize(data.origin); | ||
| if (sections) { | ||
| if (recordsByType.SOA) { | ||
| output += format(recordsByType.SOA, "SOA", { origin, newline, sections, dots }); | ||
| delete recordsByType.SOA; | ||
| } | ||
| for (const type of Object.keys(recordsByType).sort()) { | ||
| output += format(recordsByType[type], type, { origin, newline, sections, dots }); | ||
| } | ||
| } else { | ||
| const recordsSOA = data.records.filter((r) => r.type === "SOA"); | ||
| const recordsMinusSOA = data.records.filter((r) => r.type !== "SOA"); | ||
| output += format(recordsSOA, null, { origin, newline, sections, dots }); | ||
| output += format(recordsMinusSOA, null, { origin, newline, sections, dots }); | ||
| } | ||
| return `${output.trim()}${newline}`; | ||
| const recordsByType = {}; | ||
| const newline = crlf ? "\r\n" : "\n"; | ||
| if (sections) for (const record of data.records) { | ||
| if (!recordsByType[record.type]) recordsByType[record.type] = []; | ||
| recordsByType[record.type].push(record); | ||
| } | ||
| let output = ""; | ||
| if (data.header) output += `${data.header.split(/\r?\n/).map((l) => l.trim()).map((l) => l ? `;; ${l}` : ";;").join(newline).trim()}${newline}${newline}`; | ||
| const vars = []; | ||
| if (data.origin) vars.push(`$ORIGIN ${denormalize(data.origin)}`); | ||
| if (data.ttl) vars.push(`$TTL ${data.ttl}`); | ||
| if (vars.length) output += `${vars.join(newline)}${newline}${newline}`; | ||
| const origin = normalize(data.origin || ""); | ||
| if (sections) { | ||
| if (recordsByType.SOA) { | ||
| output += format(recordsByType.SOA, "SOA", { | ||
| origin, | ||
| newline, | ||
| sections, | ||
| dots | ||
| }); | ||
| delete recordsByType.SOA; | ||
| } | ||
| for (const type of Object.keys(recordsByType).sort()) output += format(recordsByType[type], type, { | ||
| origin, | ||
| newline, | ||
| sections, | ||
| dots | ||
| }); | ||
| } else { | ||
| const recordsSOA = data.records.filter((r) => r.type === "SOA"); | ||
| const recordsMinusSOA = data.records.filter((r) => r.type !== "SOA"); | ||
| output += format(recordsSOA, null, { | ||
| origin, | ||
| newline, | ||
| sections, | ||
| dots | ||
| }); | ||
| output += format(recordsMinusSOA, null, { | ||
| origin, | ||
| newline, | ||
| sections, | ||
| dots | ||
| }); | ||
| } | ||
| return `${output.trim()}${newline}`; | ||
| } | ||
| export { | ||
| parseZone, | ||
| stringifyZone | ||
| }; | ||
| //#endregion | ||
| export { parseZone, stringifyZone }; | ||
| //# sourceMappingURL=index.js.map |
+12
-11
| { | ||
| "name": "dnsz", | ||
| "version": "4.3.2", | ||
| "version": "4.3.4", | ||
| "description": "Generic DNS zone file parser and stringifier", | ||
@@ -17,13 +17,14 @@ "author": "silverwind <me@silverwind.io>", | ||
| "devDependencies": { | ||
| "@types/node": "22.15.3", | ||
| "eslint": "8.57.0", | ||
| "eslint-config-silverwind": "100.1.4", | ||
| "typescript-config-silverwind": "9.0.7", | ||
| "updates": "16.4.2", | ||
| "versions": "12.1.3", | ||
| "vite": "6.3.3", | ||
| "vite-config-silverwind": "5.2.3", | ||
| "vitest": "3.1.2", | ||
| "vitest-config-silverwind": "10.0.2" | ||
| "@types/node": "24.10.1", | ||
| "eslint": "9.39.1", | ||
| "eslint-config-silverwind": "112.0.1", | ||
| "tsdown": "0.16.7", | ||
| "tsdown-config-silverwind": "1.5.3", | ||
| "typescript-config-silverwind": "14.0.0", | ||
| "updates": "16.9.1", | ||
| "versions": "14.0.3", | ||
| "vite-config-silverwind": "6.0.5", | ||
| "vitest": "4.0.14", | ||
| "vitest-config-silverwind": "10.4.2" | ||
| } | ||
| } |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IACxB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACtB,wBAAwB;IACxB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uGAAuG;IACvG,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0DAA0D;IAC1D,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IAC/B,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,6GAA6G;IAC7G,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAuPF,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,EAAC,aAAoB,EAAE,IAAY,EAAE,UAAe,EAAE,YAAmB,EAAE,IAAY,EAAC,GAAE,gBAAqB,GAAG,WAAW,CAmFnK;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,EAAC,IAAY,EAAE,QAAe,EAAE,IAAY,EAAC,GAAE,oBAAyB,GAAG,MAAM,CA6CjI"} |
35668
95.92%11
10%327
-6.84%