@willbooster/shared-lib 10.23.0 → 10.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/appEnv.cjs.map +1 -1
  2. package/dist/appEnv.js.map +1 -1
  3. package/dist/array.cjs.map +1 -1
  4. package/dist/array.js.map +1 -1
  5. package/dist/assert.cjs.map +1 -1
  6. package/dist/assert.js.map +1 -1
  7. package/dist/concurrency.cjs.map +1 -1
  8. package/dist/concurrency.js.map +1 -1
  9. package/dist/crypto.cjs.map +1 -1
  10. package/dist/crypto.js.map +1 -1
  11. package/dist/error.cjs.map +1 -1
  12. package/dist/error.js.map +1 -1
  13. package/dist/filePath.cjs.map +1 -1
  14. package/dist/filePath.js.map +1 -1
  15. package/dist/foreignCjk.cjs.map +1 -1
  16. package/dist/foreignCjk.js.map +1 -1
  17. package/dist/formatIsoDate.cjs.map +1 -1
  18. package/dist/formatIsoDate.js.map +1 -1
  19. package/dist/github.cjs.map +1 -1
  20. package/dist/github.js.map +1 -1
  21. package/dist/githubTemplates.cjs.map +1 -1
  22. package/dist/githubTemplates.js.map +1 -1
  23. package/dist/http.cjs.map +1 -1
  24. package/dist/http.js.map +1 -1
  25. package/dist/humanize.cjs.map +1 -1
  26. package/dist/humanize.js.map +1 -1
  27. package/dist/index.cjs +1 -1
  28. package/dist/index.d.ts +2 -2
  29. package/dist/index.js +1 -1
  30. package/dist/mail.cjs.map +1 -1
  31. package/dist/mail.js.map +1 -1
  32. package/dist/markdown.cjs +6 -5
  33. package/dist/markdown.cjs.map +1 -1
  34. package/dist/markdown.d.ts +11 -3
  35. package/dist/markdown.js +6 -5
  36. package/dist/markdown.js.map +1 -1
  37. package/dist/number.cjs.map +1 -1
  38. package/dist/number.js.map +1 -1
  39. package/dist/object.cjs.map +1 -1
  40. package/dist/object.js.map +1 -1
  41. package/dist/parseCommandLineArgs.cjs.map +1 -1
  42. package/dist/parseCommandLineArgs.js.map +1 -1
  43. package/dist/parseIsoDate.cjs.map +1 -1
  44. package/dist/parseIsoDate.js.map +1 -1
  45. package/dist/prompt.cjs.map +1 -1
  46. package/dist/prompt.js.map +1 -1
  47. package/dist/promptFormatters.cjs.map +1 -1
  48. package/dist/promptFormatters.js.map +1 -1
  49. package/dist/recoverJson.cjs.map +1 -1
  50. package/dist/recoverJson.js.map +1 -1
  51. package/dist/shuffle.cjs.map +1 -1
  52. package/dist/shuffle.js.map +1 -1
  53. package/dist/sleep.cjs.map +1 -1
  54. package/dist/sleep.js.map +1 -1
  55. package/dist/sqlite.cjs.map +1 -1
  56. package/dist/sqlite.js.map +1 -1
  57. package/dist/text.cjs +2 -2
  58. package/dist/text.cjs.map +1 -1
  59. package/dist/text.d.ts +2 -0
  60. package/dist/text.js +2 -2
  61. package/dist/text.js.map +1 -1
  62. package/dist/throttledFetch.cjs.map +1 -1
  63. package/dist/throttledFetch.js.map +1 -1
  64. package/dist/timeout.cjs.map +1 -1
  65. package/dist/timeout.js.map +1 -1
  66. package/dist/url.cjs.map +1 -1
  67. package/dist/url.js.map +1 -1
  68. package/dist/yaml.cjs.map +1 -1
  69. package/dist/yaml.js.map +1 -1
  70. package/dist/zenkaku.cjs.map +1 -1
  71. package/dist/zenkaku.js.map +1 -1
  72. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{getAppEnv as e}from"./appEnv.js";import{chunk as t}from"./array.js";import{ensureTruthy as n}from"./assert.js";import{forEachConcurrently as r,mapConcurrently as i}from"./concurrency.js";import{sha256HexAsync as a,timingSafeEqualStringAsync as o}from"./crypto.js";import{BUG_ISSUE_SECTIONS as s,CHANGE_ISSUE_SECTIONS as c,ISSUE_TEMPLATE_RULES as l,PULL_REQUEST_BODY_RULES as u,PULL_REQUEST_REQUIREMENTS_RULES as d,PULL_REQUEST_SECTIONS as f,renderSectionChecklist as p,renderSectionTemplate as m}from"./githubTemplates.js";import{sleep as h}from"./sleep.js";import{errorify as g,getErrorMessage as _,hasErrorCode as v,ignoreEnoent as y,ignoreEnoentAsync as b,ignoreError as x,ignoreErrorAsync as S,withRetry as C}from"./error.js";import{getFileExtension as w}from"./filePath.js";import{formatIsoDateInTimeZone as T}from"./formatIsoDate.js";import{parseGitHubRepositoryFullName as E}from"./github.js";import{getFormString as D,parseBearerToken as O}from"./http.js";import{detectForeignCjkInJapanese as k,findForeignCjkCharactersInJapanese as A}from"./foreignCjk.js";import{formatDuration as j,formatElapsedTimeInJapanese as M,humanizeNumber as N}from"./humanize.js";import{mailTemplates as P}from"./mail.js";import{isRecord as F,omitUndefined as I}from"./object.js";import{extractCodeBlocks as L,extractIfSingleOutermostCodeBlock as R,extractSections as z,extractTaggedCodeBlock as B,parseMarkdownSections as V}from"./markdown.js";import{parseIsoDate as H}from"./parseIsoDate.js";import{parseCommandLineArgs as U}from"./parseCommandLineArgs.js";import{clamp as W,parsePositiveInteger as G}from"./number.js";import{escapeHtml as K,escapeRegExp as q,quoteForShell as J,toCodeBlock as Y,toTildeCodeBlock as X,truncate as Z}from"./text.js";import{escapePromptTag as Q,formatPrompt as $,serializeForPrompt as ee,serializeForPromptInTag as te,truncateForPrompt as ne}from"./prompt.js";import{formatFilesForPrompt as re,formatMessagesForPrompt as ie}from"./promptFormatters.js";import{shuffle as ae,shuffleWithSeed as oe}from"./shuffle.js";import{recoverJson as se}from"./recoverJson.js";import{escapeLikePattern as ce,getConnectionLevelSqlitePragmas as le,getPersistentSqlitePragmas as ue}from"./sqlite.js";import{TEST_WRITING_RULES as de}from"./testingRules.js";import{createThrottledFetch as fe}from"./throttledFetch.js";import{raceWithTimeout as pe}from"./timeout.js";import{getSafeRedirectPath as me}from"./url.js";import{yamlStringifyOptions as he}from"./yaml.js";import{zenkakuAlphanumericalsToHankaku as ge}from"./zenkaku.js";export{s as BUG_ISSUE_SECTIONS,c as CHANGE_ISSUE_SECTIONS,l as ISSUE_TEMPLATE_RULES,u as PULL_REQUEST_BODY_RULES,d as PULL_REQUEST_REQUIREMENTS_RULES,f as PULL_REQUEST_SECTIONS,de as TEST_WRITING_RULES,t as chunk,W as clamp,fe as createThrottledFetch,k as detectForeignCjkInJapanese,n as ensureTruthy,g as errorify,K as escapeHtml,ce as escapeLikePattern,Q as escapePromptTag,q as escapeRegExp,L as extractCodeBlocks,R as extractIfSingleOutermostCodeBlock,z as extractSections,B as extractTaggedCodeBlock,A as findForeignCjkCharactersInJapanese,r as forEachConcurrently,j as formatDuration,M as formatElapsedTimeInJapanese,re as formatFilesForPrompt,T as formatIsoDateInTimeZone,ie as formatMessagesForPrompt,$ as formatPrompt,e as getAppEnv,le as getConnectionLevelSqlitePragmas,_ as getErrorMessage,w as getFileExtension,D as getFormString,ue as getPersistentSqlitePragmas,me as getSafeRedirectPath,v as hasErrorCode,N as humanizeNumber,y as ignoreEnoent,b as ignoreEnoentAsync,x as ignoreError,S as ignoreErrorAsync,F as isRecord,P as mailTemplates,i as mapConcurrently,I as omitUndefined,O as parseBearerToken,U as parseCommandLineArgs,E as parseGitHubRepositoryFullName,H as parseIsoDate,V as parseMarkdownSections,G as parsePositiveInteger,J as quoteForShell,pe as raceWithTimeout,se as recoverJson,p as renderSectionChecklist,m as renderSectionTemplate,ee as serializeForPrompt,te as serializeForPromptInTag,a as sha256HexAsync,ae as shuffle,oe as shuffleWithSeed,h as sleep,o as timingSafeEqualStringAsync,Y as toCodeBlock,X as toTildeCodeBlock,Z as truncate,ne as truncateForPrompt,C as withRetry,he as yamlStringifyOptions,ge as zenkakuAlphanumericalsToHankaku};
