text-sanctifier 1.0.20 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,12 +5,15 @@
5
5
  [![bundle size](https://img.shields.io/bundlephobia/minzip/text-sanctifier)](https://bundlephobia.com/package/text-sanctifier)
6
6
  [![license](https://img.shields.io/npm/l/text-sanctifier)](https://github.com/iWhatty/text-sanctifier/blob/main/LICENSE)
7
7
  [![stars](https://img.shields.io/github/stars/iWhatty/text-sanctifier?style=social)](https://github.com/iWhatty/text-sanctifier)
8
+ [![types](https://img.shields.io/npm/types/text-sanctifier)](https://www.npmjs.com/package/text-sanctifier)
8
9
 
9
- Brutal text normalizer and invisible Unicode scrubber for modern web projects. ~1.66 KB gzipped.
10
+ Brutal text normalizer and invisible Unicode scrubber for modern web projects. ~2.17 KB gzipped.
10
11
 
11
12
  ## Features
12
13
 
13
14
  - Purges zero-width Unicode garbage
15
+ - Purges bidi controls, including the isolates (U+2066–U+2069) and the Arabic letter mark (U+061C) used in Trojan Source attacks
16
+ - Locates invisible and control characters by offset, line and column (`inspectTextDetailed`)
14
17
  - Normalizes line endings (CRLF, CR, LF) to LF
15
18
  - Collapses unwanted spaces and paragraphs
16
19
  - Nukes control characters (if enabled)
@@ -137,6 +140,42 @@ const report = inspectText(input);
137
140
 
138
141
  Use `inspectText` to preflight text content before rendering, storing, or linting. It's a diagnostic tool to help inform sanitization needs. Pass the report to `getRecommendedSanctifierOptions(report)` to auto-generate config flags for `summonSanctifier()`.
139
142
 
143
+ ### `inspectTextDetailed(text: string, options?: { maxFindings?: number }): DetailedTrashReport`
144
+
145
+ Returns each invisible or control character with its offset, line, column, code point and category, plus per-category counts and a `truncated` flag. Also exported from the browser build (`text-sanctifier/browser`). `inspectText`'s output is unchanged.
146
+
147
+ ```js
148
+ import { inspectTextDetailed } from 'text-sanctifier';
149
+
150
+ const { findings, counts, total, truncated } = inspectTextDetailed(input, { maxFindings: 100 });
151
+
152
+ /*
153
+ findings: [
154
+ { offset: 0, line: 1, column: 1, codePoint: 0xFEFF, category: 'bom', atStart: true },
155
+ { offset: 42, line: 3, column: 7, codePoint: 0x2067, category: 'bidi_control' },
156
+ ]
157
+ counts: { bidi_control: 1, zero_width: 0, nonbreaking_space: 0, bom: 1, nul: 0, c0_control: 0, c1_control: 0 }
158
+ total: 2
159
+ truncated: false
160
+ */
161
+ ```
162
+
163
+ | Category | Code points |
164
+ | --- | --- |
165
+ | `bidi_control` | U+061C, U+200E, U+200F, U+202A–U+202E, U+2066–U+2069 |
166
+ | `zero_width` | U+200B–U+200D, U+2060 |
167
+ | `nonbreaking_space` | U+00A0, U+2000–U+200A, U+202F, U+3000 (non-breaking and typographic spaces) |
168
+ | `bom` | U+FEFF; `atStart` is true at offset 0, where a BOM is legitimate |
169
+ | `nul` | U+0000 |
170
+ | `c0_control` | U+0001–U+001F except tab, LF and CR; U+007F |
171
+ | `c1_control` | U+0080–U+009F |
172
+
173
+ `offset` is a 0-based UTF-16 index; `line` and `column` are 1-based, with columns in UTF-16 code units and lines ending at LF, CR or CRLF. Every occurrence is counted; only the first `maxFindings` (default 100, `Infinity` for all) are listed, and `truncated` says when some were not. The scan is one pass with no regex and no copy of the input.
174
+
175
+ `zero_width` also flags U+200D (zero width joiner) inside emoji ZWJ sequences such as 👨‍👩‍👧, so a finding there is not necessarily hidden text. Filter by neighbouring code points if you need to tell them apart. (`purgeInvisibleChars` removes those joiners too, splitting the emoji; `keyboardOnlyEmoji` keeps the sequences whole.)
176
+
177
+ The categories cover exactly what `nukeControls` and `purgeInvisibleChars` strip.
178
+
140
179
  ---
141
180
 
142
181
  ## Notes
@@ -145,12 +184,14 @@ Use `inspectText` to preflight text content before rendering, storing, or lintin
145
184
 
146
185
  Requires a modern JavaScript runtime with ES2020+ support:
147
186
 
148
- - Node.js 14+
187
+ - Node.js 18+ (`engines.node >=18`)
149
188
  - Modern evergreen browsers
150
189
 
151
190
  ### Package and build info
152
191
 
153
192
  - **Source (`src/`):** ES2020+ ESM modules with JSDoc
193
+ - **ESM only:** no CommonJS build; `require('text-sanctifier')` works only on Node versions with `require(esm)` support (20.19+, 22.12+)
194
+ - **Types:** TypeScript declarations ship in `dist/types`
154
195
  - **Browser build (`dist/`):** Minified ESM bundle for `<script type="module">`
155
196
  - **Tree-shake friendly:** ships `sideEffects: false`
156
197
  - **Zero transpilation:** no built-in polyfills or runtime overhead
@@ -1,9 +1,12 @@
1
- function g(a={}){const c=!!a.purgeInvisibleChars,b=!!a.purgeEmojis,d=!!a.nukeControls,f=!!a.keyboardOnlyFilter,h=!!a.normalizeNewlines,m=!!a.trimSpacesAroundNewlines,e=!!a.collapseNewLines,r=!!a.preserveParagraphs,t=!!a.collapseSpaces,u=!!a.finalTrim,n=Number.isFinite(a.maxLength)&&0<=a.maxLength?a.maxLength:Infinity,k="truncate"===a.g||"noop"===a.g||"throw"===a.g?a.g:"throw";return z=>l(z,c,b,d,f,h,m,e,r,t,u,n,k)}g.strict=a=>l(a,!0,!0,!0,!1,!0,!0,!0,!1,!0,!0);
2
- g.loose=a=>l(a,!1,!1,!1,!1,!0,!0,!0,!0,!0,!0);g.keyboardOnlyEmoji=a=>l(a,!1,!1,!1,!0,!0,!0,!1,!1,!1,!0);g.keyboardOnly=a=>l(a,!0,!0,!0,!0,!0,!0,!0,!1,!0,!0);
3
- function l(a,c=!1,b=!1,d=!1,f=!1,h=!1,m=!1,e=!1,r=!1,t=!1,u=!1,n=Infinity,k="throw"){if("string"!==typeof a)throw new TypeError("sanctifyText expects a string input.");"truncate"!==k&&"noop"!==k&&"throw"!==k&&(k="throw");if(a.length>n)switch(k){case "truncate":a=a.slice(0,n);break;case "noop":return a;default:throw new RangeError(`sanctifyText input length ${a.length} exceeds maxLength ${n}.`);}c&&(a=a.replace(p,""));b&&(a=a.replace(q,""));d&&(a=a.replace(v,""));f&&(a=w(a,b));h&&(a=a.replace(x,"\n"));
4
- m&&(a=a.replace(y,"$1"));e&&(c=a,a=r?c.replace(A,"\n\n"):c.replace(B,"\n"));t&&(a=a.replace(C," "));return u?a.trim():a}var p=/[\u00A0\u2000-\u200D\u202F\u2060\u3000\uFEFF\u200E\u200F\u202A-\u202E]+/g,D=/[^\x20-\x7E\r\n]+/gu;function w(a,c=!1){a=E(a);return c?a.replace(D,""):a.replace(D,b=>{q.lastIndex=0;if(q.test(b)){q.lastIndex=0;var d="";for(const f of b.matchAll(q))d+=f[0];b=d}else b="";return b})}
5
- var F=/[\u2018\u2019\u201A\u201B\u2032\u2035]/g,G=/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g,H=/[\u2012\u2013\u2014\u2015\u2212]/g,I=/\u2026/g,J=/[\u2022\u00B7]/g,K=/[\uFF01-\uFF5E]/g;function E(a){return a.replace(F,"'").replace(G,'"').replace(H,"-").replace(I,"...").replace(J,"*").replace(K,c=>String.fromCharCode(c.charCodeAt(0)-65248))}var q;try{q=RegExp("(?:\\p{Extended_Pictographic}(?:\\uFE0F|\\uFE0E)?(?:\\u200D(?:\\p{Extended_Pictographic}|\\w)+)*)","gu")}catch{q=/[\u{2700}-\u{27BF}\u{1F300}-\u{1FAFF}\u{1F3FB}-\u{1F3FF}\u200D\uFE0F\u{1F1E6}-\u{1F1FF}]/gu}
6
- var x=/\r\n|\r|\n/g,y=/[ \t]*(\n+)[ \t]*/g,B=/\n{2,}/g,A=/\n{3,}/g,C=/ {2,}/g,v=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\u200E\u200F\u202A-\u202E]+/g;function L(a,c){a.lastIndex=0;return a.test(c)}
7
- function M(a){if("string"!==typeof a)throw new TypeError("inspectText expects a string input.");const c=[],b={hasControlChars:!1,hasInvisibleChars:!1,hasMixedNewlines:!1,newlineStyle:null,hasEmojis:!1,hasNonKeyboardChars:!1,summary:c};L(v,a)&&(b.hasControlChars=!0,c.push("Control characters detected."));L(p,a)&&(b.hasInvisibleChars=!0,c.push("Invisible Unicode characters detected."));L(q,a)&&(b.hasEmojis=!0,c.push("Emojis detected."));if("string"!==typeof a)throw new TypeError("getNewlineStats expects a string input.");
8
- var d=a.replace(/\r\n/g,"");d={i:(a.match(/\r\n/g)||[]).length,h:(d.match(/\r/g)||[]).length,j:(d.match(/\n/g)||[]).length};const f=[];0<d.i&&f.push("CRLF");0<d.h&&f.push("CR");0<d.j&&f.push("LF");const {l:h,types:m}={...d,types:f,l:1<f.length};b.hasMixedNewlines=h;b.newlineStyle=h?"Mixed":m[0]||null;b.newlineStyle&&c.push(h?"Mixed newline styles detected.":`Consistent newline style: ${b.newlineStyle}`);a:{a=E(a);if(L(D,a))for(e of a)if(!("\n"===e||"\r"===e||" "<=e&&"~">=e||L(q,e))){var e=!0;break a}e=
9
- !1}e&&(b.hasNonKeyboardChars=!0,c.push("Non-keyboard characters detected."));return b}function N(a){return{purgeInvisibleChars:a.hasInvisibleChars,purgeEmojis:a.hasEmojis,nukeControls:a.hasControlChars,keyboardOnlyFilter:a.hasNonKeyboardChars,normalizeNewlines:a.hasMixedNewlines||"CRLF"===a.newlineStyle||"CR"===a.newlineStyle}}export { g as summonSanctifier, M as inspectText, N as getRecommendedSanctifierOptions };
1
+ function n(a={}){const c=!!a.purgeInvisibleChars,d=!!a.purgeEmojis,f=!!a.nukeControls,g=!!a.keyboardOnlyFilter,l=!!a.normalizeNewlines,h=!!a.trimSpacesAroundNewlines,b=!!a.collapseNewLines,e=!!a.preserveParagraphs,k=!!a.collapseSpaces,m=!!a.finalTrim,p=Number.isFinite(a.maxLength)&&0<=a.maxLength?a.maxLength:Infinity,q="truncate"===a.g||"noop"===a.g||"throw"===a.g?a.g:"throw";return z=>r(z,c,d,f,g,l,h,b,e,k,m,p,q)}n.strict=a=>r(a,!0,!0,!0,!1,!0,!0,!0,!1,!0,!0);
2
+ n.loose=a=>r(a,!1,!1,!1,!1,!0,!0,!0,!0,!0,!0);n.keyboardOnlyEmoji=a=>r(a,!1,!1,!1,!0,!0,!0,!1,!1,!1,!0);n.keyboardOnly=a=>r(a,!0,!0,!0,!0,!0,!0,!0,!1,!0,!0);
3
+ function r(a,c=!1,d=!1,f=!1,g=!1,l=!1,h=!1,b=!1,e=!1,k=!1,m=!1,p=Infinity,q="throw"){if("string"!==typeof a)throw new TypeError("sanctifyText expects a string input.");"truncate"!==q&&"noop"!==q&&"throw"!==q&&(q="throw");if(a.length>p)switch(q){case "truncate":a=a.slice(0,p);break;case "noop":return a;default:throw new RangeError(`sanctifyText input length ${a.length} exceeds maxLength ${p}.`);}c&&(a=a.replace(t,""));d&&(a=a.replace(u,""));f&&(a=a.replace(v,""));g&&(a=w(a,d));l&&(a=a.replace(x,"\n"));
4
+ h&&(a=a.replace(y,"$1"));b&&(c=a,a=e?c.replace(A,"\n\n"):c.replace(B,"\n"));k&&(a=a.replace(C," "));return m?a.trim():a}var t=/[\u00A0\u2000-\u200D\u202F\u2060\u3000\uFEFF\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]+/g,D=/[^\x20-\x7E\r\n]+/gu;function w(a,c=!1){a=E(a);return c?a.replace(D,""):a.replace(D,d=>{u.lastIndex=0;if(u.test(d)){u.lastIndex=0;var f="";for(const g of d.matchAll(u))f+=g[0];d=f}else d="";return d})}
5
+ var F=/[\u2018\u2019\u201A\u201B\u2032\u2035]/g,G=/[\u201C\u201D\u201E\u201F\u2033\u2036\u00AB\u00BB]/g,H=/[\u2012\u2013\u2014\u2015\u2212]/g,I=/\u2026/g,J=/[\u2022\u00B7]/g,K=/[\uFF01-\uFF5E]/g;function E(a){return a.replace(F,"'").replace(G,'"').replace(H,"-").replace(I,"...").replace(J,"*").replace(K,c=>String.fromCharCode(c.charCodeAt(0)-65248))}var u;try{u=RegExp("(?:\\p{Extended_Pictographic}(?:\\uFE0F|\\uFE0E)?(?:\\u200D(?:\\p{Extended_Pictographic}|\\w)+)*)","gu")}catch{u=/[\u{2700}-\u{27BF}\u{1F300}-\u{1FAFF}\u{1F3FB}-\u{1F3FF}\u200D\uFE0F\u{1F1E6}-\u{1F1FF}]/gu}
6
+ var x=/\r\n|\r|\n/g,y=/[ \t]*(\n+)[ \t]*/g,B=/\n{2,}/g,A=/\n{3,}/g,C=/ {2,}/g,v=/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F\u0080-\u009F\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]+/g;function L(a,c){a.lastIndex=0;return a.test(c)}
7
+ function M(a){if("string"!==typeof a)throw new TypeError("inspectText expects a string input.");const c=[],d={hasControlChars:!1,hasInvisibleChars:!1,hasMixedNewlines:!1,newlineStyle:null,hasEmojis:!1,hasNonKeyboardChars:!1,summary:c};L(v,a)&&(d.hasControlChars=!0,c.push("Control characters detected."));L(t,a)&&(d.hasInvisibleChars=!0,c.push("Invisible Unicode characters detected."));L(u,a)&&(d.hasEmojis=!0,c.push("Emojis detected."));if("string"!==typeof a)throw new TypeError("getNewlineStats expects a string input.");
8
+ var f=0,g=0;let l=0;for(let k=0,m=a.length;k<m;k++){const p=a.charCodeAt(k);13===p?10===a.charCodeAt(k+1)?(f++,k++):g++:10===p&&l++}f={i:f,h:g,j:l};g=[];0<f.i&&g.push("CRLF");0<f.h&&g.push("CR");0<f.j&&g.push("LF");const {l:h,types:b}={...f,types:g,l:1<g.length};d.hasMixedNewlines=h;d.newlineStyle=h?"Mixed":b[0]||null;d.newlineStyle&&c.push(h?"Mixed newline styles detected.":`Consistent newline style: ${d.newlineStyle}`);a:{a=E(a);if(L(D,a))for(e of a)if(!("\n"===e||"\r"===e||" "<=e&&"~">=e||L(u,
9
+ e))){var e=!0;break a}e=!1}e&&(d.hasNonKeyboardChars=!0,c.push("Non-keyboard characters detected."));return d}
10
+ function N(a,c={}){if("string"!==typeof a)throw new TypeError("inspectTextDetailed expects a string input.");c=c&&c.maxFindings;c=Infinity===c||Number.isInteger(c)&&0<=c?c:100;const d={bidi_control:0,zero_width:0,nonbreaking_space:0,bom:0,nul:0,c0_control:0,c1_control:0},f=[];let g=0,l=1,h=0;for(let e=0,k=a.length;e<k;e++){var b=a.charCodeAt(e);if(32<=b&&127>b)continue;if(10===b){l++;h=e+1;continue}if(13===b){10!==a.charCodeAt(e+1)&&(l++,h=e+1);continue}const m=128>b?0===b?"nul":32>b?9===b||10===
11
+ b||13===b?null:"c0_control":127===b?"c0_control":null:159>=b?"c1_control":160===b?"nonbreaking_space":1564===b?"bidi_control":8192>b?null:8202>=b?"nonbreaking_space":8205>=b?"zero_width":8207>=b||8234<=b&&8238>=b?"bidi_control":8239===b?"nonbreaking_space":8288===b?"zero_width":8294<=b&&8297>=b?"bidi_control":12288===b?"nonbreaking_space":65279===b?"bom":null;null!==m&&(d[m]++,g++,f.length<c&&(b={offset:e,line:l,column:e-h+1,codePoint:b,category:m},"bom"===m&&(b.atStart=0===e),f.push(b)))}return{findings:f,
12
+ counts:d,total:g,truncated:g>f.length}}function O(a){return{purgeInvisibleChars:a.hasInvisibleChars,purgeEmojis:a.hasEmojis,nukeControls:a.hasControlChars,keyboardOnlyFilter:a.hasNonKeyboardChars,normalizeNewlines:a.hasMixedNewlines||"CRLF"===a.newlineStyle||"CR"===a.newlineStyle}}export { n as summonSanctifier, M as inspectText, O as getRecommendedSanctifierOptions, N as inspectTextDetailed };
@@ -1,4 +1,5 @@
1
1
  import { inspectText } from './inspectText.js';
2
+ import { inspectTextDetailed } from './inspectText.js';
2
3
  import { getRecommendedSanctifierOptions } from './inspectText.js';
3
4
  import { summonSanctifier } from './sanctifyText.js';
4
- export { inspectText, getRecommendedSanctifierOptions, summonSanctifier };
5
+ export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions, summonSanctifier };
@@ -3,6 +3,26 @@
3
3
  * @param {string} text @returns {UnicodeTrashReport}
4
4
  */
5
5
  export function inspectText(text: string): UnicodeTrashReport;
6
+ /**
7
+ * Locates invisible and control characters, one finding per character.
8
+ *
9
+ * Categories:
10
+ * - `bidi_control`: U+061C, U+200E, U+200F, U+202A–U+202E, U+2066–U+2069
11
+ * - `zero_width`: U+200B–U+200D, U+2060
12
+ * - `nonbreaking_space`: non-breaking and typographic spaces U+00A0, U+2000–U+200A, U+202F, U+3000
13
+ * - `bom`: U+FEFF (`atStart` says whether it sits at offset 0)
14
+ * - `nul`: U+0000
15
+ * - `c0_control`: U+0001–U+001F except tab, LF and CR; and U+007F
16
+ * - `c1_control`: U+0080–U+009F
17
+ *
18
+ * Every occurrence is counted; only the first `maxFindings` are listed.
19
+ * One pass over the string, no regex, no copy.
20
+ *
21
+ * @param {string} text
22
+ * @param {InspectDetailedOptions} [options]
23
+ * @returns {DetailedTrashReport}
24
+ */
25
+ export function inspectTextDetailed(text: string, options?: InspectDetailedOptions): DetailedTrashReport;
6
26
  /**
7
27
  * Counts the number of different newline types in a string.
8
28
  * @param {string} text
@@ -36,3 +56,52 @@ export type UnicodeTrashReport = {
36
56
  hasNonKeyboardChars: boolean;
37
57
  summary: string[];
38
58
  };
59
+ export type TrashCategory = "bidi_control" | "zero_width" | "nonbreaking_space" | "bom" | "nul" | "c0_control" | "c1_control";
60
+ /**
61
+ * One located character.
62
+ * - `offset`: 0-based UTF-16 index into the input (a JS string index).
63
+ * - `line`: 1-based; lines end at LF, CR, or CRLF (counted once).
64
+ * - `column`: 1-based, in UTF-16 code units from the start of the line.
65
+ * - `atStart`: present on `bom` findings only; true at offset 0, where a BOM is legitimate.
66
+ */
67
+ export type TrashFinding = {
68
+ offset: number;
69
+ line: number;
70
+ column: number;
71
+ codePoint: number;
72
+ category: TrashCategory;
73
+ atStart?: boolean;
74
+ };
75
+ export type TrashCounts = {
76
+ bidi_control: number;
77
+ zero_width: number;
78
+ nonbreaking_space: number;
79
+ bom: number;
80
+ nul: number;
81
+ c0_control: number;
82
+ c1_control: number;
83
+ };
84
+ export type DetailedTrashReport = {
85
+ /**
86
+ * - In offset order, at most `maxFindings` long.
87
+ */
88
+ findings: TrashFinding[];
89
+ /**
90
+ * - Every occurrence per category, listed or not.
91
+ */
92
+ counts: TrashCounts;
93
+ /**
94
+ * - Sum of `counts`.
95
+ */
96
+ total: number;
97
+ /**
98
+ * - True when `total` exceeds `findings.length`.
99
+ */
100
+ truncated: boolean;
101
+ };
102
+ export type InspectDetailedOptions = {
103
+ /**
104
+ * - Cap on listed findings (non-negative integer or Infinity).
105
+ */
106
+ maxFindings?: number;
107
+ };
@@ -123,7 +123,7 @@ export function normalizeTypographicJank(text: string): string;
123
123
  * Targets:
124
124
  * - Non-breaking spaces (\u00A0)
125
125
  * - Zero-width spaces and miscellaneous Unicode spaces (\u2000–\u200D, \u202F, \u2060, \u3000, \uFEFF)
126
- * - Left-to-right/right-to-left markers and overrides (\u200E, \u200F, \u202A–\u202E)
126
+ * - Bidi controls: marks, embeddings/overrides and isolates (\u061C, \u200E, \u200F, \u202A–\u202E, \u2066–\u2069)
127
127
  *
128
128
  * @param {string} text
129
129
  * @returns {string}
@@ -145,7 +145,7 @@ export let EMOJI_REGEX: RegExp;
145
145
  * Control characters nuked:
146
146
  * - ASCII control range (0x00-0x1F, 0x7F)
147
147
  * - Unicode control range (0x80-0x9F)
148
- * - RTL/LTR markers (U+200E, U+200F, U+202A–U+202E)
148
+ * - Bidi controls (U+061C, U+200E, U+200F, U+202A–U+202E, U+2066–U+2069)
149
149
  *
150
150
  * @param {string} text
151
151
  * @returns {string}
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "text-sanctifier",
3
- "version": "1.0.20",
4
- "type": "module",
5
- "description": "A brutal text normalizer and invisible trash scrubber for modern web projects.",
6
- "engines": {
7
- "node": ">=18"
8
- },
9
- "main": "./src/index.js",
10
- "module": "./src/index.js",
11
- "browser": "./dist/text-sanctifier.min.js",
12
- "files": [
13
- "src",
14
- "dist/text-sanctifier.min.js",
15
- "dist/types",
16
- "LICENSE",
17
- "README.md",
18
- "ADDITIONAL_TERMS.md",
19
- "robots.txt"
20
- ],
21
- "exports": {
22
- ".": {
23
- "types": "./dist/types/index.d.ts",
24
- "default": "./src/index.js"
25
- },
26
- "./browser": {
27
- "types": "./dist/types/index.d.ts",
28
- "default": "./dist/text-sanctifier.min.js"
29
- }
30
- },
31
- "types": "./dist/types/index.d.ts",
32
- "sideEffects": false,
33
- "scripts": {
34
- "build": "node scripts/buildc.js",
35
- "build:types": "tsc -p tsconfig.types.json",
36
- "test": "node tests/sanctifyText.test.js",
37
- "test-min": "node tests/sanctifyText.test.min.js",
38
- "ci": "npm run test && npm run build && npm run build:types && npm run test-min"
39
- },
40
- "keywords": [
41
- "text",
42
- "sanitize",
43
- "normalize",
44
- "invisible",
45
- "scrubber",
46
- "unicode",
47
- "clean",
48
- "sanctify"
49
- ],
50
- "author": "WATT3D <WATT3D@protonmail.com>",
51
- "license": "SEE LICENSE IN LICENSE",
52
- "repository": {
53
- "type": "git",
54
- "url": "git+https://github.com/iWhatty/text-sanctifier.git"
55
- },
56
- "bugs": {
57
- "url": "https://github.com/iWhatty/text-sanctifier/issues"
58
- },
59
- "homepage": "https://github.com/iWhatty/text-sanctifier#readme",
60
- "directories": {
61
- "test": "tests"
62
- },
63
- "devDependencies": {
64
- "esbuild": "^0.25.3",
65
- "google-closure-compiler": "^20240317.0.0",
66
- "terser": "^5.39.0",
67
- "typescript": "^5.9.3"
68
- }
69
- }
1
+ {
2
+ "name": "text-sanctifier",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "description": "A brutal text normalizer and invisible trash scrubber for modern web projects.",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
9
+ "main": "./src/index.js",
10
+ "module": "./src/index.js",
11
+ "browser": "./dist/text-sanctifier.min.js",
12
+ "files": [
13
+ "src",
14
+ "dist/text-sanctifier.min.js",
15
+ "dist/types",
16
+ "LICENSE",
17
+ "README.md",
18
+ "ADDITIONAL_TERMS.md",
19
+ "robots.txt"
20
+ ],
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/types/index.d.ts",
24
+ "default": "./src/index.js"
25
+ },
26
+ "./browser": {
27
+ "types": "./dist/types/index.d.ts",
28
+ "default": "./dist/text-sanctifier.min.js"
29
+ }
30
+ },
31
+ "types": "./dist/types/index.d.ts",
32
+ "sideEffects": false,
33
+ "scripts": {
34
+ "build": "node scripts/buildc.js",
35
+ "build:types": "tsc -p tsconfig.types.json",
36
+ "test": "node tests/sanctifyText.test.js && node tests/inspectTextDetailed.test.js",
37
+ "test-min": "node tests/sanctifyText.test.min.js",
38
+ "ci": "npm run test && npm run build && npm run build:types && npm run test-min"
39
+ },
40
+ "keywords": [
41
+ "text",
42
+ "sanitize",
43
+ "normalize",
44
+ "invisible",
45
+ "scrubber",
46
+ "unicode",
47
+ "clean",
48
+ "sanctify"
49
+ ],
50
+ "author": "WATT3D <WATT3D@protonmail.com>",
51
+ "license": "SEE LICENSE IN LICENSE",
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/iWhatty/text-sanctifier.git"
55
+ },
56
+ "bugs": {
57
+ "url": "https://github.com/iWhatty/text-sanctifier/issues"
58
+ },
59
+ "homepage": "https://github.com/iWhatty/text-sanctifier#readme",
60
+ "directories": {
61
+ "test": "tests"
62
+ },
63
+ "devDependencies": {
64
+ "esbuild": "^0.25.3",
65
+ "google-closure-compiler": "^20240317.0.0",
66
+ "terser": "^5.39.0",
67
+ "typescript": "^5.9.3"
68
+ }
69
+ }
package/robots.txt CHANGED
@@ -1,79 +1,79 @@
1
- # WATT3D — AI scraping policy
2
- #
3
- # This file enumerates known AI / LLM training and inference crawlers
4
- # that the WATT3D AI Training Rider does not consent to. See
5
- # ./ADDITIONAL_TERMS.md for the legal scope and definitions.
6
- #
7
- # Honoring robots.txt is voluntary. The rider applies whether or not
8
- # a bot respects this file. The enumeration is non-exhaustive — bots
9
- # not listed here are still bound by the rider if they perform
10
- # Training Use as defined in ADDITIONAL_TERMS.md §1.
11
-
12
- User-agent: GPTBot
13
- Disallow: /
14
-
15
- User-agent: ChatGPT-User
16
- Disallow: /
17
-
18
- User-agent: OAI-SearchBot
19
- Disallow: /
20
-
21
- User-agent: anthropic-ai
22
- Disallow: /
23
-
24
- User-agent: ClaudeBot
25
- Disallow: /
26
-
27
- User-agent: Claude-Web
28
- Disallow: /
29
-
30
- User-agent: Claude-SearchBot
31
- Disallow: /
32
-
33
- User-agent: Google-Extended
34
- Disallow: /
35
-
36
- User-agent: Applebot-Extended
37
- Disallow: /
38
-
39
- User-agent: Meta-ExternalAgent
40
- Disallow: /
41
-
42
- User-agent: Meta-ExternalFetcher
43
- Disallow: /
44
-
45
- User-agent: FacebookBot
46
- Disallow: /
47
-
48
- User-agent: CCBot
49
- Disallow: /
50
-
51
- User-agent: PerplexityBot
52
- Disallow: /
53
-
54
- User-agent: Perplexity-User
55
- Disallow: /
56
-
57
- User-agent: cohere-ai
58
- Disallow: /
59
-
60
- User-agent: Bytespider
61
- Disallow: /
62
-
63
- User-agent: Amazonbot
64
- Disallow: /
65
-
66
- User-agent: Omgilibot
67
- Disallow: /
68
-
69
- User-agent: AI2Bot
70
- Disallow: /
71
-
72
- User-agent: Diffbot
73
- Disallow: /
74
-
75
- # Everyone else: full access. Search engines and human-driven crawlers
76
- # remain welcome.
77
-
78
- User-agent: *
79
- Allow: /
1
+ # WATT3D — AI scraping policy
2
+ #
3
+ # This file enumerates known AI / LLM training and inference crawlers
4
+ # that the WATT3D AI Training Rider does not consent to. See
5
+ # ./ADDITIONAL_TERMS.md for the legal scope and definitions.
6
+ #
7
+ # Honoring robots.txt is voluntary. The rider applies whether or not
8
+ # a bot respects this file. The enumeration is non-exhaustive — bots
9
+ # not listed here are still bound by the rider if they perform
10
+ # Training Use as defined in ADDITIONAL_TERMS.md §1.
11
+
12
+ User-agent: GPTBot
13
+ Disallow: /
14
+
15
+ User-agent: ChatGPT-User
16
+ Disallow: /
17
+
18
+ User-agent: OAI-SearchBot
19
+ Disallow: /
20
+
21
+ User-agent: anthropic-ai
22
+ Disallow: /
23
+
24
+ User-agent: ClaudeBot
25
+ Disallow: /
26
+
27
+ User-agent: Claude-Web
28
+ Disallow: /
29
+
30
+ User-agent: Claude-SearchBot
31
+ Disallow: /
32
+
33
+ User-agent: Google-Extended
34
+ Disallow: /
35
+
36
+ User-agent: Applebot-Extended
37
+ Disallow: /
38
+
39
+ User-agent: Meta-ExternalAgent
40
+ Disallow: /
41
+
42
+ User-agent: Meta-ExternalFetcher
43
+ Disallow: /
44
+
45
+ User-agent: FacebookBot
46
+ Disallow: /
47
+
48
+ User-agent: CCBot
49
+ Disallow: /
50
+
51
+ User-agent: PerplexityBot
52
+ Disallow: /
53
+
54
+ User-agent: Perplexity-User
55
+ Disallow: /
56
+
57
+ User-agent: cohere-ai
58
+ Disallow: /
59
+
60
+ User-agent: Bytespider
61
+ Disallow: /
62
+
63
+ User-agent: Amazonbot
64
+ Disallow: /
65
+
66
+ User-agent: Omgilibot
67
+ Disallow: /
68
+
69
+ User-agent: AI2Bot
70
+ Disallow: /
71
+
72
+ User-agent: Diffbot
73
+ Disallow: /
74
+
75
+ # Everyone else: full access. Search engines and human-driven crawlers
76
+ # remain welcome.
77
+
78
+ User-agent: *
79
+ Allow: /
package/src/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { inspectText } from './inspectText.js';
2
- import { getRecommendedSanctifierOptions } from './inspectText.js';
3
- import { summonSanctifier } from './sanctifyText.js';
4
- export { inspectText, getRecommendedSanctifierOptions, summonSanctifier };
1
+ import { inspectText } from './inspectText.js';
2
+ import { inspectTextDetailed } from './inspectText.js';
3
+ import { getRecommendedSanctifierOptions } from './inspectText.js';
4
+ import { summonSanctifier } from './sanctifyText.js';
5
+ export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions, summonSanctifier };
package/src/index.js CHANGED
@@ -1,12 +1,12 @@
1
- // src/index.js
2
-
3
-
4
- import { inspectText, getRecommendedSanctifierOptions } from './inspectText.js';
5
- export { inspectText, getRecommendedSanctifierOptions };
6
-
7
- import { summonSanctifier } from './sanctifyText.js';
8
- export { summonSanctifier };
9
-
10
-
11
- // In Future: export helpers for power users
12
- // export { purgeInvisibleTrash, purgeEmojisCharacters, normalizeNewlines, trimSpacesAroundNewlines, collapseParagraphs, collapseExtraSpaces, purgeControlCharacters } from './sanctifyText.js';
1
+ // src/index.js
2
+
3
+
4
+ import { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions } from './inspectText.js';
5
+ export { inspectText, inspectTextDetailed, getRecommendedSanctifierOptions };
6
+
7
+ import { summonSanctifier } from './sanctifyText.js';
8
+ export { summonSanctifier };
9
+
10
+
11
+ // In Future: export helpers for power users
12
+ // export { purgeInvisibleTrash, purgeEmojisCharacters, normalizeNewlines, trimSpacesAroundNewlines, collapseParagraphs, collapseExtraSpaces, purgeControlCharacters } from './sanctifyText.js';