1
+ import{getAppEnv as e}from"./appEnv.js";import{chunk as t}from"./array.js";import{ensureTruthy as n}from"./assert.js";import{forEachConcurrently as r,mapConcurrently as i}from"./concurrency.js";import{sha256HexAsync as a,timingSafeEqualStringAsync as o}from"./crypto.js";import{BUG_ISSUE_SECTIONS as s,CHANGE_ISSUE_SECTIONS as c,ISSUE_TEMPLATE_RULES as l,PULL_REQUEST_BODY_RULES as u,PULL_REQUEST_REQUIREMENTS_RULES as d,PULL_REQUEST_SECTIONS as f,renderSectionChecklist as p,renderSectionTemplate as m}from"./githubTemplates.js";import{sleep as h}from"./sleep.js";import{errorify as g,getErrorMessage as _,hasErrorCode as v,ignoreEnoent as y,ignoreEnoentAsync as b,ignoreError as x,ignoreErrorAsync as S,withRetry as C}from"./error.js";import{getFileExtension as w}from"./filePath.js";import{formatIsoDateInTimeZone as T}from"./formatIsoDate.js";import{parseGitHubRepositoryFullName as E}from"./github.js";import{getFormString as D,parseBearerToken as O}from"./http.js";import{detectForeignCjkInJapanese as k,findForeignCjkCharactersInJapanese as A}from"./foreignCjk.js";import{formatDuration as j,formatElapsedTimeInJapanese as M,humanizeNumber as N}from"./humanize.js";import{mailTemplates as P}from"./mail.js";import{isRecord as F,omitUndefined as I}from"./object.js";import{extractCodeBlocks as L,extractIfSingleOutermostCodeBlock as R,extractSections as z,extractTaggedCodeBlock as B,parseMarkdownSections as V}from"./markdown.js";import{parseIsoDate as H}from"./parseIsoDate.js";import{parseCommandLineArgs as U}from"./parseCommandLineArgs.js";import{clamp as W,parsePositiveInteger as G}from"./number.js";import{escapeHtml as K,escapeRegExp as q,getTildeFence as J,quoteForShell as Y,toCodeBlock as X,toTildeCodeBlock as Z,truncate as Q}from"./text.js";import{escapePromptTag as $,formatPrompt as ee,serializeForPrompt as te,serializeForPromptInTag as ne,truncateForPrompt as re}from"./prompt.js";import{formatFilesForPrompt as ie,formatMessagesForPrompt as ae}from"./promptFormatters.js";import{shuffle as oe,shuffleWithSeed as se}from"./shuffle.js";import{recoverJson as ce}from"./recoverJson.js";import{escapeLikePattern as le,getConnectionLevelSqlitePragmas as ue,getPersistentSqlitePragmas as de}from"./sqlite.js";import{TEST_WRITING_RULES as fe}from"./testingRules.js";import{createThrottledFetch as pe}from"./throttledFetch.js";import{raceWithTimeout as me}from"./timeout.js";import{getSafeRedirectPath as he}from"./url.js";import{yamlStringifyOptions as ge}from"./yaml.js";import{zenkakuAlphanumericalsToHankaku as _e}from"./zenkaku.js";export{s as BUG_ISSUE_SECTIONS,c as CHANGE_ISSUE_SECTIONS,l as ISSUE_TEMPLATE_RULES,u as PULL_REQUEST_BODY_RULES,d as PULL_REQUEST_REQUIREMENTS_RULES,f as PULL_REQUEST_SECTIONS,fe as TEST_WRITING_RULES,t as chunk,W as clamp,pe as createThrottledFetch,k as detectForeignCjkInJapanese,n as ensureTruthy,g as errorify,K as escapeHtml,le as escapeLikePattern,$ as escapePromptTag,q as escapeRegExp,L as extractCodeBlocks,R as extractIfSingleOutermostCodeBlock,z as extractSections,B as extractTaggedCodeBlock,A as findForeignCjkCharactersInJapanese,r as forEachConcurrently,j as formatDuration,M as formatElapsedTimeInJapanese,ie as formatFilesForPrompt,T as formatIsoDateInTimeZone,ae as formatMessagesForPrompt,ee as formatPrompt,e as getAppEnv,ue as getConnectionLevelSqlitePragmas,_ as getErrorMessage,w as getFileExtension,D as getFormString,de as getPersistentSqlitePragmas,he as getSafeRedirectPath,J as getTildeFence,v as hasErrorCode,N as humanizeNumber,y as ignoreEnoent,b as ignoreEnoentAsync,x as ignoreError,S as ignoreErrorAsync,F as isRecord,P as mailTemplates,i as mapConcurrently,I as omitUndefined,O as parseBearerToken,U as parseCommandLineArgs,E as parseGitHubRepositoryFullName,H as parseIsoDate,V as parseMarkdownSections,G as parsePositiveInteger,Y as quoteForShell,me as raceWithTimeout,ce as recoverJson,p as renderSectionChecklist,m as renderSectionTemplate,te as serializeForPrompt,ne as serializeForPromptInTag,a as sha256HexAsync,oe as shuffle,se as shuffleWithSeed,h as sleep,o as timingSafeEqualStringAsync,X as toCodeBlock,Z as toTildeCodeBlock,Q as truncate,re as truncateForPrompt,C as withRetry,ge as yamlStringifyOptions,_e as zenkakuAlphanumericalsToHankaku};
package/dist/mail.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mail.cjs","names":["mailTemplates","appName","resetUrl","expirationDuration"],"sources":["../src/mail.ts"],"sourcesContent":["export interface MailData {\n from: string;\n subject: string;\n text: string;\n}\n\nclass MailTemplates {\n resetPassword(appName: string, resetUrl: string, expirationDuration?: string): MailData {\n return {\n from: 'no-reply@willbooster.dev',\n subject: 'パスワード再設定のご案内',\n text: `\n${appName} をご利用いただき誠にありがとうございます。\n\nお客様からパスワード再設定のリクエストを受け取りました。\n以下のURLをクリックして、新しいパスワードを設定してください。\n\n${resetUrl}\n${\n expirationDuration\n ? `\n上記のURLは発行から約${expirationDuration}で失効します。\n期限を過ぎてしまった場合や、URLが機能しない場合は、\n恐れ入りますが、再度パスワードを再設定してください。\n`\n : ''\n}\nなお、本メールアドレスは配信専用ですので、返信いただけません。\n\n引き続き ${appName} をよろしくお願い申し上げます。\n `.trim(),\n };\n }\n}\n\nexport const mailTemplates = new MailTemplates();\n"],"mappings":"aAmCA,MAAaA,EAAgB,IAAI,KA7Bb,CAClB,cAAc,EAAiB,EAAkB,EAAuC,CACtF,MAAO,CACL,KAAM,2BACN,QAAS,eACT,KAAM;EACVC,EAAQ;;;;;EAKRC,EAAS;EAETC,EACI;cACQA,EAAmB;;;EAI3B,GACL;;;OAGMF,EAAQ;MACT,KAAK,CACP,CACF,CACF"}
1
+ {"version":3,"file":"mail.cjs","names":["mailTemplates","appName","resetUrl","expirationDuration"],"sources":["../src/mail.ts"],"sourcesContent":["export interface MailData {\n from: string;\n subject: string;\n text: string;\n}\n\nclass MailTemplates {\n resetPassword(appName: string, resetUrl: string, expirationDuration?: string): MailData {\n return {\n from: 'no-reply@willbooster.dev',\n subject: 'パスワード再設定のご案内',\n text: `\n${appName} をご利用いただき誠にありがとうございます。\n\nお客様からパスワード再設定のリクエストを受け取りました。\n以下のURLをクリックして、新しいパスワードを設定してください。\n\n${resetUrl}\n${\n expirationDuration\n ? `\n上記のURLは発行から約${expirationDuration}で失効します。\n期限を過ぎてしまった場合や、URLが機能しない場合は、\n恐れ入りますが、再度パスワードを再設定してください。\n`\n : ''\n}\nなお、本メールアドレスは配信専用ですので、返信いただけません。\n\n引き続き ${appName} をよろしくお願い申し上げます。\n `.trim(),\n };\n }\n}\n\nexport const mailTemplates = new MailTemplates();\n"],"mappings":"A,aAmCA,MAAaA,EAAgB,IAAI,KA7Bb,CAClB,cAAc,EAAiB,EAAkB,EAAuC,CACtF,MAAO,CACL,KAAM,2BACN,QAAS,eACT,KAAM;EACVC,EAAQ;;;;;EAKRC,EAAS;EAETC,EACI;cACQA,EAAmB;;;EAI3B,GACL;;;OAGMF,EAAQ;MACT,KAAK,CACP,CACF,CACF,E"}
package/dist/mail.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mail.js","names":["mailTemplates","appName","resetUrl","expirationDuration"],"sources":["../src/mail.ts"],"sourcesContent":["export interface MailData {\n from: string;\n subject: string;\n text: string;\n}\n\nclass MailTemplates {\n resetPassword(appName: string, resetUrl: string, expirationDuration?: string): MailData {\n return {\n from: 'no-reply@willbooster.dev',\n subject: 'パスワード再設定のご案内',\n text: `\n${appName} をご利用いただき誠にありがとうございます。\n\nお客様からパスワード再設定のリクエストを受け取りました。\n以下のURLをクリックして、新しいパスワードを設定してください。\n\n${resetUrl}\n${\n expirationDuration\n ? `\n上記のURLは発行から約${expirationDuration}で失効します。\n期限を過ぎてしまった場合や、URLが機能しない場合は、\n恐れ入りますが、再度パスワードを再設定してください。\n`\n : ''\n}\nなお、本メールアドレスは配信専用ですので、返信いただけません。\n\n引き続き ${appName} をよろしくお願い申し上げます。\n `.trim(),\n };\n }\n}\n\nexport const mailTemplates = new MailTemplates();\n"],"mappings":"AAmCA,MAAaA,EAAgB,IAAI,KA7Bb,CAClB,cAAc,EAAiB,EAAkB,EAAuC,CACtF,MAAO,CACL,KAAM,2BACN,QAAS,eACT,KAAM;EACVC,EAAQ;;;;;EAKRC,EAAS;EAETC,EACI;cACQA,EAAmB;;;EAI3B,GACL;;;OAGMF,EAAQ;MACT,KAAK,CACP,CACF,CACF"}
1
+ {"version":3,"file":"mail.js","names":["mailTemplates","appName","resetUrl","expirationDuration"],"sources":["../src/mail.ts"],"sourcesContent":["export interface MailData {\n from: string;\n subject: string;\n text: string;\n}\n\nclass MailTemplates {\n resetPassword(appName: string, resetUrl: string, expirationDuration?: string): MailData {\n return {\n from: 'no-reply@willbooster.dev',\n subject: 'パスワード再設定のご案内',\n text: `\n${appName} をご利用いただき誠にありがとうございます。\n\nお客様からパスワード再設定のリクエストを受け取りました。\n以下のURLをクリックして、新しいパスワードを設定してください。\n\n${resetUrl}\n${\n expirationDuration\n ? `\n上記のURLは発行から約${expirationDuration}で失効します。\n期限を過ぎてしまった場合や、URLが機能しない場合は、\n恐れ入りますが、再度パスワードを再設定してください。\n`\n : ''\n}\nなお、本メールアドレスは配信専用ですので、返信いただけません。\n\n引き続き ${appName} をよろしくお願い申し上げます。\n `.trim(),\n };\n }\n}\n\nexport const mailTemplates = new MailTemplates();\n"],"mappings":"AAmCA,MAAaA,EAAgB,IAAI,KA7Bb,CAClB,cAAc,EAAiB,EAAkB,EAAuC,CACtF,MAAO,CACL,KAAM,2BACN,QAAS,eACT,KAAM;EACVC,EAAQ;;;;;EAKRC,EAAS;EAETC,EACI;cACQA,EAAmB;;;EAI3B,GACL;;;OAGMF,EAAQ;MACT,KAAK,CACP,CACF,CACF,E"}
package/dist/markdown.cjs CHANGED
@@ -1,6 +1,7 @@
1
- "use strict";function e(e){let t=g(e),n=[];for(let e=0;e<t.length;e++){let r=f(t[e]??``);if(!r)continue;let i=p(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>h(e,r.indent)).join(`
2
- `),isClosed:i<t.length}),e=i}return n}function t(e,t){let n=u(e);return!n||t?.length&&!t.some(e=>e.toLowerCase()===n.language)?e:n.code}const n=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function r(e,t){let r=`<${t}>`,a=i(g(e),r);if(a!==void 0)return a;let o=u(e);if(o?.isWhole&&n.has(o.language))return i(g(o.code),r)}function i(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=f(e[a]??``);if(!o)continue;let s=p(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>h(e,o.indent)).join(`
3
- `).trim()}}function a(e,t){let n=o(e).map(e=>({...e,key:s(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:s(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function o(e){let t=u(e),r=t&&n.has(t.language)?t:void 0;if(r?.isWhole)return l(r.code);let i=l(e);return i.length===0&&r?l(r.code):i}function s(e){return c(c(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function c(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function l(e){let t=g(e),n=d(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
4
- `).trim()}})}function u(e){let t=g(e.replace(/^\s*\n/u,``).trimEnd()),n=f(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&m(t[r]??``,n)?r:p(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>h(e,n.indent)).join(`
5
- `),isWhole:i>=r}}function d(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function f(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function p(e,t,n){let r=t;for(;r<e.length&&!m(e[r]??``,n);)r++;return r}function m(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function h(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function g(e){return e.split(/\r?\n/u)}exports.extractCodeBlocks=e,exports.extractIfSingleOutermostCodeBlock=t,exports.extractSections=a,exports.extractTaggedCodeBlock=r,exports.parseMarkdownSections=o;
1
+ "use strict";function e(e){let t=y(e),n=[];for(let e=0;e<t.length;e++){let r=h(t[e]??``);if(!r)continue;let i=g(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>v(e,r.indent)).join(`
2
+ `),isClosed:i<t.length}),e=i}return n}function t(e,t){let{languages:i,fence:a}=n(t)?{languages:t}:t??{},o=e=>!i?.length||i.some(t=>t.toLowerCase()===e),s=a===void 0?void 0:r(e,a,o);if(s!==void 0)return s;let c=p(e);return!c||!o(c.language)?e:c.code}function n(e){return Array.isArray(e)}function r(e,t,n){let r=y(e),a=r.findLastIndex(e=>e.trimEnd()===t),o=i(r,a,t,n);if(o!==void 0)return r.slice(o+1,a).join(`
3
+ `)}function i(e,t,n,r){for(let i=0;i<t;i++){let t=e[i]??``,a=t.trimEnd(),o=a.startsWith(n)&&a.charAt(n.length)!==n.charAt(0),s=a.slice(n.length).trim().split(/\s/u)[0]?.toLowerCase()??``;if(o&&(s===``||r(s)))return i;let c=h(t);c&&(i=g(e,i+1,c))}}const a=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function o(e,t){let n=`<${t}>`,r=s(y(e),n);if(r!==void 0)return r;let i=p(e);if(i?.isWhole&&a.has(i.language))return s(y(i.code),n)}function s(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=h(e[a]??``);if(!o)continue;let s=g(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>v(e,o.indent)).join(`
4
+ `).trim()}}function c(e,t){let n=l(e).map(e=>({...e,key:u(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:u(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function l(e){let t=p(e),n=t&&a.has(t.language)?t:void 0;if(n?.isWhole)return f(n.code);let r=f(e);return r.length===0&&n?f(n.code):r}function u(e){return d(d(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function d(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function f(e){let t=y(e),n=m(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
5
+ `).trim()}})}function p(e){let t=y(e.replace(/^\s*\n/u,``).trimEnd()),n=h(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&_(t[r]??``,n)?r:g(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>v(e,n.indent)).join(`
6
+ `),isWhole:i>=r}}function m(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function h(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function g(e,t,n){let r=t;for(;r<e.length&&!_(e[r]??``,n);)r++;return r}function _(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function v(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function y(e){return e.split(/\r?\n/u)}exports.extractCodeBlocks=e,exports.extractIfSingleOutermostCodeBlock=t,exports.extractSections=c,exports.extractTaggedCodeBlock=o,exports.parseMarkdownSections=l;
6
7
  //# sourceMappingURL=markdown.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.cjs","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","block","findOutermostCodeBlock","text","languages","language","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","start","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not one of a nonempty `languages` list\n * (compared case-insensitively). An omitted or empty list allows any language.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n */\nexport function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string {\n const block = findOutermostCodeBlock(text);\n if (!block) return text;\n if (languages?.length && !languages.some((language) => language.toLowerCase() === block.language)) return text;\n return block.code;\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"markdown.cjs","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","isLanguageList","languagesOrOptions","isAllowed","languages","allowed","language","known","findBlockWithKnownFence","text","block","findOutermostCodeBlock","value","start","findSentOpeningFence","trimmed","isSentFence","opener","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\nexport interface ExtractCodeBlockOptions {\n /** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */\n languages?: readonly string[];\n /** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */\n fence?: string;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not allowed. A plain array is taken as `languages`.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n * With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line\n * holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line\n * holding exactly `fence` is returned. Without such a pair, the rules above apply.\n */\nexport function extractIfSingleOutermostCodeBlock(\n text: string,\n languagesOrOptions?: readonly string[] | ExtractCodeBlockOptions\n): string {\n const { languages, fence }: ExtractCodeBlockOptions = isLanguageList(languagesOrOptions)\n ? { languages: languagesOrOptions }\n : (languagesOrOptions ?? {});\n const isAllowed = (language: string): boolean =>\n !languages?.length || languages.some((allowed) => allowed.toLowerCase() === language);\n const known = fence === undefined ? undefined : findBlockWithKnownFence(text, fence, isAllowed);\n if (known !== undefined) return known;\n const block = findOutermostCodeBlock(text);\n if (!block || !isAllowed(block.language)) return text;\n return block.code;\n}\n\nfunction isLanguageList(value: readonly string[] | ExtractCodeBlockOptions | undefined): value is readonly string[] {\n return Array.isArray(value);\n}\n\nfunction findBlockWithKnownFence(\n text: string,\n fence: string,\n isAllowed: (language: string) => boolean\n): string | undefined {\n const lines = splitLines(text);\n const end = lines.findLastIndex((line) => line.trimEnd() === fence);\n const start = findSentOpeningFence(lines, end, fence, isAllowed);\n if (start === undefined) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\n/** Returns the index of the first line before `end` opening `fence` with an allowed language or none, skipping other blocks. */\nfunction findSentOpeningFence(\n lines: readonly string[],\n end: number,\n fence: string,\n isAllowed: (language: string) => boolean\n): number | undefined {\n for (let index = 0; index < end; index++) {\n const line = lines[index] ?? '';\n const trimmed = line.trimEnd();\n const isSentFence = trimmed.startsWith(fence) && trimmed.charAt(fence.length) !== fence.charAt(0);\n const language = trimmed.slice(fence.length).trim().split(/\\s/u)[0]?.toLowerCase() ?? '';\n if (isSentFence && (language === '' || isAllowed(language))) return index;\n const opener = parseOpeningFence(line);\n if (opener) index = findClosingFence(lines, index + 1, opener);\n }\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"A,aAqBA,SAAgBA,EAAkB,EAA+B,CAC/D,IAAMC,EAAQC,EAAWC,CAAQ,EAC3BC,EAAsB,CAAC,EAC7B,IAAK,IAAIC,EAAQ,EAAGA,EAAQJ,EAAM,OAAQ,IAAS,CACjD,IAAMK,EAAQC,EAAkBN,EAAMI,IAAU,EAAE,EAClD,GAAI,CAACC,EAAO,SACZ,IAAME,EAAMC,EAAiBR,EAAOI,EAAQ,EAAGC,CAAK,EACpDF,EAAO,KAAK,CACV,GAAIE,EAAM,UAAY,CAAE,SAAUA,EAAM,QAAS,EACjD,KAAML,EACH,MAAMI,EAAQ,EAAGG,CAAG,CAAC,CACrB,IAAK,GAASE,EAAYC,EAAML,EAAM,MAAM,CAAC,CAAC,CAC9C,KAAK;CAAI,EACZ,SAAUE,EAAMP,EAAM,MACxB,CAAC,EACD,EAAQO,CACV,CACA,OAAOJ,CACT,CAmBA,SAAgBQ,EACd,EACA,EACQ,CACR,GAAM,CAAE,YAAW,SAAmCC,EAAeC,CAAkB,EACnF,CAAE,UAAWA,CAAmB,EAC/BA,GAAsB,CAAC,EACtBC,EAAa,GACjB,CAACC,GAAW,QAAUA,EAAU,KAAM,GAAYC,EAAQ,YAAY,IAAMC,CAAQ,EAChFC,EAAQb,IAAU,IAAA,GAAY,IAAA,GAAYc,EAAwBC,EAAMf,EAAOS,CAAS,EAC9F,GAAII,IAAU,IAAA,GAAW,OAAOA,EAChC,IAAMG,EAAQC,EAAuBF,CAAI,EAEzC,MADI,CAACC,GAAS,CAACP,EAAUO,EAAM,QAAQ,EAAUD,EAC1CC,EAAM,IACf,CAEA,SAAST,EAAe,EAA4F,CAClH,OAAO,MAAM,QAAQW,CAAK,CAC5B,CAEA,SAASJ,EACP,EACA,EACA,EACoB,CACpB,IAAMnB,EAAQC,EAAWmB,CAAI,EACvBb,EAAMP,EAAM,cAAe,GAASU,EAAK,QAAQ,IAAML,CAAK,EAC5DmB,EAAQC,EAAqBzB,EAAOO,EAAKF,EAAOS,CAAS,EAC/D,GAAIU,IAAU,IAAA,GACd,OAAOxB,EAAM,MAAMwB,EAAQ,EAAGjB,CAAG,CAAC,CAAC,KAAK;CAAI,CAC9C,CAGA,SAASkB,EACP,EACA,EACA,EACA,EACoB,CACpB,IAAK,IAAIrB,EAAQ,EAAGA,EAAQG,EAAK,IAAS,CACxC,IAAMG,EAAOV,EAAMI,IAAU,GACvBsB,EAAUhB,EAAK,QAAQ,EACvBiB,EAAcD,EAAQ,WAAWrB,CAAK,GAAKqB,EAAQ,OAAOrB,EAAM,MAAM,IAAMA,EAAM,OAAO,CAAC,EAC1FY,EAAWS,EAAQ,MAAMrB,EAAM,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,EAAE,YAAY,GAAK,GACtF,GAAIsB,IAAgBV,IAAa,IAAMH,EAAUG,CAAQ,GAAI,OAAOb,EACpE,IAAMwB,EAAStB,EAAkBI,CAAI,EACjCkB,IAAQ,EAAQpB,EAAiBR,EAAOI,EAAQ,EAAGwB,CAAM,EAC/D,CACF,CAEA,MAAMC,kBAA6B,IAAI,IAAI,CAAC,GAAI,WAAY,IAAI,CAAC,EASjE,SAAgBC,EAAuB,EAAc,EAAqC,CACxF,IAAMC,EAAM,IAAIC,EAAQ,GAClBC,EAAQC,EAAoBjC,EAAWmB,CAAI,EAAGW,CAAG,EACvD,GAAIE,IAAU,IAAA,GAAW,OAAOA,EAGhC,IAAME,EAAUb,EAAuBF,CAAI,EAC3C,GAAIe,GAAS,SAAWN,EAA2B,IAAIM,EAAQ,QAAQ,EACrE,OAAOD,EAAoBjC,EAAWkC,EAAQ,IAAI,EAAGJ,CAAG,CAE5D,CAEA,SAASG,EAAoB,EAA0B,EAAiC,CACtF,IAAK,IAAI9B,EAAQ,EAAGA,EAAQJ,EAAM,OAAQ,IAAS,CACjD,IAAMU,EAAOV,EAAMI,IAAU,GACvBgC,EAAiB9B,EAAkBI,CAAI,EAC7C,GAAI0B,EAAgB,CAClB,EAAQ5B,EAAiBR,EAAOI,EAAQ,EAAGgC,CAAc,EACzD,QACF,CACA,GAAI1B,EAAK,KAAK,IAAMqB,EAAK,SACzB,IAAIM,EAAajC,EAAQ,EACzB,KAAOiC,EAAarC,EAAM,QAAU,CAACA,EAAMqC,EAAW,EAAE,KAAK,GAAG,IAChE,IAAMhC,EAAQC,EAAkBN,EAAMqC,IAAe,EAAE,EACvD,GAAI,CAAChC,EAAO,SACZ,IAAME,EAAMC,EAAiBR,EAAOqC,EAAa,EAAGhC,CAAK,EACzD,GAAIE,EAAMP,EAAM,OACd,OAAOA,EACJ,MAAMqC,EAAa,EAAG9B,CAAG,CAAC,CAC1B,IAAK,GAAgBE,EAAY6B,EAAajC,EAAM,MAAM,CAAC,CAAC,CAC5D,KAAK;CAAI,CAAC,CACV,KAAK,CAEZ,CACF,CAmBA,SAAgBkC,EACd,EACA,EAC+B,CAC/B,IAAMC,EAAaC,EAAsBvC,CAAQ,CAAC,CAAC,IAAK,IAAa,CACnE,GAAGwC,EACH,IAAKC,EAAiBD,EAAQ,OAAO,CACvC,EAAE,EACIE,EAAY,CAAC,GAAG,IAAI,IAAIC,CAAK,CAAC,CAAC,CAAC,IAAK,IAAU,CAAE,OAAM,IAAKF,EAAiBG,CAAI,CAAE,EAAE,EACrFC,EAA0C,OAAO,OAAO,IAAI,EAClE,IAAK,GAAM,CAAE,OAAM,SAASH,EAAW,CACrC,IAAII,EACJ,IAAK,IAAMC,KAAaT,EAClBS,EAAU,MAAQC,IAEpB,CAACF,GACDC,EAAU,MAAQD,EAAK,OACtBC,EAAU,QAAUD,EAAK,OAASC,EAAU,UAAYH,GAAQE,EAAK,UAAYF,KAElF,EAAOG,GAGND,IACDA,EAAK,UAAYF,GAAQF,EAAU,KAAM,GAAUO,EAAM,OAASL,GAAQK,EAAM,MAAQD,CAAG,GAC3FF,EAAK,UAAS,EAASF,GAAQE,EAAK,SAC1C,CACA,OAAOD,CACT,CASA,SAAgBN,EAAsB,EAAqC,CACzE,IAAMpB,EAAQC,EAAuBpB,CAAQ,EACvCiC,EAAUd,GAASQ,EAA2B,IAAIR,EAAM,QAAQ,EAAIA,EAAQ,IAAA,GAClF,GAAIc,GAAS,QAAS,OAAOiB,EAAcjB,EAAQ,IAAI,EACvD,IAAMY,EAAWK,EAAclD,CAAQ,EACvC,OAAO6C,EAAS,SAAW,GAAKZ,EAAUiB,EAAcjB,EAAQ,IAAI,EAAIY,CAC1E,CAEA,SAASJ,EAAiB,EAAyB,CACjD,OAAOU,EAAkBA,EAAkBC,CAAO,CAAC,CAAC,QAAQ,qCAAsC,EAAE,CAAC,CAAC,CACnG,WAAW,QAAS,GAAG,CAAC,CACxB,YAAY,CACjB,CAEA,SAASD,EAAkB,EAAsB,CAE/C,OAAOjC,EAAK,WAAW,oCAAqC,EAAE,CAChE,CAEA,SAASgC,EAAc,EAAqC,CAC1D,IAAMpD,EAAQC,EAAWC,CAAQ,EAC3BqD,EAAWC,EAAaxD,CAAK,EACnC,OAAOuD,EAAS,KAAK,EAAS,IAAU,CACtC,IAAIE,EAAYrD,EAAQ,EACxB,KAAOqD,EAAYF,EAAS,SAAWA,EAASE,EAAU,EAAE,OAAS,GAAKH,EAAQ,OAAO,IACzF,MAAO,CACL,MAAOA,EAAQ,MACf,QAASA,EAAQ,KACjB,QAAStD,EACN,MAAMsD,EAAQ,MAAQ,EAAGC,EAASE,EAAU,EAAE,KAAK,CAAC,CACpD,KAAK;CAAI,CAAC,CACV,KAAK,CACV,CACF,CAAC,CACH,CAEA,SAASnC,EAAuB,EAAgF,CAE9G,IAAMtB,EAAQC,EAAWmB,EAAK,QAAQ,UAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,EACxDf,EAAQC,EAAkBN,EAAM,IAAM,EAAE,EAC9C,GAAI,CAACK,EAAO,OACZ,IAAMqD,EAAY1D,EAAM,OAAS,EAC3BO,EACJmD,EAAY,GAAKC,EAAe3D,EAAM0D,IAAc,GAAIrD,CAAK,EAAIqD,EAAYlD,EAAiBR,EAAO,EAAGK,CAAK,EAC/G,MAAO,CACL,SAAUA,EAAM,SAChB,KAAML,EACH,MAAM,EAAGO,CAAG,CAAC,CACb,IAAK,GAASE,EAAYC,EAAML,EAAM,MAAM,CAAC,CAAC,CAC9C,KAAK;CAAI,EACZ,QAASE,GAAOmD,CAClB,CACF,CAEA,SAASF,EAAa,EAA4E,CAChG,IAAMD,EAA6D,CAAC,EACpE,IAAK,IAAInD,EAAQ,EAAGA,EAAQJ,EAAM,OAAQ,IAAS,CACjD,IAAMU,EAAOV,EAAMI,IAAU,GACvBC,EAAQC,EAAkBI,CAAI,EACpC,GAAIL,EAAO,CACT,EAAQG,EAAiBR,EAAOI,EAAQ,EAAGC,CAAK,EAChD,QACF,CACA,IAAMuD,EAAQ,6BAA6B,KAAKlD,CAAI,EACpD,GAAI,CAACkD,EAAO,SACZ,IAAMxC,GAAQwC,EAAM,IAAM,GAAA,CAEvB,QAAQ,yBAA0B,EAAE,CAAC,CACrC,KAAK,CAAC,CACN,WAAW,iBAAkB,EAAQ,EAAoB,IACxDC,EAAQ,WAAW,GAAG,GAAKA,EAAQ,SAAS,GAAG,EAAIA,EAAQ,MAAM,EAAG,EAAE,EAAIA,CAC5E,EACFN,EAAS,KAAK,CAAE,QAAO,MAAOK,EAAM,EAAE,EAAE,QAAU,EAAG,MAAK,CAAC,CAC7D,CACA,OAAOL,CACT,CAEA,SAASjD,EAAkB,EAAiC,CAC1D,IAAMsD,EAAQ,+BAA+B,KAAKlD,CAAI,EAChDL,EAAQuD,IAAQ,GAChBE,EAAOF,IAAQ,IAAM,GAE3B,GAAI,GAACvD,GAAUA,EAAM,WAAW,GAAG,GAAKyD,EAAK,SAAS,GAAG,GACzD,MAAO,CACL,KAAMzD,EAAM,OAAO,CAAC,EACpB,OAAQA,EAAM,OACd,OAAQuD,EAAM,EAAE,EAAE,QAAU,EAC5B,SAAUE,EAAK,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,EAAE,YAAY,GAAK,EAC1D,CACF,CAGA,SAAStD,EAAiB,EAA0B,EAAe,EAAsB,CACvF,IAAIJ,EAAQoB,EACZ,KAAOpB,EAAQJ,EAAM,QAAU,CAAC2D,EAAe3D,EAAMI,IAAU,GAAIC,CAAK,GAAG,IAC3E,OAAOD,CACT,CAEA,SAASuD,EAAe,EAAc,EAAuB,CAC3D,IAAMI,EAAS,+BAA+B,KAAKrD,CAAI,CAAC,GAAG,GAC3D,OAAOqD,GAAQ,OAAO,CAAC,IAAM1D,EAAM,MAAQ0D,EAAO,QAAU1D,EAAM,MACpE,CAEA,SAASI,EAAY,EAAc,EAAwB,CACzD,IAAIL,EAAQ,EACZ,KAAOA,EAAQ4D,GAAUtD,EAAKN,KAAW,KAAK,IAC9C,OAAOM,EAAK,MAAMN,CAAK,CACzB,CAEA,SAASH,EAAW,EAAwB,CAC1C,OAAOmB,EAAK,MAAM,QAAQ,CAC5B,C"}
@@ -12,15 +12,23 @@ export interface CodeBlock {
12
12
  * inside block quotes, deeply nested list items, and indented code blocks are not.
13
13
  */
14
14
  export declare function extractCodeBlocks(markdown: string): CodeBlock[];
15
+ export interface ExtractCodeBlockOptions {
16
+ /** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */
17
+ languages?: readonly string[];
18
+ /** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */
19
+ fence?: string;
20
+ }
15
21
  /**
16
22
  * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the
17
- * response does not start with a fence or the fence's language is not one of a nonempty `languages` list
18
- * (compared case-insensitively). An omitted or empty list allows any language.
23
+ * response does not start with a fence or the fence's language is not allowed. A plain array is taken as `languages`.
19
24
  * When the response also ends with a matching closing fence, everything between its first and last lines is returned,
20
25
  * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is
21
26
  * returned and text after it is dropped; an unclosed block runs to the end of the text.
27
+ * With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line
28
+ * holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line
29
+ * holding exactly `fence` is returned. Without such a pair, the rules above apply.
22
30
  */
23
- export declare function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string;
31
+ export declare function extractIfSingleOutermostCodeBlock(text: string, languagesOrOptions?: readonly string[] | ExtractCodeBlockOptions): string;
24
32
  /**
25
33
  * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding
26
34
  * only `<tagName>` in text such as an LLM response, e.g. `<answer>\n~~~\n42\n~~~`, or `undefined` when there is none.
package/dist/markdown.js CHANGED
@@ -1,6 +1,7 @@
1
- function e(e){let t=g(e),n=[];for(let e=0;e<t.length;e++){let r=f(t[e]??``);if(!r)continue;let i=p(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>h(e,r.indent)).join(`
2
- `),isClosed:i<t.length}),e=i}return n}function t(e,t){let n=u(e);return!n||t?.length&&!t.some(e=>e.toLowerCase()===n.language)?e:n.code}const n=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function r(e,t){let r=`<${t}>`,a=i(g(e),r);if(a!==void 0)return a;let o=u(e);if(o?.isWhole&&n.has(o.language))return i(g(o.code),r)}function i(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=f(e[a]??``);if(!o)continue;let s=p(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>h(e,o.indent)).join(`
3
- `).trim()}}function a(e,t){let n=o(e).map(e=>({...e,key:s(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:s(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function o(e){let t=u(e),r=t&&n.has(t.language)?t:void 0;if(r?.isWhole)return l(r.code);let i=l(e);return i.length===0&&r?l(r.code):i}function s(e){return c(c(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function c(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function l(e){let t=g(e),n=d(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
4
- `).trim()}})}function u(e){let t=g(e.replace(/^\s*\n/u,``).trimEnd()),n=f(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&m(t[r]??``,n)?r:p(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>h(e,n.indent)).join(`
5
- `),isWhole:i>=r}}function d(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=f(r);if(i){n=p(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function f(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function p(e,t,n){let r=t;for(;r<e.length&&!m(e[r]??``,n);)r++;return r}function m(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function h(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function g(e){return e.split(/\r?\n/u)}export{e as extractCodeBlocks,t as extractIfSingleOutermostCodeBlock,a as extractSections,r as extractTaggedCodeBlock,o as parseMarkdownSections};
1
+ function e(e){let t=y(e),n=[];for(let e=0;e<t.length;e++){let r=h(t[e]??``);if(!r)continue;let i=g(t,e+1,r);n.push({...r.language&&{language:r.language},code:t.slice(e+1,i).map(e=>v(e,r.indent)).join(`
2
+ `),isClosed:i<t.length}),e=i}return n}function t(e,t){let{languages:i,fence:a}=n(t)?{languages:t}:t??{},o=e=>!i?.length||i.some(t=>t.toLowerCase()===e),s=a===void 0?void 0:r(e,a,o);if(s!==void 0)return s;let c=p(e);return!c||!o(c.language)?e:c.code}function n(e){return Array.isArray(e)}function r(e,t,n){let r=y(e),a=r.findLastIndex(e=>e.trimEnd()===t),o=i(r,a,t,n);if(o!==void 0)return r.slice(o+1,a).join(`
3
+ `)}function i(e,t,n,r){for(let i=0;i<t;i++){let t=e[i]??``,a=t.trimEnd(),o=a.startsWith(n)&&a.charAt(n.length)!==n.charAt(0),s=a.slice(n.length).trim().split(/\s/u)[0]?.toLowerCase()??``;if(o&&(s===``||r(s)))return i;let c=h(t);c&&(i=g(e,i+1,c))}}const a=/* @__PURE__ */ new Set([``,`markdown`,`md`]);function o(e,t){let n=`<${t}>`,r=s(y(e),n);if(r!==void 0)return r;let i=p(e);if(i?.isWhole&&a.has(i.language))return s(y(i.code),n)}function s(e,t){for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}if(r.trim()!==t)continue;let a=n+1;for(;a<e.length&&!e[a]?.trim();)a++;let o=h(e[a]??``);if(!o)continue;let s=g(e,a+1,o);if(s<e.length)return e.slice(a+1,s).map(e=>v(e,o.indent)).join(`
4
+ `).trim()}}function c(e,t){let n=l(e).map(e=>({...e,key:u(e.heading)})),r=[...new Set(t)].map(e=>({name:e,key:u(e)})),i=Object.create(null);for(let{name:e,key:t}of r){let a;for(let r of n)r.key===t&&(!a||r.depth<a.depth||r.depth===a.depth&&r.heading===e&&a.heading!==e)&&(a=r);a&&(a.heading!==e&&r.some(n=>n.name!==e&&n.key===t)||a.content&&(i[e]=a.content))}return i}function l(e){let t=p(e),n=t&&a.has(t.language)?t:void 0;if(n?.isWhole)return f(n.code);let r=f(e);return r.length===0&&n?f(n.code):r}function u(e){return d(d(e).replace(/^\d+(?:\.\d+)*(?:\.\s+|\)\s*|\s+)/u,``)).replaceAll(/\s+/gu,` `).toLowerCase()}function d(e){return e.replaceAll(/^[\s*_`~"'「」]+|[\s*_`~"'「」::]+$/gu,``)}function f(e){let t=y(e),n=m(t);return n.map((e,r)=>{let i=r+1;for(;i<n.length&&(n[i]?.depth??0)>e.depth;)i++;return{depth:e.depth,heading:e.text,content:t.slice(e.index+1,n[i]?.index).join(`
5
+ `).trim()}})}function p(e){let t=y(e.replace(/^\s*\n/u,``).trimEnd()),n=h(t[0]??``);if(!n)return;let r=t.length-1,i=r>0&&_(t[r]??``,n)?r:g(t,1,n);return{language:n.language,code:t.slice(1,i).map(e=>v(e,n.indent)).join(`
6
+ `),isWhole:i>=r}}function m(e){let t=[];for(let n=0;n<e.length;n++){let r=e[n]??``,i=h(r);if(i){n=g(e,n+1,i);continue}let a=/^(#{1,6})(?:[ \t]+(.*))?$/u.exec(r);if(!a)continue;let o=(a[2]??``).replace(/(?:^|[ \t]+)#+[ \t]*$/u,``).trim().replaceAll(/(`+)(.+?)\1/gu,(e,t,n)=>n.startsWith(` `)&&n.endsWith(` `)?n.slice(1,-1):n);t.push({index:n,depth:a[1]?.length??1,text:o})}return t}function h(e){let t=/^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(e),n=t?.[2],r=t?.[3]??``;if(!(!n||n.startsWith("`")&&r.includes("`")))return{char:n.charAt(0),length:n.length,indent:t[1]?.length??0,language:r.trim().split(/\s/u)[0]?.toLowerCase()??``}}function g(e,t,n){let r=t;for(;r<e.length&&!_(e[r]??``,n);)r++;return r}function _(e,t){let n=/^ {0,3}(`{3,}|~{3,})[ \t]*$/u.exec(e)?.[1];return n?.charAt(0)===t.char&&n.length>=t.length}function v(e,t){let n=0;for(;n<t&&e[n]===` `;)n++;return e.slice(n)}function y(e){return e.split(/\r?\n/u)}export{e as extractCodeBlocks,t as extractIfSingleOutermostCodeBlock,c as extractSections,o as extractTaggedCodeBlock,l as parseMarkdownSections};
6
7
  //# sourceMappingURL=markdown.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","block","findOutermostCodeBlock","text","languages","language","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","start","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not one of a nonempty `languages` list\n * (compared case-insensitively). An omitted or empty list allows any language.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n */\nexport function extractIfSingleOutermostCodeBlock(text: string, languages?: readonly string[]): string {\n const block = findOutermostCodeBlock(text);\n if (!block) return text;\n if (languages?.length && !languages.some((language) => language.toLowerCase() === block.language)) return text;\n return block.code;\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"markdown.js","names":["extractCodeBlocks","lines","splitLines","markdown","blocks","index","fence","parseOpeningFence","end","findClosingFence","stripIndent","line","extractIfSingleOutermostCodeBlock","isLanguageList","languagesOrOptions","isAllowed","languages","allowed","language","known","findBlockWithKnownFence","text","block","findOutermostCodeBlock","value","start","findSentOpeningFence","trimmed","isSentFence","opener","WRAPPED_MARKDOWN_LANGUAGES","extractTaggedCodeBlock","tag","tagName","found","findTaggedCodeBlock","wrapper","enclosingFence","fenceIndex","contentLine","extractSections","candidates","parseMarkdownSections","section","normalizeHeading","requested","names","name","sections","best","candidate","key","other","splitSections","stripHeadingEdges","heading","headings","findHeadings","nextIndex","lastIndex","isClosingFence","match","content","info","closer","indent"],"sources":["../src/markdown.ts"],"sourcesContent":["export interface CodeBlock {\n /** The first word of the info string, lowercased; `undefined` when the fence has none. */\n language?: string;\n code: string;\n /** `false` when the text ends before the closing fence, e.g. because the LLM output was cut off. */\n isClosed: boolean;\n}\n\ninterface Fence {\n char: string;\n length: number;\n indent: number;\n language: string;\n}\n\n/**\n * Extracts the fenced code blocks (backticks or tildes, any fence length) of Markdown text such as an LLM response.\n * A block runs to the first closing fence of the same character that is at least as long as its opening fence,\n * so a longer fence can hold shorter fences. Only fences indented by at most three spaces are recognized; fences\n * inside block quotes, deeply nested list items, and indented code blocks are not.\n */\nexport function extractCodeBlocks(markdown: string): CodeBlock[] {\n const lines = splitLines(markdown);\n const blocks: CodeBlock[] = [];\n for (let index = 0; index < lines.length; index++) {\n const fence = parseOpeningFence(lines[index] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, index + 1, fence);\n blocks.push({\n ...(fence.language && { language: fence.language }),\n code: lines\n .slice(index + 1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isClosed: end < lines.length,\n });\n index = end;\n }\n return blocks;\n}\n\nexport interface ExtractCodeBlockOptions {\n /** Allowed languages, compared case-insensitively; an omitted or empty list allows any language. */\n languages?: readonly string[];\n /** The exact fence the caller asked the LLM to wrap its response in, e.g. `getTildeFence(text)`. */\n fence?: string;\n}\n\n/**\n * Returns the contents of the code block that an LLM wrapped its whole response in, or `text` itself when the\n * response does not start with a fence or the fence's language is not allowed. A plain array is taken as `languages`.\n * When the response also ends with a matching closing fence, everything between its first and last lines is returned,\n * so fences the LLM nested without lengthening the outer fence stay in the contents. Otherwise the first block is\n * returned and text after it is dropped; an unclosed block runs to the end of the text.\n * With `fence`, a preamble and a postamble around the block are dropped first: everything between the first line\n * holding exactly `fence` and an allowed language or none, outside blocks of other languages, and the last later line\n * holding exactly `fence` is returned. Without such a pair, the rules above apply.\n */\nexport function extractIfSingleOutermostCodeBlock(\n text: string,\n languagesOrOptions?: readonly string[] | ExtractCodeBlockOptions\n): string {\n const { languages, fence }: ExtractCodeBlockOptions = isLanguageList(languagesOrOptions)\n ? { languages: languagesOrOptions }\n : (languagesOrOptions ?? {});\n const isAllowed = (language: string): boolean =>\n !languages?.length || languages.some((allowed) => allowed.toLowerCase() === language);\n const known = fence === undefined ? undefined : findBlockWithKnownFence(text, fence, isAllowed);\n if (known !== undefined) return known;\n const block = findOutermostCodeBlock(text);\n if (!block || !isAllowed(block.language)) return text;\n return block.code;\n}\n\nfunction isLanguageList(value: readonly string[] | ExtractCodeBlockOptions | undefined): value is readonly string[] {\n return Array.isArray(value);\n}\n\nfunction findBlockWithKnownFence(\n text: string,\n fence: string,\n isAllowed: (language: string) => boolean\n): string | undefined {\n const lines = splitLines(text);\n const end = lines.findLastIndex((line) => line.trimEnd() === fence);\n const start = findSentOpeningFence(lines, end, fence, isAllowed);\n if (start === undefined) return;\n return lines.slice(start + 1, end).join('\\n');\n}\n\n/** Returns the index of the first line before `end` opening `fence` with an allowed language or none, skipping other blocks. */\nfunction findSentOpeningFence(\n lines: readonly string[],\n end: number,\n fence: string,\n isAllowed: (language: string) => boolean\n): number | undefined {\n for (let index = 0; index < end; index++) {\n const line = lines[index] ?? '';\n const trimmed = line.trimEnd();\n const isSentFence = trimmed.startsWith(fence) && trimmed.charAt(fence.length) !== fence.charAt(0);\n const language = trimmed.slice(fence.length).trim().split(/\\s/u)[0]?.toLowerCase() ?? '';\n if (isSentFence && (language === '' || isAllowed(language))) return index;\n const opener = parseOpeningFence(line);\n if (opener) index = findClosingFence(lines, index + 1, opener);\n }\n}\n\nconst WRAPPED_MARKDOWN_LANGUAGES = new Set(['', 'markdown', 'md']);\n\n/**\n * Returns the trimmed contents of the first closed fenced code block (backticks or tildes) that follows a line holding\n * only `<tagName>` in text such as an LLM response, e.g. `<answer>\\n~~~\\n42\\n~~~`, or `undefined` when there is none.\n * Only blank lines may separate the tag line from the opening fence. Mentions of the tag within prose lines and tag\n * lines inside other fenced blocks (e.g. a format example) are skipped, but a whole-response `markdown`, `md`, or\n * unlabeled wrapper is read inside, as `parseMarkdownSections` does.\n */\nexport function extractTaggedCodeBlock(text: string, tagName: string): string | undefined {\n const tag = `<${tagName}>`;\n const found = findTaggedCodeBlock(splitLines(text), tag);\n if (found !== undefined) return found;\n // Only a real wrapper hides every tag line from the scan above; a leading example block paired with the tagged\n // block's closing fence merely looks like one, so its contents must not be read first.\n const wrapper = findOutermostCodeBlock(text);\n if (wrapper?.isWhole && WRAPPED_MARKDOWN_LANGUAGES.has(wrapper.language)) {\n return findTaggedCodeBlock(splitLines(wrapper.code), tag);\n }\n}\n\nfunction findTaggedCodeBlock(lines: readonly string[], tag: string): string | undefined {\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const enclosingFence = parseOpeningFence(line);\n if (enclosingFence) {\n index = findClosingFence(lines, index + 1, enclosingFence);\n continue;\n }\n if (line.trim() !== tag) continue;\n let fenceIndex = index + 1;\n while (fenceIndex < lines.length && !lines[fenceIndex]?.trim()) fenceIndex++;\n const fence = parseOpeningFence(lines[fenceIndex] ?? '');\n if (!fence) continue;\n const end = findClosingFence(lines, fenceIndex + 1, fence);\n if (end < lines.length) {\n return lines\n .slice(fenceIndex + 1, end)\n .map((contentLine) => stripIndent(contentLine, fence.indent))\n .join('\\n')\n .trim();\n }\n }\n}\n\nexport interface MarkdownSection {\n depth: number;\n /** The heading text with the closing `#` sequence and inline-code backticks removed. */\n heading: string;\n /** The source text up to the next heading at the same or a shallower depth, trimmed; subsections are included. */\n content: string;\n}\n\n/**\n * Extracts the content of the section headed by each of `names` from Markdown text such as an LLM response, keyed by\n * the given names. A heading matches a name when both are equal after removing decorations at their edges (such as\n * `**bold**`, backticks, and quotes), a leading number like `1.` or `2.1`, and a trailing colon, ignoring case and spacing;\n * headings at any depth are candidates.\n * Among matching headings, the shallowest wins, then one equal to the name as written, then the first; each heading\n * serves at most one name. When distinct requested names normalize alike, only exact matches at the shallowest\n * matching depth are returned. Missing, empty, or ambiguous sections are absent from the prototype-free result.\n */\nexport function extractSections<const Name extends string>(\n markdown: string,\n names: readonly Name[]\n): Partial<Record<Name, string>> {\n const candidates = parseMarkdownSections(markdown).map((section) => ({\n ...section,\n key: normalizeHeading(section.heading),\n }));\n const requested = [...new Set(names)].map((name) => ({ name, key: normalizeHeading(name) }));\n const sections: Partial<Record<Name, string>> = Object.create(null);\n for (const { name, key } of requested) {\n let best: (typeof candidates)[number] | undefined;\n for (const candidate of candidates) {\n if (candidate.key !== key) continue;\n if (\n !best ||\n candidate.depth < best.depth ||\n (candidate.depth === best.depth && candidate.heading === name && best.heading !== name)\n ) {\n best = candidate;\n }\n }\n if (!best) continue;\n if (best.heading !== name && requested.some((other) => other.name !== name && other.key === key)) continue;\n if (best.content) sections[name] = best.content;\n }\n return sections;\n}\n\n/**\n * Returns every ATX heading of Markdown text in order, with the source text under it.\n * Only headings starting at the beginning of a line outside fenced code blocks count; setext headings (underlined\n * with `=` or `-`) are ignored. A whole-response `markdown`, `md`, or unlabeled wrapper is read inside as\n * `extractIfSingleOutermostCodeBlock` finds it, preserving nested fences. A leading wrapper followed by prose is\n * also read inside, but only when there are no headings outside fences.\n */\nexport function parseMarkdownSections(markdown: string): MarkdownSection[] {\n const block = findOutermostCodeBlock(markdown);\n const wrapper = block && WRAPPED_MARKDOWN_LANGUAGES.has(block.language) ? block : undefined;\n if (wrapper?.isWhole) return splitSections(wrapper.code);\n const sections = splitSections(markdown);\n return sections.length === 0 && wrapper ? splitSections(wrapper.code) : sections;\n}\n\nfunction normalizeHeading(heading: string): string {\n return stripHeadingEdges(stripHeadingEdges(heading).replace(/^\\d+(?:\\.\\d+)*(?:\\.\\s+|\\)\\s*|\\s+)/u, ''))\n .replaceAll(/\\s+/gu, ' ')\n .toLowerCase();\n}\n\nfunction stripHeadingEdges(text: string): string {\n // Only the edges: `_` and `*` inside a heading belong to names such as `my_file.py`.\n return text.replaceAll(/^[\\s*_`~\"'「」]+|[\\s*_`~\"'「」::]+$/gu, '');\n}\n\nfunction splitSections(markdown: string): MarkdownSection[] {\n const lines = splitLines(markdown);\n const headings = findHeadings(lines);\n return headings.map((heading, index) => {\n let nextIndex = index + 1;\n while (nextIndex < headings.length && (headings[nextIndex]?.depth ?? 0) > heading.depth) nextIndex++;\n return {\n depth: heading.depth,\n heading: heading.text,\n content: lines\n .slice(heading.index + 1, headings[nextIndex]?.index)\n .join('\\n')\n .trim(),\n };\n });\n}\n\nfunction findOutermostCodeBlock(text: string): { language: string; code: string; isWhole: boolean } | undefined {\n // Keep the opening fence's indentation, which is stripped from the contents.\n const lines = splitLines(text.replace(/^\\s*\\n/u, '').trimEnd());\n const fence = parseOpeningFence(lines[0] ?? '');\n if (!fence) return;\n const lastIndex = lines.length - 1;\n const end =\n lastIndex > 0 && isClosingFence(lines[lastIndex] ?? '', fence) ? lastIndex : findClosingFence(lines, 1, fence);\n return {\n language: fence.language,\n code: lines\n .slice(1, end)\n .map((line) => stripIndent(line, fence.indent))\n .join('\\n'),\n isWhole: end >= lastIndex,\n };\n}\n\nfunction findHeadings(lines: readonly string[]): { index: number; depth: number; text: string }[] {\n const headings: { index: number; depth: number; text: string }[] = [];\n for (let index = 0; index < lines.length; index++) {\n const line = lines[index] ?? '';\n const fence = parseOpeningFence(line);\n if (fence) {\n index = findClosingFence(lines, index + 1, fence);\n continue;\n }\n const match = /^(#{1,6})(?:[ \\t]+(.*))?$/u.exec(line);\n if (!match) continue;\n const text = (match[2] ?? '')\n // The closing sequence of an ATX heading needs a space before it, so `# C#` keeps its `#`.\n .replace(/(?:^|[ \\t]+)#+[ \\t]*$/u, '')\n .trim()\n .replaceAll(/(`+)(.+?)\\1/gu, (_match, _delimiter: string, content: string) =>\n content.startsWith(' ') && content.endsWith(' ') ? content.slice(1, -1) : content\n );\n headings.push({ index, depth: match[1]?.length ?? 1, text });\n }\n return headings;\n}\n\nfunction parseOpeningFence(line: string): Fence | undefined {\n const match = /^( {0,3})(`{3,}|~{3,})(.*)$/u.exec(line);\n const fence = match?.[2];\n const info = match?.[3] ?? '';\n // A backtick in the info string makes the line inline code instead of a fence.\n if (!fence || (fence.startsWith('`') && info.includes('`'))) return;\n return {\n char: fence.charAt(0),\n length: fence.length,\n indent: match[1]?.length ?? 0,\n language: info.trim().split(/\\s/u)[0]?.toLowerCase() ?? '',\n };\n}\n\n/** Returns the index of the line closing `fence`, or the number of lines when it is never closed. */\nfunction findClosingFence(lines: readonly string[], start: number, fence: Fence): number {\n let index = start;\n while (index < lines.length && !isClosingFence(lines[index] ?? '', fence)) index++;\n return index;\n}\n\nfunction isClosingFence(line: string, fence: Fence): boolean {\n const closer = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/u.exec(line)?.[1];\n return closer?.charAt(0) === fence.char && closer.length >= fence.length;\n}\n\nfunction stripIndent(line: string, indent: number): string {\n let index = 0;\n while (index < indent && line[index] === ' ') index++;\n return line.slice(index);\n}\n\nfunction splitLines(text: string): string[] {\n return text.split(/\\r?\\n/u);\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"number.cjs","names":["parsePositiveInteger","text","value","clamp","min","max"],"sources":["../src/number.ts"],"sourcesContent":["/**\n * Parses a decimal integer of at least 1 written without sign, whitespace, or leading zeros, e.g. a CLI option or an\n * environment variable. Returns undefined for any other input, including values beyond `Number.MAX_SAFE_INTEGER`.\n */\nexport function parsePositiveInteger(text: string): number | undefined {\n if (!/^[1-9]\\d*$/u.test(text)) return undefined;\n const value = Number(text);\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\n/** Limits a number to the range from `min` to `max`, both inclusive. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n"],"mappings":"aAIA,SAAgBA,EAAqB,EAAkC,CACrE,GAAI,CAAC,cAAc,KAAKC,CAAI,EAAG,OAC/B,IAAMC,EAAQ,OAAOD,CAAI,EACzB,OAAO,OAAO,cAAcC,CAAK,EAAIA,EAAQ,IAAA,EAC/C,CAGA,SAAgBC,EAAM,EAAe,EAAa,EAAqB,CACrE,OAAO,KAAK,IAAI,KAAK,IAAID,EAAOE,CAAG,EAAGC,CAAG,CAC3C"}
1
+ {"version":3,"file":"number.cjs","names":["parsePositiveInteger","text","value","clamp","min","max"],"sources":["../src/number.ts"],"sourcesContent":["/**\n * Parses a decimal integer of at least 1 written without sign, whitespace, or leading zeros, e.g. a CLI option or an\n * environment variable. Returns undefined for any other input, including values beyond `Number.MAX_SAFE_INTEGER`.\n */\nexport function parsePositiveInteger(text: string): number | undefined {\n if (!/^[1-9]\\d*$/u.test(text)) return undefined;\n const value = Number(text);\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\n/** Limits a number to the range from `min` to `max`, both inclusive. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n"],"mappings":"A,aAIA,SAAgBA,EAAqB,EAAkC,CACrE,GAAI,CAAC,cAAc,KAAKC,CAAI,EAAG,OAC/B,IAAMC,EAAQ,OAAOD,CAAI,EACzB,OAAO,OAAO,cAAcC,CAAK,EAAIA,EAAQ,IAAA,EAC/C,CAGA,SAAgBC,EAAM,EAAe,EAAa,EAAqB,CACrE,OAAO,KAAK,IAAI,KAAK,IAAID,EAAOE,CAAG,EAAGC,CAAG,CAC3C,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"number.js","names":["parsePositiveInteger","text","value","clamp","min","max"],"sources":["../src/number.ts"],"sourcesContent":["/**\n * Parses a decimal integer of at least 1 written without sign, whitespace, or leading zeros, e.g. a CLI option or an\n * environment variable. Returns undefined for any other input, including values beyond `Number.MAX_SAFE_INTEGER`.\n */\nexport function parsePositiveInteger(text: string): number | undefined {\n if (!/^[1-9]\\d*$/u.test(text)) return undefined;\n const value = Number(text);\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\n/** Limits a number to the range from `min` to `max`, both inclusive. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n"],"mappings":"AAIA,SAAgBA,EAAqB,EAAkC,CACrE,GAAI,CAAC,cAAc,KAAKC,CAAI,EAAG,OAC/B,IAAMC,EAAQ,OAAOD,CAAI,EACzB,OAAO,OAAO,cAAcC,CAAK,EAAIA,EAAQ,IAAA,EAC/C,CAGA,SAAgBC,EAAM,EAAe,EAAa,EAAqB,CACrE,OAAO,KAAK,IAAI,KAAK,IAAID,EAAOE,CAAG,EAAGC,CAAG,CAC3C"}
1
+ {"version":3,"file":"number.js","names":["parsePositiveInteger","text","value","clamp","min","max"],"sources":["../src/number.ts"],"sourcesContent":["/**\n * Parses a decimal integer of at least 1 written without sign, whitespace, or leading zeros, e.g. a CLI option or an\n * environment variable. Returns undefined for any other input, including values beyond `Number.MAX_SAFE_INTEGER`.\n */\nexport function parsePositiveInteger(text: string): number | undefined {\n if (!/^[1-9]\\d*$/u.test(text)) return undefined;\n const value = Number(text);\n return Number.isSafeInteger(value) ? value : undefined;\n}\n\n/** Limits a number to the range from `min` to `max`, both inclusive. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n"],"mappings":"AAIA,SAAgBA,EAAqB,EAAkC,CACrE,GAAI,CAAC,cAAc,KAAKC,CAAI,EAAG,OAC/B,IAAMC,EAAQ,OAAOD,CAAI,EACzB,OAAO,OAAO,cAAcC,CAAK,EAAIA,EAAQ,IAAA,EAC/C,CAGA,SAAgBC,EAAM,EAAe,EAAa,EAAqB,CACrE,OAAO,KAAK,IAAI,KAAK,IAAID,EAAOE,CAAG,EAAGC,CAAG,CAC3C,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"object.cjs","names":["isRecord","value","omitUndefined","result","key","propertyValue"],"sources":["../src/object.ts"],"sourcesContent":["/** Whether a value is a non-null object other than an array, e.g. a parsed JSON object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Returns a shallow copy of the own enumerable string-keyed properties whose values are not `undefined`,\n * e.g. to turn `process.env` into a `Record<string, string>`.\n * Expects a plain record: the copy is a plain object without the input's prototype, so the type rejects arrays and\n * class instances such as `Date`, though not a class that declares a string index signature.\n */\nexport function omitUndefined<T extends Readonly<Record<string, unknown>>>(value: T): WithoutUndefined<T> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const propertyValue = (value as Record<string, unknown>)[key];\n if (propertyValue === undefined) continue;\n if (key === '__proto__') {\n // Assignment would invoke the `__proto__` setter and replace the prototype instead of copying the key.\n Object.defineProperty(result, key, {\n configurable: true,\n enumerable: true,\n value: propertyValue,\n writable: true,\n });\n } else {\n result[key] = propertyValue;\n }\n }\n return result as WithoutUndefined<T>;\n}\n\n// A known key whose value may be `undefined` becomes optional because it may be omitted; an index signature, whose keys\n// are never guaranteed to exist, keeps its shape so that `process.env` yields `Record<string, string>`. Symbol keys are\n// not copied.\ntype WithoutUndefined<T> = {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? K : undefined extends T[K] ? never : K\n ]: Exclude<T[K], undefined>;\n} & {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? never : undefined extends T[K] ? K : never\n ]?: Exclude<T[K], undefined>;\n};\n\ntype IsIndexKey<K> = string extends K ? true : number extends K ? true : false;\n"],"mappings":"aACA,SAAgBA,EAAS,EAAkD,CACzE,OAAO,OAAOC,GAAU,YAAYA,GAAkB,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAQA,SAAgBC,EAA2D,EAA+B,CACxG,IAAMC,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAO,OAAO,KAAKH,CAAK,EAAG,CACpC,IAAMI,EAAiBJ,EAAkCG,GACrDC,IAAkB,IAAA,KAClBD,IAAQ,YAEV,OAAO,eAAeD,EAAQC,EAAK,CACjC,aAAc,GACd,WAAY,GACZ,MAAOC,EACP,SAAU,EACZ,CAAC,EAED,EAAOD,GAAOC,EAElB,CACA,OAAOF,CACT"}
1
+ {"version":3,"file":"object.cjs","names":["isRecord","value","omitUndefined","result","key","propertyValue"],"sources":["../src/object.ts"],"sourcesContent":["/** Whether a value is a non-null object other than an array, e.g. a parsed JSON object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Returns a shallow copy of the own enumerable string-keyed properties whose values are not `undefined`,\n * e.g. to turn `process.env` into a `Record<string, string>`.\n * Expects a plain record: the copy is a plain object without the input's prototype, so the type rejects arrays and\n * class instances such as `Date`, though not a class that declares a string index signature.\n */\nexport function omitUndefined<T extends Readonly<Record<string, unknown>>>(value: T): WithoutUndefined<T> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const propertyValue = (value as Record<string, unknown>)[key];\n if (propertyValue === undefined) continue;\n if (key === '__proto__') {\n // Assignment would invoke the `__proto__` setter and replace the prototype instead of copying the key.\n Object.defineProperty(result, key, {\n configurable: true,\n enumerable: true,\n value: propertyValue,\n writable: true,\n });\n } else {\n result[key] = propertyValue;\n }\n }\n return result as WithoutUndefined<T>;\n}\n\n// A known key whose value may be `undefined` becomes optional because it may be omitted; an index signature, whose keys\n// are never guaranteed to exist, keeps its shape so that `process.env` yields `Record<string, string>`. Symbol keys are\n// not copied.\ntype WithoutUndefined<T> = {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? K : undefined extends T[K] ? never : K\n ]: Exclude<T[K], undefined>;\n} & {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? never : undefined extends T[K] ? K : never\n ]?: Exclude<T[K], undefined>;\n};\n\ntype IsIndexKey<K> = string extends K ? true : number extends K ? true : false;\n"],"mappings":"A,aACA,SAAgBA,EAAS,EAAkD,CACzE,OAAO,OAAOC,GAAU,YAAYA,GAAkB,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAQA,SAAgBC,EAA2D,EAA+B,CACxG,IAAMC,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAO,OAAO,KAAKH,CAAK,EAAG,CACpC,IAAMI,EAAiBJ,EAAkCG,GACrDC,IAAkB,IAAA,KAClBD,IAAQ,YAEV,OAAO,eAAeD,EAAQC,EAAK,CACjC,aAAc,GACd,WAAY,GACZ,MAAOC,EACP,SAAU,EACZ,CAAC,EAED,EAAOD,GAAOC,EAElB,CACA,OAAOF,CACT,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"object.js","names":["isRecord","value","omitUndefined","result","key","propertyValue"],"sources":["../src/object.ts"],"sourcesContent":["/** Whether a value is a non-null object other than an array, e.g. a parsed JSON object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Returns a shallow copy of the own enumerable string-keyed properties whose values are not `undefined`,\n * e.g. to turn `process.env` into a `Record<string, string>`.\n * Expects a plain record: the copy is a plain object without the input's prototype, so the type rejects arrays and\n * class instances such as `Date`, though not a class that declares a string index signature.\n */\nexport function omitUndefined<T extends Readonly<Record<string, unknown>>>(value: T): WithoutUndefined<T> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const propertyValue = (value as Record<string, unknown>)[key];\n if (propertyValue === undefined) continue;\n if (key === '__proto__') {\n // Assignment would invoke the `__proto__` setter and replace the prototype instead of copying the key.\n Object.defineProperty(result, key, {\n configurable: true,\n enumerable: true,\n value: propertyValue,\n writable: true,\n });\n } else {\n result[key] = propertyValue;\n }\n }\n return result as WithoutUndefined<T>;\n}\n\n// A known key whose value may be `undefined` becomes optional because it may be omitted; an index signature, whose keys\n// are never guaranteed to exist, keeps its shape so that `process.env` yields `Record<string, string>`. Symbol keys are\n// not copied.\ntype WithoutUndefined<T> = {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? K : undefined extends T[K] ? never : K\n ]: Exclude<T[K], undefined>;\n} & {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? never : undefined extends T[K] ? K : never\n ]?: Exclude<T[K], undefined>;\n};\n\ntype IsIndexKey<K> = string extends K ? true : number extends K ? true : false;\n"],"mappings":"AACA,SAAgBA,EAAS,EAAkD,CACzE,OAAO,OAAOC,GAAU,YAAYA,GAAkB,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAQA,SAAgBC,EAA2D,EAA+B,CACxG,IAAMC,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAO,OAAO,KAAKH,CAAK,EAAG,CACpC,IAAMI,EAAiBJ,EAAkCG,GACrDC,IAAkB,IAAA,KAClBD,IAAQ,YAEV,OAAO,eAAeD,EAAQC,EAAK,CACjC,aAAc,GACd,WAAY,GACZ,MAAOC,EACP,SAAU,EACZ,CAAC,EAED,EAAOD,GAAOC,EAElB,CACA,OAAOF,CACT"}
1
+ {"version":3,"file":"object.js","names":["isRecord","value","omitUndefined","result","key","propertyValue"],"sources":["../src/object.ts"],"sourcesContent":["/** Whether a value is a non-null object other than an array, e.g. a parsed JSON object. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Returns a shallow copy of the own enumerable string-keyed properties whose values are not `undefined`,\n * e.g. to turn `process.env` into a `Record<string, string>`.\n * Expects a plain record: the copy is a plain object without the input's prototype, so the type rejects arrays and\n * class instances such as `Date`, though not a class that declares a string index signature.\n */\nexport function omitUndefined<T extends Readonly<Record<string, unknown>>>(value: T): WithoutUndefined<T> {\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n const propertyValue = (value as Record<string, unknown>)[key];\n if (propertyValue === undefined) continue;\n if (key === '__proto__') {\n // Assignment would invoke the `__proto__` setter and replace the prototype instead of copying the key.\n Object.defineProperty(result, key, {\n configurable: true,\n enumerable: true,\n value: propertyValue,\n writable: true,\n });\n } else {\n result[key] = propertyValue;\n }\n }\n return result as WithoutUndefined<T>;\n}\n\n// A known key whose value may be `undefined` becomes optional because it may be omitted; an index signature, whose keys\n// are never guaranteed to exist, keeps its shape so that `process.env` yields `Record<string, string>`. Symbol keys are\n// not copied.\ntype WithoutUndefined<T> = {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? K : undefined extends T[K] ? never : K\n ]: Exclude<T[K], undefined>;\n} & {\n [\n K in keyof T as K extends symbol ? never : IsIndexKey<K> extends true ? never : undefined extends T[K] ? K : never\n ]?: Exclude<T[K], undefined>;\n};\n\ntype IsIndexKey<K> = string extends K ? true : number extends K ? true : false;\n"],"mappings":"AACA,SAAgBA,EAAS,EAAkD,CACzE,OAAO,OAAOC,GAAU,YAAYA,GAAkB,CAAC,MAAM,QAAQA,CAAK,CAC5E,CAQA,SAAgBC,EAA2D,EAA+B,CACxG,IAAMC,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAO,OAAO,KAAKH,CAAK,EAAG,CACpC,IAAMI,EAAiBJ,EAAkCG,GACrDC,IAAkB,IAAA,KAClBD,IAAQ,YAEV,OAAO,eAAeD,EAAQC,EAAK,CACjC,aAAc,GACd,WAAY,GACZ,MAAOC,EACP,SAAU,EACZ,CAAC,EAED,EAAOD,GAAOC,EAElB,CACA,OAAOF,CACT,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseCommandLineArgs.cjs","names":["parseCommandLineArgs","argsString","result","current","inDoubleQuote","inSingleQuote","char"],"sources":["../src/parseCommandLineArgs.ts"],"sourcesContent":["/**\n * Parses a command line string into an array of arguments, preserving quoted strings.\n *\n * This function handles:\n * - Space-separated arguments\n * - Double-quoted strings (preserves spaces within)\n * - Single-quoted strings (preserves spaces within)\n *\n * @param argsString The command line string to parse\n * @returns An array of parsed arguments\n */\nexport function parseCommandLineArgs(argsString: string): string[] {\n if (!argsString) return [];\n\n const result: string[] = [];\n let current = '';\n let inDoubleQuote = false;\n let inSingleQuote = false;\n\n for (const char of argsString) {\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n continue;\n }\n\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n continue;\n }\n\n if (char === ' ' && !inDoubleQuote && !inSingleQuote) {\n if (current) {\n result.push(current);\n current = '';\n }\n continue;\n }\n\n current += char;\n }\n\n if (current) {\n result.push(current);\n }\n\n return result;\n}\n"],"mappings":"aAWA,SAAgBA,EAAqB,EAA8B,CACjE,GAAI,CAACC,EAAY,MAAO,CAAC,EAEzB,IAAMC,EAAmB,CAAC,EACtBC,EAAU,GACVC,EAAgB,GAChBC,EAAgB,GAEpB,IAAK,IAAMC,KAAQL,EACbK,IAAS,KAAO,CAACD,EACnB,EAAgB,CAACD,EAIfE,IAAS,KAAO,CAACF,EACnB,EAAgB,CAACC,EAIfC,IAAS,KAAO,CAACF,GAAiB,CAACC,EACrC,AAEEF,KADAD,EAAO,KAAKC,CAAO,EACT,IAKd,GAAWG,EAOb,OAJIH,GACFD,EAAO,KAAKC,CAAO,EAGdD,CACT"}
1
+ {"version":3,"file":"parseCommandLineArgs.cjs","names":["parseCommandLineArgs","argsString","result","current","inDoubleQuote","inSingleQuote","char"],"sources":["../src/parseCommandLineArgs.ts"],"sourcesContent":["/**\n * Parses a command line string into an array of arguments, preserving quoted strings.\n *\n * This function handles:\n * - Space-separated arguments\n * - Double-quoted strings (preserves spaces within)\n * - Single-quoted strings (preserves spaces within)\n *\n * @param argsString The command line string to parse\n * @returns An array of parsed arguments\n */\nexport function parseCommandLineArgs(argsString: string): string[] {\n if (!argsString) return [];\n\n const result: string[] = [];\n let current = '';\n let inDoubleQuote = false;\n let inSingleQuote = false;\n\n for (const char of argsString) {\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n continue;\n }\n\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n continue;\n }\n\n if (char === ' ' && !inDoubleQuote && !inSingleQuote) {\n if (current) {\n result.push(current);\n current = '';\n }\n continue;\n }\n\n current += char;\n }\n\n if (current) {\n result.push(current);\n }\n\n return result;\n}\n"],"mappings":"A,aAWA,SAAgBA,EAAqB,EAA8B,CACjE,GAAI,CAACC,EAAY,MAAO,CAAC,EAEzB,IAAMC,EAAmB,CAAC,EACtBC,EAAU,GACVC,EAAgB,GAChBC,EAAgB,GAEpB,IAAK,IAAMC,KAAQL,EACbK,IAAS,KAAO,CAACD,EACnB,EAAgB,CAACD,EAIfE,IAAS,KAAO,CAACF,EACnB,EAAgB,CAACC,EAIfC,IAAS,KAAO,CAACF,GAAiB,CAACC,EACrC,AAEEF,KADAD,EAAO,KAAKC,CAAO,EACT,IAKd,GAAWG,EAOb,OAJIH,GACFD,EAAO,KAAKC,CAAO,EAGdD,CACT,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseCommandLineArgs.js","names":["parseCommandLineArgs","argsString","result","current","inDoubleQuote","inSingleQuote","char"],"sources":["../src/parseCommandLineArgs.ts"],"sourcesContent":["/**\n * Parses a command line string into an array of arguments, preserving quoted strings.\n *\n * This function handles:\n * - Space-separated arguments\n * - Double-quoted strings (preserves spaces within)\n * - Single-quoted strings (preserves spaces within)\n *\n * @param argsString The command line string to parse\n * @returns An array of parsed arguments\n */\nexport function parseCommandLineArgs(argsString: string): string[] {\n if (!argsString) return [];\n\n const result: string[] = [];\n let current = '';\n let inDoubleQuote = false;\n let inSingleQuote = false;\n\n for (const char of argsString) {\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n continue;\n }\n\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n continue;\n }\n\n if (char === ' ' && !inDoubleQuote && !inSingleQuote) {\n if (current) {\n result.push(current);\n current = '';\n }\n continue;\n }\n\n current += char;\n }\n\n if (current) {\n result.push(current);\n }\n\n return result;\n}\n"],"mappings":"AAWA,SAAgBA,EAAqB,EAA8B,CACjE,GAAI,CAACC,EAAY,MAAO,CAAC,EAEzB,IAAMC,EAAmB,CAAC,EACtBC,EAAU,GACVC,EAAgB,GAChBC,EAAgB,GAEpB,IAAK,IAAMC,KAAQL,EACbK,IAAS,KAAO,CAACD,EACnB,EAAgB,CAACD,EAIfE,IAAS,KAAO,CAACF,EACnB,EAAgB,CAACC,EAIfC,IAAS,KAAO,CAACF,GAAiB,CAACC,EACrC,AAEEF,KADAD,EAAO,KAAKC,CAAO,EACT,IAKd,GAAWG,EAOb,OAJIH,GACFD,EAAO,KAAKC,CAAO,EAGdD,CACT"}
1
+ {"version":3,"file":"parseCommandLineArgs.js","names":["parseCommandLineArgs","argsString","result","current","inDoubleQuote","inSingleQuote","char"],"sources":["../src/parseCommandLineArgs.ts"],"sourcesContent":["/**\n * Parses a command line string into an array of arguments, preserving quoted strings.\n *\n * This function handles:\n * - Space-separated arguments\n * - Double-quoted strings (preserves spaces within)\n * - Single-quoted strings (preserves spaces within)\n *\n * @param argsString The command line string to parse\n * @returns An array of parsed arguments\n */\nexport function parseCommandLineArgs(argsString: string): string[] {\n if (!argsString) return [];\n\n const result: string[] = [];\n let current = '';\n let inDoubleQuote = false;\n let inSingleQuote = false;\n\n for (const char of argsString) {\n if (char === '\"' && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n continue;\n }\n\n if (char === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n continue;\n }\n\n if (char === ' ' && !inDoubleQuote && !inSingleQuote) {\n if (current) {\n result.push(current);\n current = '';\n }\n continue;\n }\n\n current += char;\n }\n\n if (current) {\n result.push(current);\n }\n\n return result;\n}\n"],"mappings":"AAWA,SAAgBA,EAAqB,EAA8B,CACjE,GAAI,CAACC,EAAY,MAAO,CAAC,EAEzB,IAAMC,EAAmB,CAAC,EACtBC,EAAU,GACVC,EAAgB,GAChBC,EAAgB,GAEpB,IAAK,IAAMC,KAAQL,EACbK,IAAS,KAAO,CAACD,EACnB,EAAgB,CAACD,EAIfE,IAAS,KAAO,CAACF,EACnB,EAAgB,CAACC,EAIfC,IAAS,KAAO,CAACF,GAAiB,CAACC,EACrC,AAEEF,KADAD,EAAO,KAAKC,CAAO,EACT,IAKd,GAAWG,EAOb,OAJIH,GACFD,EAAO,KAAKC,CAAO,EAGdD,CACT,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseIsoDate.cjs","names":["parseIsoDate","input","value","match","yearText","monthText","dayText","hourText","minuteText","secondText","fraction","zone","year","month","day","local","hour","minute","second","offset","parseOffset","digits","hours","minutes"],"sources":["../src/parseIsoDate.ts"],"sourcesContent":["export type ParsedIsoDate = { kind: 'date'; value: string } | { kind: 'datetime'; value: string };\n\n/**\n * Parses an ISO calendar date or a timestamp with an explicit UTC offset, without guessing\n * a timezone or rolling invalid calendar dates forward. Returns undefined for unsupported input.\n *\n * Accepts YYYY-MM-DD and YYYY-MM-DDTHH:mm[:ss[.fraction]] followed by Z or ±HH:mm\n * (±HHmm also works). Surrounding whitespace is ignored. Date-only values stay date-only;\n * timestamps become UTC strings, retaining all fractional digits. Local timestamps, unknown\n * offsets (-00:00/-0000), leap seconds, and input or normalized UTC years outside 0000–9999\n * are unsupported.\n */\nexport function parseIsoDate(input: unknown): ParsedIsoDate | undefined {\n if (typeof input !== 'string') return undefined;\n const value = input.trim();\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2}))?$/u.exec(value);\n if (!match) return undefined;\n const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zone] = match;\n const year = Number(yearText);\n const month = Number(monthText);\n const day = Number(dayText);\n const local = new Date(0);\n local.setUTCFullYear(year, month - 1, day);\n if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day) {\n return undefined;\n }\n if (hourText === undefined) return { kind: 'date', value };\n const hour = Number(hourText);\n const minute = Number(minuteText);\n const second = Number(secondText ?? 0);\n if (hour > 23 || minute > 59 || second > 59) return undefined;\n const offset = parseOffset(zone!);\n if (offset === undefined) return undefined;\n local.setUTCHours(hour, minute - offset, second);\n if (local.getUTCFullYear() < 0 || local.getUTCFullYear() > 9999) return undefined;\n return { kind: 'datetime', value: `${local.toISOString().slice(0, 19)}${fraction ?? ''}Z` };\n}\n\nfunction parseOffset(zone: string): number | undefined {\n if (zone === 'Z') return 0;\n const digits = zone.slice(1).replace(':', '');\n const hours = Number(digits.slice(0, 2));\n const minutes = Number(digits.slice(2));\n if (hours > 23 || minutes > 59 || (zone.startsWith('-') && hours === 0 && minutes === 0)) return undefined;\n return (zone.startsWith('-') ? -1 : 1) * (hours * 60 + minutes);\n}\n"],"mappings":"aAYA,SAAgBA,EAAa,EAA2C,CACtE,GAAI,OAAOC,GAAU,SAAU,OAC/B,IAAMC,EAAQD,EAAM,KAAK,EACnBE,EAAQ,2FAA2F,KAAKD,CAAK,EACnH,GAAI,CAACC,EAAO,OACZ,GAAM,EAAGC,EAAUC,EAAWC,EAASC,EAAUC,EAAYC,EAAYC,EAAUC,GAAQR,EACrFS,EAAO,OAAOR,CAAQ,EACtBS,EAAQ,OAAOR,CAAS,EACxBS,EAAM,OAAOR,CAAO,EACpBS,kBAAQ,IAAI,KAAK,CAAC,EAExB,GADAA,EAAM,eAAeH,EAAMC,EAAQ,EAAGC,CAAG,EACrCC,EAAM,eAAe,IAAMH,GAAQG,EAAM,YAAY,IAAMF,EAAQ,GAAKE,EAAM,WAAW,IAAMD,EACjG,OAEF,GAAIP,IAAa,IAAA,GAAW,MAAO,CAAE,KAAM,OAAQ,OAAM,EACzD,IAAMS,EAAO,OAAOT,CAAQ,EACtBU,EAAS,OAAOT,CAAU,EAC1BU,EAAS,OAAOT,GAAc,CAAC,EACrC,GAAIO,EAAO,IAAMC,EAAS,IAAMC,EAAS,GAAI,OAC7C,IAAMC,EAASC,EAAYT,CAAK,EAChC,GAAIQ,IAAW,IAAA,KACfJ,EAAM,YAAYC,EAAMC,EAASE,EAAQD,CAAM,EAC3C,IAAM,eAAe,EAAI,GAAKH,EAAM,eAAe,EAAI,OAC3D,MAAO,CAAE,KAAM,WAAY,MAAO,GAAGA,EAAM,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,IAAIL,GAAY,GAAG,EAAG,CAC5F,CAEA,SAASU,EAAY,EAAkC,CACrD,GAAIT,IAAS,IAAK,MAAO,GACzB,IAAMU,EAASV,EAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAK,EAAE,EACtCW,EAAQ,OAAOD,EAAO,MAAM,EAAG,CAAC,CAAC,EACjCE,EAAU,OAAOF,EAAO,MAAM,CAAC,CAAC,EACtC,GAAI,IAAQ,IAAME,EAAU,IAAOZ,EAAK,WAAW,GAAG,GAAKW,IAAU,GAAKC,IAAY,GACtF,OAAQZ,EAAK,WAAW,GAAG,EAAI,GAAK,IAAMW,EAAQ,GAAKC,EACzD"}
1
+ {"version":3,"file":"parseIsoDate.cjs","names":["parseIsoDate","input","value","match","yearText","monthText","dayText","hourText","minuteText","secondText","fraction","zone","year","month","day","local","hour","minute","second","offset","parseOffset","digits","hours","minutes"],"sources":["../src/parseIsoDate.ts"],"sourcesContent":["export type ParsedIsoDate = { kind: 'date'; value: string } | { kind: 'datetime'; value: string };\n\n/**\n * Parses an ISO calendar date or a timestamp with an explicit UTC offset, without guessing\n * a timezone or rolling invalid calendar dates forward. Returns undefined for unsupported input.\n *\n * Accepts YYYY-MM-DD and YYYY-MM-DDTHH:mm[:ss[.fraction]] followed by Z or ±HH:mm\n * (±HHmm also works). Surrounding whitespace is ignored. Date-only values stay date-only;\n * timestamps become UTC strings, retaining all fractional digits. Local timestamps, unknown\n * offsets (-00:00/-0000), leap seconds, and input or normalized UTC years outside 0000–9999\n * are unsupported.\n */\nexport function parseIsoDate(input: unknown): ParsedIsoDate | undefined {\n if (typeof input !== 'string') return undefined;\n const value = input.trim();\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2}))?$/u.exec(value);\n if (!match) return undefined;\n const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zone] = match;\n const year = Number(yearText);\n const month = Number(monthText);\n const day = Number(dayText);\n const local = new Date(0);\n local.setUTCFullYear(year, month - 1, day);\n if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day) {\n return undefined;\n }\n if (hourText === undefined) return { kind: 'date', value };\n const hour = Number(hourText);\n const minute = Number(minuteText);\n const second = Number(secondText ?? 0);\n if (hour > 23 || minute > 59 || second > 59) return undefined;\n const offset = parseOffset(zone!);\n if (offset === undefined) return undefined;\n local.setUTCHours(hour, minute - offset, second);\n if (local.getUTCFullYear() < 0 || local.getUTCFullYear() > 9999) return undefined;\n return { kind: 'datetime', value: `${local.toISOString().slice(0, 19)}${fraction ?? ''}Z` };\n}\n\nfunction parseOffset(zone: string): number | undefined {\n if (zone === 'Z') return 0;\n const digits = zone.slice(1).replace(':', '');\n const hours = Number(digits.slice(0, 2));\n const minutes = Number(digits.slice(2));\n if (hours > 23 || minutes > 59 || (zone.startsWith('-') && hours === 0 && minutes === 0)) return undefined;\n return (zone.startsWith('-') ? -1 : 1) * (hours * 60 + minutes);\n}\n"],"mappings":"A,aAYA,SAAgBA,EAAa,EAA2C,CACtE,GAAI,OAAOC,GAAU,SAAU,OAC/B,IAAMC,EAAQD,EAAM,KAAK,EACnBE,EAAQ,2FAA2F,KAAKD,CAAK,EACnH,GAAI,CAACC,EAAO,OACZ,GAAM,EAAGC,EAAUC,EAAWC,EAASC,EAAUC,EAAYC,EAAYC,EAAUC,GAAQR,EACrFS,EAAO,OAAOR,CAAQ,EACtBS,EAAQ,OAAOR,CAAS,EACxBS,EAAM,OAAOR,CAAO,EACpBS,kBAAQ,IAAI,KAAK,CAAC,EAExB,GADAA,EAAM,eAAeH,EAAMC,EAAQ,EAAGC,CAAG,EACrCC,EAAM,eAAe,IAAMH,GAAQG,EAAM,YAAY,IAAMF,EAAQ,GAAKE,EAAM,WAAW,IAAMD,EACjG,OAEF,GAAIP,IAAa,IAAA,GAAW,MAAO,CAAE,KAAM,OAAQ,OAAM,EACzD,IAAMS,EAAO,OAAOT,CAAQ,EACtBU,EAAS,OAAOT,CAAU,EAC1BU,EAAS,OAAOT,GAAc,CAAC,EACrC,GAAIO,EAAO,IAAMC,EAAS,IAAMC,EAAS,GAAI,OAC7C,IAAMC,EAASC,EAAYT,CAAK,EAChC,GAAIQ,IAAW,IAAA,KACfJ,EAAM,YAAYC,EAAMC,EAASE,EAAQD,CAAM,EAC3C,IAAM,eAAe,EAAI,GAAKH,EAAM,eAAe,EAAI,OAC3D,MAAO,CAAE,KAAM,WAAY,MAAO,GAAGA,EAAM,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,IAAIL,GAAY,GAAG,EAAG,CAC5F,CAEA,SAASU,EAAY,EAAkC,CACrD,GAAIT,IAAS,IAAK,MAAO,GACzB,IAAMU,EAASV,EAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAK,EAAE,EACtCW,EAAQ,OAAOD,EAAO,MAAM,EAAG,CAAC,CAAC,EACjCE,EAAU,OAAOF,EAAO,MAAM,CAAC,CAAC,EACtC,GAAI,IAAQ,IAAME,EAAU,IAAOZ,EAAK,WAAW,GAAG,GAAKW,IAAU,GAAKC,IAAY,GACtF,OAAQZ,EAAK,WAAW,GAAG,EAAI,GAAK,IAAMW,EAAQ,GAAKC,EACzD,C"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseIsoDate.js","names":["parseIsoDate","input","value","match","yearText","monthText","dayText","hourText","minuteText","secondText","fraction","zone","year","month","day","local","hour","minute","second","offset","parseOffset","digits","hours","minutes"],"sources":["../src/parseIsoDate.ts"],"sourcesContent":["export type ParsedIsoDate = { kind: 'date'; value: string } | { kind: 'datetime'; value: string };\n\n/**\n * Parses an ISO calendar date or a timestamp with an explicit UTC offset, without guessing\n * a timezone or rolling invalid calendar dates forward. Returns undefined for unsupported input.\n *\n * Accepts YYYY-MM-DD and YYYY-MM-DDTHH:mm[:ss[.fraction]] followed by Z or ±HH:mm\n * (±HHmm also works). Surrounding whitespace is ignored. Date-only values stay date-only;\n * timestamps become UTC strings, retaining all fractional digits. Local timestamps, unknown\n * offsets (-00:00/-0000), leap seconds, and input or normalized UTC years outside 0000–9999\n * are unsupported.\n */\nexport function parseIsoDate(input: unknown): ParsedIsoDate | undefined {\n if (typeof input !== 'string') return undefined;\n const value = input.trim();\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2}))?$/u.exec(value);\n if (!match) return undefined;\n const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zone] = match;\n const year = Number(yearText);\n const month = Number(monthText);\n const day = Number(dayText);\n const local = new Date(0);\n local.setUTCFullYear(year, month - 1, day);\n if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day) {\n return undefined;\n }\n if (hourText === undefined) return { kind: 'date', value };\n const hour = Number(hourText);\n const minute = Number(minuteText);\n const second = Number(secondText ?? 0);\n if (hour > 23 || minute > 59 || second > 59) return undefined;\n const offset = parseOffset(zone!);\n if (offset === undefined) return undefined;\n local.setUTCHours(hour, minute - offset, second);\n if (local.getUTCFullYear() < 0 || local.getUTCFullYear() > 9999) return undefined;\n return { kind: 'datetime', value: `${local.toISOString().slice(0, 19)}${fraction ?? ''}Z` };\n}\n\nfunction parseOffset(zone: string): number | undefined {\n if (zone === 'Z') return 0;\n const digits = zone.slice(1).replace(':', '');\n const hours = Number(digits.slice(0, 2));\n const minutes = Number(digits.slice(2));\n if (hours > 23 || minutes > 59 || (zone.startsWith('-') && hours === 0 && minutes === 0)) return undefined;\n return (zone.startsWith('-') ? -1 : 1) * (hours * 60 + minutes);\n}\n"],"mappings":"AAYA,SAAgBA,EAAa,EAA2C,CACtE,GAAI,OAAOC,GAAU,SAAU,OAC/B,IAAMC,EAAQD,EAAM,KAAK,EACnBE,EAAQ,2FAA2F,KAAKD,CAAK,EACnH,GAAI,CAACC,EAAO,OACZ,GAAM,EAAGC,EAAUC,EAAWC,EAASC,EAAUC,EAAYC,EAAYC,EAAUC,GAAQR,EACrFS,EAAO,OAAOR,CAAQ,EACtBS,EAAQ,OAAOR,CAAS,EACxBS,EAAM,OAAOR,CAAO,EACpBS,kBAAQ,IAAI,KAAK,CAAC,EAExB,GADAA,EAAM,eAAeH,EAAMC,EAAQ,EAAGC,CAAG,EACrCC,EAAM,eAAe,IAAMH,GAAQG,EAAM,YAAY,IAAMF,EAAQ,GAAKE,EAAM,WAAW,IAAMD,EACjG,OAEF,GAAIP,IAAa,IAAA,GAAW,MAAO,CAAE,KAAM,OAAQ,OAAM,EACzD,IAAMS,EAAO,OAAOT,CAAQ,EACtBU,EAAS,OAAOT,CAAU,EAC1BU,EAAS,OAAOT,GAAc,CAAC,EACrC,GAAIO,EAAO,IAAMC,EAAS,IAAMC,EAAS,GAAI,OAC7C,IAAMC,EAASC,EAAYT,CAAK,EAChC,GAAIQ,IAAW,IAAA,KACfJ,EAAM,YAAYC,EAAMC,EAASE,EAAQD,CAAM,EAC3C,IAAM,eAAe,EAAI,GAAKH,EAAM,eAAe,EAAI,OAC3D,MAAO,CAAE,KAAM,WAAY,MAAO,GAAGA,EAAM,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,IAAIL,GAAY,GAAG,EAAG,CAC5F,CAEA,SAASU,EAAY,EAAkC,CACrD,GAAIT,IAAS,IAAK,MAAO,GACzB,IAAMU,EAASV,EAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAK,EAAE,EACtCW,EAAQ,OAAOD,EAAO,MAAM,EAAG,CAAC,CAAC,EACjCE,EAAU,OAAOF,EAAO,MAAM,CAAC,CAAC,EACtC,GAAI,IAAQ,IAAME,EAAU,IAAOZ,EAAK,WAAW,GAAG,GAAKW,IAAU,GAAKC,IAAY,GACtF,OAAQZ,EAAK,WAAW,GAAG,EAAI,GAAK,IAAMW,EAAQ,GAAKC,EACzD"}
1
+ {"version":3,"file":"parseIsoDate.js","names":["parseIsoDate","input","value","match","yearText","monthText","dayText","hourText","minuteText","secondText","fraction","zone","year","month","day","local","hour","minute","second","offset","parseOffset","digits","hours","minutes"],"sources":["../src/parseIsoDate.ts"],"sourcesContent":["export type ParsedIsoDate = { kind: 'date'; value: string } | { kind: 'datetime'; value: string };\n\n/**\n * Parses an ISO calendar date or a timestamp with an explicit UTC offset, without guessing\n * a timezone or rolling invalid calendar dates forward. Returns undefined for unsupported input.\n *\n * Accepts YYYY-MM-DD and YYYY-MM-DDTHH:mm[:ss[.fraction]] followed by Z or ±HH:mm\n * (±HHmm also works). Surrounding whitespace is ignored. Date-only values stay date-only;\n * timestamps become UTC strings, retaining all fractional digits. Local timestamps, unknown\n * offsets (-00:00/-0000), leap seconds, and input or normalized UTC years outside 0000–9999\n * are unsupported.\n */\nexport function parseIsoDate(input: unknown): ParsedIsoDate | undefined {\n if (typeof input !== 'string') return undefined;\n const value = input.trim();\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})(?:T(\\d{2}):(\\d{2})(?::(\\d{2})(\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2}))?$/u.exec(value);\n if (!match) return undefined;\n const [, yearText, monthText, dayText, hourText, minuteText, secondText, fraction, zone] = match;\n const year = Number(yearText);\n const month = Number(monthText);\n const day = Number(dayText);\n const local = new Date(0);\n local.setUTCFullYear(year, month - 1, day);\n if (local.getUTCFullYear() !== year || local.getUTCMonth() !== month - 1 || local.getUTCDate() !== day) {\n return undefined;\n }\n if (hourText === undefined) return { kind: 'date', value };\n const hour = Number(hourText);\n const minute = Number(minuteText);\n const second = Number(secondText ?? 0);\n if (hour > 23 || minute > 59 || second > 59) return undefined;\n const offset = parseOffset(zone!);\n if (offset === undefined) return undefined;\n local.setUTCHours(hour, minute - offset, second);\n if (local.getUTCFullYear() < 0 || local.getUTCFullYear() > 9999) return undefined;\n return { kind: 'datetime', value: `${local.toISOString().slice(0, 19)}${fraction ?? ''}Z` };\n}\n\nfunction parseOffset(zone: string): number | undefined {\n if (zone === 'Z') return 0;\n const digits = zone.slice(1).replace(':', '');\n const hours = Number(digits.slice(0, 2));\n const minutes = Number(digits.slice(2));\n if (hours > 23 || minutes > 59 || (zone.startsWith('-') && hours === 0 && minutes === 0)) return undefined;\n return (zone.startsWith('-') ? -1 : 1) * (hours * 60 + minutes);\n}\n"],"mappings":"AAYA,SAAgBA,EAAa,EAA2C,CACtE,GAAI,OAAOC,GAAU,SAAU,OAC/B,IAAMC,EAAQD,EAAM,KAAK,EACnBE,EAAQ,2FAA2F,KAAKD,CAAK,EACnH,GAAI,CAACC,EAAO,OACZ,GAAM,EAAGC,EAAUC,EAAWC,EAASC,EAAUC,EAAYC,EAAYC,EAAUC,GAAQR,EACrFS,EAAO,OAAOR,CAAQ,EACtBS,EAAQ,OAAOR,CAAS,EACxBS,EAAM,OAAOR,CAAO,EACpBS,kBAAQ,IAAI,KAAK,CAAC,EAExB,GADAA,EAAM,eAAeH,EAAMC,EAAQ,EAAGC,CAAG,EACrCC,EAAM,eAAe,IAAMH,GAAQG,EAAM,YAAY,IAAMF,EAAQ,GAAKE,EAAM,WAAW,IAAMD,EACjG,OAEF,GAAIP,IAAa,IAAA,GAAW,MAAO,CAAE,KAAM,OAAQ,OAAM,EACzD,IAAMS,EAAO,OAAOT,CAAQ,EACtBU,EAAS,OAAOT,CAAU,EAC1BU,EAAS,OAAOT,GAAc,CAAC,EACrC,GAAIO,EAAO,IAAMC,EAAS,IAAMC,EAAS,GAAI,OAC7C,IAAMC,EAASC,EAAYT,CAAK,EAChC,GAAIQ,IAAW,IAAA,KACfJ,EAAM,YAAYC,EAAMC,EAASE,EAAQD,CAAM,EAC3C,IAAM,eAAe,EAAI,GAAKH,EAAM,eAAe,EAAI,OAC3D,MAAO,CAAE,KAAM,WAAY,MAAO,GAAGA,EAAM,YAAY,CAAC,CAAC,MAAM,EAAG,EAAE,IAAIL,GAAY,GAAG,EAAG,CAC5F,CAEA,SAASU,EAAY,EAAkC,CACrD,GAAIT,IAAS,IAAK,MAAO,GACzB,IAAMU,EAASV,EAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,IAAK,EAAE,EACtCW,EAAQ,OAAOD,EAAO,MAAM,EAAG,CAAC,CAAC,EACjCE,EAAU,OAAOF,EAAO,MAAM,CAAC,CAAC,EACtC,GAAI,IAAQ,IAAME,EAAU,IAAOZ,EAAK,WAAW,GAAG,GAAKW,IAAU,GAAKC,IAAY,GACtF,OAAQZ,EAAK,WAAW,GAAG,EAAI,GAAK,IAAMW,EAAQ,GAAKC,EACzD,C"}