apdev-js 0.1.0 → 0.2.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/dist/cli.js CHANGED
@@ -9,88 +9,98 @@ var __dirname = /* @__PURE__ */ getDirname();
9
9
 
10
10
  // src/cli.ts
11
11
  import { readFileSync as readFileSync4 } from "fs";
12
- import { resolve, dirname, join as join3 } from "path";
13
- import { fileURLToPath as fileURLToPath2 } from "url";
12
+ import { resolve, dirname as dirname2, join as join4, sep as sep3 } from "path";
13
+ import { fileURLToPath as fileURLToPath3 } from "url";
14
14
  import { execFileSync } from "child_process";
15
15
  import { Command } from "commander";
16
16
 
17
17
  // src/check-chars.ts
18
- import { readFileSync } from "fs";
19
- import { extname } from "path";
20
- var EMOJI_RANGES = [
21
- [127744, 128511],
22
- // Symbols and Pictographs
23
- [128512, 128591],
24
- // Emoticons
25
- [128640, 128767],
26
- // Transport and Map Symbols
27
- [128896, 129023],
28
- // Geometric Shapes Extended
29
- [129280, 129535],
30
- // Supplemental Symbols and Pictographs
31
- [9728, 9983],
32
- // Miscellaneous Symbols
33
- [9984, 10175]
34
- // Dingbats
35
- ];
36
- var EXTRA_ALLOWED_RANGES = [
37
- [128, 255],
38
- // Latin-1 Supplement
39
- [8192, 8303],
40
- // General Punctuation
41
- [8448, 8527],
42
- // Letterlike Symbols
43
- [8592, 8703],
44
- // Arrows
45
- [8704, 8959],
46
- // Mathematical Operators
47
- [8960, 9215],
48
- // Miscellaneous Technical
49
- [9472, 9599],
50
- // Box Drawing
51
- [9632, 9727],
52
- // Geometric Shapes
53
- [11008, 11263],
54
- // Miscellaneous Symbols and Arrows
55
- [65024, 65039]
56
- // Variation Selectors
57
- ];
58
- var ALL_RANGES = [...EMOJI_RANGES, ...EXTRA_ALLOWED_RANGES];
59
- var DANGEROUS_CODEPOINTS = /* @__PURE__ */ new Map([
60
- // Bidi control characters (Trojan Source - CVE-2021-42574)
61
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
62
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
63
- [8236, "POP DIRECTIONAL FORMATTING"],
64
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
65
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
66
- [8294, "LEFT-TO-RIGHT ISOLATE"],
67
- [8295, "RIGHT-TO-LEFT ISOLATE"],
68
- [8296, "FIRST STRONG ISOLATE"],
69
- [8297, "POP DIRECTIONAL ISOLATE"],
70
- // Zero-width characters
71
- [8203, "ZERO WIDTH SPACE"],
72
- [8204, "ZERO WIDTH NON-JOINER"],
73
- [8205, "ZERO WIDTH JOINER"],
74
- [8206, "LEFT-TO-RIGHT MARK"],
75
- [8207, "RIGHT-TO-LEFT MARK"],
76
- [8288, "WORD JOINER"]
77
- ]);
78
- var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
79
- var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
80
- function isAllowedChar(c) {
81
- const code = c.codePointAt(0);
82
- if (code <= 127) {
83
- return true;
18
+ import { readFileSync, existsSync } from "fs";
19
+ import { extname, dirname, join, sep } from "path";
20
+ import { fileURLToPath as fileURLToPath2 } from "url";
21
+ function getCharsetsDir() {
22
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
23
+ const devPath = join(thisDir, "charsets");
24
+ if (existsSync(devPath)) {
25
+ return devPath;
26
+ }
27
+ return join(thisDir, "..", "src", "charsets");
28
+ }
29
+ function loadCharset(nameOrPath) {
30
+ if (nameOrPath.includes(sep) || nameOrPath.includes("/") || nameOrPath.endsWith(".json")) {
31
+ if (!existsSync(nameOrPath)) {
32
+ throw new Error(`Charset file not found: ${nameOrPath}`);
33
+ }
34
+ return JSON.parse(readFileSync(nameOrPath, "utf-8"));
84
35
  }
85
- for (const [start, end] of ALL_RANGES) {
86
- if (code >= start && code <= end) {
87
- return true;
36
+ const filePath = join(getCharsetsDir(), `${nameOrPath}.json`);
37
+ if (!existsSync(filePath)) {
38
+ throw new Error(`Unknown charset: ${nameOrPath}`);
39
+ }
40
+ return JSON.parse(readFileSync(filePath, "utf-8"));
41
+ }
42
+ function parseRanges(entries) {
43
+ return entries.map((e) => [parseInt(e.start, 16), parseInt(e.end, 16)]);
44
+ }
45
+ function parseDangerous(entries) {
46
+ const map = /* @__PURE__ */ new Map();
47
+ for (const e of entries) {
48
+ map.set(parseInt(e.code, 16), e.name);
49
+ }
50
+ return map;
51
+ }
52
+ function resolveCharsets(charsetNames, charsetFiles) {
53
+ const base = loadCharset("base");
54
+ const rangesSet = /* @__PURE__ */ new Map();
55
+ const dangerous = parseDangerous(base.dangerous ?? []);
56
+ function addRanges(entries) {
57
+ for (const [s, e] of parseRanges(entries)) {
58
+ rangesSet.set(`${s}-${e}`, [s, e]);
59
+ }
60
+ }
61
+ addRanges(base.emoji_ranges ?? []);
62
+ addRanges(base.extra_ranges ?? []);
63
+ for (const name of charsetNames) {
64
+ const data = loadCharset(name);
65
+ addRanges(data.emoji_ranges ?? []);
66
+ addRanges(data.extra_ranges ?? []);
67
+ if (data.dangerous) {
68
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
69
+ dangerous.set(code, dname);
70
+ }
88
71
  }
89
72
  }
73
+ for (const path2 of charsetFiles) {
74
+ const data = loadCharset(path2);
75
+ addRanges(data.emoji_ranges ?? []);
76
+ addRanges(data.extra_ranges ?? []);
77
+ if (data.dangerous) {
78
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
79
+ dangerous.set(code, dname);
80
+ }
81
+ }
82
+ }
83
+ const ranges = [...rangesSet.values()].sort((a, b) => a[0] - b[0]);
84
+ return { ranges, dangerous };
85
+ }
86
+ var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
87
+ var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
88
+ function isInRanges(code, ranges) {
89
+ if (code <= 127) return true;
90
+ for (const [start, end] of ranges) {
91
+ if (code >= start && code <= end) return true;
92
+ }
90
93
  return false;
91
94
  }
92
- function isDangerousChar(c) {
93
- return DANGEROUS_CODEPOINTS.has(c.codePointAt(0));
95
+ var _baseRanges = null;
96
+ var _baseDangerous = null;
97
+ function getBaseDefaults() {
98
+ if (!_baseRanges || !_baseDangerous) {
99
+ const defaults = resolveCharsets([], []);
100
+ _baseRanges = defaults.ranges;
101
+ _baseDangerous = defaults.dangerous;
102
+ }
103
+ return { ranges: _baseRanges, dangerous: _baseDangerous };
94
104
  }
95
105
  function computeCommentMask(content, suffix) {
96
106
  if (PYTHON_SUFFIXES.has(suffix)) {
@@ -209,8 +219,13 @@ function computeCommentMaskJs(content) {
209
219
  }
210
220
  return mask;
211
221
  }
212
- function checkFile(filePath, maxProblems = 5) {
222
+ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
213
223
  const problems = [];
224
+ if (!extraRanges || !dangerousMap) {
225
+ const defaults = getBaseDefaults();
226
+ extraRanges ??= defaults.ranges;
227
+ dangerousMap ??= defaults.dangerous;
228
+ }
214
229
  try {
215
230
  const content = readFileSync(filePath, "utf-8");
216
231
  const suffix = extname(filePath).toLowerCase();
@@ -220,15 +235,15 @@ function checkFile(filePath, maxProblems = 5) {
220
235
  for (const char of content) {
221
236
  position++;
222
237
  const code = char.codePointAt(0);
223
- if (isDangerousChar(char)) {
238
+ if (dangerousMap.has(code)) {
224
239
  if (!commentMask.has(offset)) {
225
- const name = DANGEROUS_CODEPOINTS.get(code);
240
+ const name = dangerousMap.get(code);
226
241
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
227
242
  problems.push(
228
243
  `Dangerous character in code at position ${position}: U+${hex} (${name})`
229
244
  );
230
245
  }
231
- } else if (!isAllowedChar(char)) {
246
+ } else if (!isInRanges(code, extraRanges)) {
232
247
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
233
248
  problems.push(
234
249
  `Illegal character at position ${position}: ${JSON.stringify(char)} (U+${hex})`
@@ -239,15 +254,15 @@ function checkFile(filePath, maxProblems = 5) {
239
254
  }
240
255
  offset += char.length;
241
256
  }
242
- } catch {
243
- problems.push(`Failed to read file: ${filePath}`);
257
+ } catch (e) {
258
+ problems.push(`Failed to read file: ${filePath} (${e})`);
244
259
  }
245
260
  return problems;
246
261
  }
247
- function checkPaths(paths) {
262
+ function checkPaths(paths, extraRanges, dangerousMap) {
248
263
  let hasError = false;
249
264
  for (const path2 of paths) {
250
- const problems = checkFile(path2);
265
+ const problems = checkFile(path2, 5, extraRanges, dangerousMap);
251
266
  if (problems.length > 0) {
252
267
  hasError = true;
253
268
  console.log(`
@@ -262,7 +277,7 @@ ${path2} contains illegal characters:`);
262
277
 
263
278
  // src/check-imports.ts
264
279
  import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
265
- import { join, relative, sep, extname as extname2, basename } from "path";
280
+ import { join as join2, relative, sep as sep2, extname as extname2, basename } from "path";
266
281
  import ts from "typescript";
267
282
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
268
283
  ".ts",
@@ -282,7 +297,7 @@ var INDEX_BASENAMES = /* @__PURE__ */ new Set([
282
297
  ]);
283
298
  function fileToModule(filePath, srcDir) {
284
299
  const rel = relative(srcDir, filePath);
285
- const parts = rel.split(sep);
300
+ const parts = rel.split(sep2);
286
301
  const last = parts[parts.length - 1];
287
302
  if (last === "index.ts" || last === "index.js" || last === "index.tsx" || last === "index.jsx") {
288
303
  parts.pop();
@@ -355,7 +370,7 @@ function findSourceFiles(dir) {
355
370
  if (entry === "node_modules" || entry === "dist" || entry === ".git") {
356
371
  continue;
357
372
  }
358
- const full = join(d, entry);
373
+ const full = join2(d, entry);
359
374
  const stat = statSync(full);
360
375
  if (stat.isDirectory()) {
361
376
  walk(full);
@@ -472,10 +487,10 @@ Found ${cycles.length} circular import(s):
472
487
 
473
488
  // src/config.ts
474
489
  import { readFileSync as readFileSync3 } from "fs";
475
- import { join as join2 } from "path";
490
+ import { join as join3 } from "path";
476
491
  function loadConfig(projectDir) {
477
492
  const dir = projectDir ?? process.cwd();
478
- const pkgPath = join2(dir, "package.json");
493
+ const pkgPath = join3(dir, "package.json");
479
494
  let raw;
480
495
  try {
481
496
  raw = readFileSync3(pkgPath, "utf-8");
@@ -491,9 +506,12 @@ function loadConfig(projectDir) {
491
506
  }
492
507
 
493
508
  // src/cli.ts
509
+ function collect(value, previous) {
510
+ return [...previous, value];
511
+ }
494
512
  function getVersion() {
495
- const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
496
- const pkgPath = join3(thisDir, "..", "package.json");
513
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
514
+ const pkgPath = join4(thisDir, "..", "package.json");
497
515
  try {
498
516
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
499
517
  return pkg.version ?? "0.0.0";
@@ -502,15 +520,32 @@ function getVersion() {
502
520
  }
503
521
  }
504
522
  function getReleaseScript() {
505
- const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
506
- return join3(thisDir, "..", "release.sh");
523
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
524
+ return join4(thisDir, "..", "release.sh");
507
525
  }
508
526
  function buildProgram() {
509
527
  const program2 = new Command();
510
528
  program2.name("apdev").description("Shared development tools for TypeScript/JavaScript projects").version(getVersion());
511
- program2.command("check-chars").description("Validate files contain only allowed characters").argument("<files...>", "Files to check").action((files) => {
529
+ program2.command("check-chars").description("Validate files contain only allowed characters").argument("<files...>", "Files to check").option("--charset <name>", "Extra charset preset (repeatable)", collect, []).option("--charset-file <path>", "Custom charset JSON file (repeatable)", collect, []).action((files, opts) => {
530
+ let charsetNames = opts.charset;
531
+ let charsetFiles = opts.charsetFile;
532
+ if (charsetNames.length === 0 && charsetFiles.length === 0) {
533
+ const envVal = process.env.APDEV_EXTRA_CHARS ?? "";
534
+ if (envVal) {
535
+ for (const item of envVal.split(",")) {
536
+ const trimmed = item.trim();
537
+ if (!trimmed) continue;
538
+ if (trimmed.includes("/") || trimmed.includes(sep3) || trimmed.endsWith(".json")) {
539
+ charsetFiles.push(trimmed);
540
+ } else {
541
+ charsetNames.push(trimmed);
542
+ }
543
+ }
544
+ }
545
+ }
546
+ const { ranges, dangerous } = resolveCharsets(charsetNames, charsetFiles);
512
547
  const resolved = files.map((f) => resolve(f));
513
- const code = checkPaths(resolved);
548
+ const code = checkPaths(resolved, ranges, dangerous);
514
549
  process.exit(code);
515
550
  });
516
551
  program2.command("check-imports").description("Detect circular imports in a JS/TS package").option("--package <name>", "Base package name (e.g. mylib). Reads from package.json apdev config if omitted.").option("--src-dir <dir>", "Source directory containing the package (default: src)").action((opts) => {
@@ -526,7 +561,7 @@ function buildProgram() {
526
561
  const code = checkCircularImports(resolve(srcDir), basePackage);
527
562
  process.exit(code);
528
563
  });
529
- program2.command("release").description("Interactive release automation (build, tag, GitHub release, npm publish)").option("--yes, -y", "Auto-accept all defaults (silent mode)").argument("[version]", "Version to release (auto-detected from package.json if omitted)").action((version, opts) => {
564
+ program2.command("release").description("Interactive release automation (build, tag, GitHub release, npm publish)").option("-y, --yes", "Auto-accept all defaults (silent mode)").argument("[version]", "Version to release (auto-detected from package.json if omitted)").action((version, opts) => {
530
565
  const script = getReleaseScript();
531
566
  try {
532
567
  readFileSync4(script);
package/dist/index.cjs CHANGED
@@ -37,7 +37,10 @@ __export(src_exports, {
37
37
  fileToModule: () => fileToModule,
38
38
  findCycles: () => findCycles,
39
39
  isAllowedChar: () => isAllowedChar,
40
+ isDangerousChar: () => isDangerousChar,
41
+ loadCharset: () => loadCharset,
40
42
  loadConfig: () => loadConfig,
43
+ resolveCharsets: () => resolveCharsets,
41
44
  version: () => version
42
45
  });
43
46
  module.exports = __toCommonJS(src_exports);
@@ -49,80 +52,100 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
49
52
  // src/check-chars.ts
50
53
  var import_node_fs = require("fs");
51
54
  var import_node_path = require("path");
52
- var EMOJI_RANGES = [
53
- [127744, 128511],
54
- // Symbols and Pictographs
55
- [128512, 128591],
56
- // Emoticons
57
- [128640, 128767],
58
- // Transport and Map Symbols
59
- [128896, 129023],
60
- // Geometric Shapes Extended
61
- [129280, 129535],
62
- // Supplemental Symbols and Pictographs
63
- [9728, 9983],
64
- // Miscellaneous Symbols
65
- [9984, 10175]
66
- // Dingbats
67
- ];
68
- var EXTRA_ALLOWED_RANGES = [
69
- [128, 255],
70
- // Latin-1 Supplement
71
- [8192, 8303],
72
- // General Punctuation
73
- [8448, 8527],
74
- // Letterlike Symbols
75
- [8592, 8703],
76
- // Arrows
77
- [8704, 8959],
78
- // Mathematical Operators
79
- [8960, 9215],
80
- // Miscellaneous Technical
81
- [9472, 9599],
82
- // Box Drawing
83
- [9632, 9727],
84
- // Geometric Shapes
85
- [11008, 11263],
86
- // Miscellaneous Symbols and Arrows
87
- [65024, 65039]
88
- // Variation Selectors
89
- ];
90
- var ALL_RANGES = [...EMOJI_RANGES, ...EXTRA_ALLOWED_RANGES];
91
- var DANGEROUS_CODEPOINTS = /* @__PURE__ */ new Map([
92
- // Bidi control characters (Trojan Source - CVE-2021-42574)
93
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
94
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
95
- [8236, "POP DIRECTIONAL FORMATTING"],
96
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
97
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
98
- [8294, "LEFT-TO-RIGHT ISOLATE"],
99
- [8295, "RIGHT-TO-LEFT ISOLATE"],
100
- [8296, "FIRST STRONG ISOLATE"],
101
- [8297, "POP DIRECTIONAL ISOLATE"],
102
- // Zero-width characters
103
- [8203, "ZERO WIDTH SPACE"],
104
- [8204, "ZERO WIDTH NON-JOINER"],
105
- [8205, "ZERO WIDTH JOINER"],
106
- [8206, "LEFT-TO-RIGHT MARK"],
107
- [8207, "RIGHT-TO-LEFT MARK"],
108
- [8288, "WORD JOINER"]
109
- ]);
110
- var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
111
- var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
112
- function isAllowedChar(c) {
113
- const code = c.codePointAt(0);
114
- if (code <= 127) {
115
- return true;
55
+ var import_node_url = require("url");
56
+ function getCharsetsDir() {
57
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
58
+ const devPath = (0, import_node_path.join)(thisDir, "charsets");
59
+ if ((0, import_node_fs.existsSync)(devPath)) {
60
+ return devPath;
61
+ }
62
+ return (0, import_node_path.join)(thisDir, "..", "src", "charsets");
63
+ }
64
+ function loadCharset(nameOrPath) {
65
+ if (nameOrPath.includes(import_node_path.sep) || nameOrPath.includes("/") || nameOrPath.endsWith(".json")) {
66
+ if (!(0, import_node_fs.existsSync)(nameOrPath)) {
67
+ throw new Error(`Charset file not found: ${nameOrPath}`);
68
+ }
69
+ return JSON.parse((0, import_node_fs.readFileSync)(nameOrPath, "utf-8"));
70
+ }
71
+ const filePath = (0, import_node_path.join)(getCharsetsDir(), `${nameOrPath}.json`);
72
+ if (!(0, import_node_fs.existsSync)(filePath)) {
73
+ throw new Error(`Unknown charset: ${nameOrPath}`);
74
+ }
75
+ return JSON.parse((0, import_node_fs.readFileSync)(filePath, "utf-8"));
76
+ }
77
+ function parseRanges(entries) {
78
+ return entries.map((e) => [parseInt(e.start, 16), parseInt(e.end, 16)]);
79
+ }
80
+ function parseDangerous(entries) {
81
+ const map = /* @__PURE__ */ new Map();
82
+ for (const e of entries) {
83
+ map.set(parseInt(e.code, 16), e.name);
84
+ }
85
+ return map;
86
+ }
87
+ function resolveCharsets(charsetNames, charsetFiles) {
88
+ const base = loadCharset("base");
89
+ const rangesSet = /* @__PURE__ */ new Map();
90
+ const dangerous = parseDangerous(base.dangerous ?? []);
91
+ function addRanges(entries) {
92
+ for (const [s, e] of parseRanges(entries)) {
93
+ rangesSet.set(`${s}-${e}`, [s, e]);
94
+ }
116
95
  }
117
- for (const [start, end] of ALL_RANGES) {
118
- if (code >= start && code <= end) {
119
- return true;
96
+ addRanges(base.emoji_ranges ?? []);
97
+ addRanges(base.extra_ranges ?? []);
98
+ for (const name of charsetNames) {
99
+ const data = loadCharset(name);
100
+ addRanges(data.emoji_ranges ?? []);
101
+ addRanges(data.extra_ranges ?? []);
102
+ if (data.dangerous) {
103
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
104
+ dangerous.set(code, dname);
105
+ }
106
+ }
107
+ }
108
+ for (const path of charsetFiles) {
109
+ const data = loadCharset(path);
110
+ addRanges(data.emoji_ranges ?? []);
111
+ addRanges(data.extra_ranges ?? []);
112
+ if (data.dangerous) {
113
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
114
+ dangerous.set(code, dname);
115
+ }
120
116
  }
121
117
  }
118
+ const ranges = [...rangesSet.values()].sort((a, b) => a[0] - b[0]);
119
+ return { ranges, dangerous };
120
+ }
121
+ var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
122
+ var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
123
+ function isInRanges(code, ranges) {
124
+ if (code <= 127) return true;
125
+ for (const [start, end] of ranges) {
126
+ if (code >= start && code <= end) return true;
127
+ }
122
128
  return false;
123
129
  }
130
+ var _baseRanges = null;
131
+ var _baseDangerous = null;
132
+ function getBaseDefaults() {
133
+ if (!_baseRanges || !_baseDangerous) {
134
+ const defaults = resolveCharsets([], []);
135
+ _baseRanges = defaults.ranges;
136
+ _baseDangerous = defaults.dangerous;
137
+ }
138
+ return { ranges: _baseRanges, dangerous: _baseDangerous };
139
+ }
140
+ function isAllowedChar(c) {
141
+ const { ranges, dangerous } = getBaseDefaults();
142
+ const code = c.codePointAt(0);
143
+ if (dangerous.has(code)) return false;
144
+ return isInRanges(code, ranges);
145
+ }
124
146
  function isDangerousChar(c) {
125
- return DANGEROUS_CODEPOINTS.has(c.codePointAt(0));
147
+ const { dangerous } = getBaseDefaults();
148
+ return dangerous.has(c.codePointAt(0));
126
149
  }
127
150
  function computeCommentMask(content, suffix) {
128
151
  if (PYTHON_SUFFIXES.has(suffix)) {
@@ -241,8 +264,13 @@ function computeCommentMaskJs(content) {
241
264
  }
242
265
  return mask;
243
266
  }
244
- function checkFile(filePath, maxProblems = 5) {
267
+ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
245
268
  const problems = [];
269
+ if (!extraRanges || !dangerousMap) {
270
+ const defaults = getBaseDefaults();
271
+ extraRanges ??= defaults.ranges;
272
+ dangerousMap ??= defaults.dangerous;
273
+ }
246
274
  try {
247
275
  const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
248
276
  const suffix = (0, import_node_path.extname)(filePath).toLowerCase();
@@ -252,15 +280,15 @@ function checkFile(filePath, maxProblems = 5) {
252
280
  for (const char of content) {
253
281
  position++;
254
282
  const code = char.codePointAt(0);
255
- if (isDangerousChar(char)) {
283
+ if (dangerousMap.has(code)) {
256
284
  if (!commentMask.has(offset)) {
257
- const name = DANGEROUS_CODEPOINTS.get(code);
285
+ const name = dangerousMap.get(code);
258
286
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
259
287
  problems.push(
260
288
  `Dangerous character in code at position ${position}: U+${hex} (${name})`
261
289
  );
262
290
  }
263
- } else if (!isAllowedChar(char)) {
291
+ } else if (!isInRanges(code, extraRanges)) {
264
292
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
265
293
  problems.push(
266
294
  `Illegal character at position ${position}: ${JSON.stringify(char)} (U+${hex})`
@@ -271,15 +299,15 @@ function checkFile(filePath, maxProblems = 5) {
271
299
  }
272
300
  offset += char.length;
273
301
  }
274
- } catch {
275
- problems.push(`Failed to read file: ${filePath}`);
302
+ } catch (e) {
303
+ problems.push(`Failed to read file: ${filePath} (${e})`);
276
304
  }
277
305
  return problems;
278
306
  }
279
- function checkPaths(paths) {
307
+ function checkPaths(paths, extraRanges, dangerousMap) {
280
308
  let hasError = false;
281
309
  for (const path of paths) {
282
- const problems = checkFile(path);
310
+ const problems = checkFile(path, 5, extraRanges, dangerousMap);
283
311
  if (problems.length > 0) {
284
312
  hasError = true;
285
313
  console.log(`
@@ -525,9 +553,9 @@ function loadConfig(projectDir) {
525
553
  // src/index.ts
526
554
  var import_node_fs4 = require("fs");
527
555
  var import_node_path4 = require("path");
528
- var import_node_url = require("url");
556
+ var import_node_url2 = require("url");
529
557
  function readVersion() {
530
- const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
558
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path4.dirname)((0, import_node_url2.fileURLToPath)(importMetaUrl));
531
559
  try {
532
560
  const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path4.join)(thisDir, "..", "package.json"), "utf-8"));
533
561
  return pkg.version ?? "0.0.0";
@@ -545,6 +573,9 @@ var version = readVersion();
545
573
  fileToModule,
546
574
  findCycles,
547
575
  isAllowedChar,
576
+ isDangerousChar,
577
+ loadCharset,
548
578
  loadConfig,
579
+ resolveCharsets,
549
580
  version
550
581
  });
package/dist/index.d.cts CHANGED
@@ -1,14 +1,51 @@
1
- /** Return true if the character is in the allowed set. */
1
+ /**
2
+ * Character validation tool.
3
+ *
4
+ * Checks that files contain only allowed characters: ASCII, common emoji,
5
+ * and standard technical symbols (arrows, box-drawing, math operators, etc.).
6
+ *
7
+ * Additionally flags dangerous invisible/bidi characters in code regions
8
+ * (Trojan Source - CVE-2021-42574) while allowing them in comments.
9
+ */
10
+ interface RangeEntry {
11
+ start: string;
12
+ end: string;
13
+ name: string;
14
+ }
15
+ interface DangerousEntry {
16
+ code: string;
17
+ name: string;
18
+ }
19
+ interface CharsetData {
20
+ name: string;
21
+ description?: string;
22
+ emoji_ranges?: RangeEntry[];
23
+ extra_ranges?: RangeEntry[];
24
+ dangerous?: DangerousEntry[];
25
+ }
26
+ declare function loadCharset(nameOrPath: string): CharsetData;
27
+ declare function resolveCharsets(charsetNames: string[], charsetFiles: string[]): {
28
+ ranges: [number, number][];
29
+ dangerous: Map<number, string>;
30
+ };
31
+ /**
32
+ * Return true if the character is in the base allowed set.
33
+ *
34
+ * Dangerous codepoints (Trojan Source vectors) are excluded even though
35
+ * they fall within allowed Unicode ranges.
36
+ */
2
37
  declare function isAllowedChar(c: string): boolean;
38
+ /** Return true if the character is a dangerous invisible/bidi codepoint. */
39
+ declare function isDangerousChar(c: string): boolean;
3
40
  /**
4
41
  * Check a single file for illegal characters.
5
42
  * Returns a list of problem descriptions (empty if the file is clean).
6
43
  */
7
- declare function checkFile(filePath: string, maxProblems?: number): string[];
44
+ declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
8
45
  /**
9
46
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
10
47
  */
11
- declare function checkPaths(paths: string[]): number;
48
+ declare function checkPaths(paths: string[], extraRanges?: [number, number][], dangerousMap?: Map<number, string>): number;
12
49
 
13
50
  /**
14
51
  * Circular import detection tool.
@@ -38,4 +75,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
38
75
 
39
76
  declare const version: string;
40
77
 
41
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, loadConfig, version };
78
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, version };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,51 @@
1
- /** Return true if the character is in the allowed set. */
1
+ /**
2
+ * Character validation tool.
3
+ *
4
+ * Checks that files contain only allowed characters: ASCII, common emoji,
5
+ * and standard technical symbols (arrows, box-drawing, math operators, etc.).
6
+ *
7
+ * Additionally flags dangerous invisible/bidi characters in code regions
8
+ * (Trojan Source - CVE-2021-42574) while allowing them in comments.
9
+ */
10
+ interface RangeEntry {
11
+ start: string;
12
+ end: string;
13
+ name: string;
14
+ }
15
+ interface DangerousEntry {
16
+ code: string;
17
+ name: string;
18
+ }
19
+ interface CharsetData {
20
+ name: string;
21
+ description?: string;
22
+ emoji_ranges?: RangeEntry[];
23
+ extra_ranges?: RangeEntry[];
24
+ dangerous?: DangerousEntry[];
25
+ }
26
+ declare function loadCharset(nameOrPath: string): CharsetData;
27
+ declare function resolveCharsets(charsetNames: string[], charsetFiles: string[]): {
28
+ ranges: [number, number][];
29
+ dangerous: Map<number, string>;
30
+ };
31
+ /**
32
+ * Return true if the character is in the base allowed set.
33
+ *
34
+ * Dangerous codepoints (Trojan Source vectors) are excluded even though
35
+ * they fall within allowed Unicode ranges.
36
+ */
2
37
  declare function isAllowedChar(c: string): boolean;
38
+ /** Return true if the character is a dangerous invisible/bidi codepoint. */
39
+ declare function isDangerousChar(c: string): boolean;
3
40
  /**
4
41
  * Check a single file for illegal characters.
5
42
  * Returns a list of problem descriptions (empty if the file is clean).
6
43
  */
7
- declare function checkFile(filePath: string, maxProblems?: number): string[];
44
+ declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
8
45
  /**
9
46
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
10
47
  */
11
- declare function checkPaths(paths: string[]): number;
48
+ declare function checkPaths(paths: string[], extraRanges?: [number, number][], dangerousMap?: Map<number, string>): number;
12
49
 
13
50
  /**
14
51
  * Circular import detection tool.
@@ -38,4 +75,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
38
75
 
39
76
  declare const version: string;
40
77
 
41
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, loadConfig, version };
78
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, version };
package/dist/index.js CHANGED
@@ -6,82 +6,102 @@ var getDirname = () => path.dirname(getFilename());
6
6
  var __dirname = /* @__PURE__ */ getDirname();
7
7
 
8
8
  // src/check-chars.ts
9
- import { readFileSync } from "fs";
10
- import { extname } from "path";
11
- var EMOJI_RANGES = [
12
- [127744, 128511],
13
- // Symbols and Pictographs
14
- [128512, 128591],
15
- // Emoticons
16
- [128640, 128767],
17
- // Transport and Map Symbols
18
- [128896, 129023],
19
- // Geometric Shapes Extended
20
- [129280, 129535],
21
- // Supplemental Symbols and Pictographs
22
- [9728, 9983],
23
- // Miscellaneous Symbols
24
- [9984, 10175]
25
- // Dingbats
26
- ];
27
- var EXTRA_ALLOWED_RANGES = [
28
- [128, 255],
29
- // Latin-1 Supplement
30
- [8192, 8303],
31
- // General Punctuation
32
- [8448, 8527],
33
- // Letterlike Symbols
34
- [8592, 8703],
35
- // Arrows
36
- [8704, 8959],
37
- // Mathematical Operators
38
- [8960, 9215],
39
- // Miscellaneous Technical
40
- [9472, 9599],
41
- // Box Drawing
42
- [9632, 9727],
43
- // Geometric Shapes
44
- [11008, 11263],
45
- // Miscellaneous Symbols and Arrows
46
- [65024, 65039]
47
- // Variation Selectors
48
- ];
49
- var ALL_RANGES = [...EMOJI_RANGES, ...EXTRA_ALLOWED_RANGES];
50
- var DANGEROUS_CODEPOINTS = /* @__PURE__ */ new Map([
51
- // Bidi control characters (Trojan Source - CVE-2021-42574)
52
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
53
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
54
- [8236, "POP DIRECTIONAL FORMATTING"],
55
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
56
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
57
- [8294, "LEFT-TO-RIGHT ISOLATE"],
58
- [8295, "RIGHT-TO-LEFT ISOLATE"],
59
- [8296, "FIRST STRONG ISOLATE"],
60
- [8297, "POP DIRECTIONAL ISOLATE"],
61
- // Zero-width characters
62
- [8203, "ZERO WIDTH SPACE"],
63
- [8204, "ZERO WIDTH NON-JOINER"],
64
- [8205, "ZERO WIDTH JOINER"],
65
- [8206, "LEFT-TO-RIGHT MARK"],
66
- [8207, "RIGHT-TO-LEFT MARK"],
67
- [8288, "WORD JOINER"]
68
- ]);
69
- var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
70
- var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
71
- function isAllowedChar(c) {
72
- const code = c.codePointAt(0);
73
- if (code <= 127) {
74
- return true;
9
+ import { readFileSync, existsSync } from "fs";
10
+ import { extname, dirname, join, sep } from "path";
11
+ import { fileURLToPath as fileURLToPath2 } from "url";
12
+ function getCharsetsDir() {
13
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
14
+ const devPath = join(thisDir, "charsets");
15
+ if (existsSync(devPath)) {
16
+ return devPath;
17
+ }
18
+ return join(thisDir, "..", "src", "charsets");
19
+ }
20
+ function loadCharset(nameOrPath) {
21
+ if (nameOrPath.includes(sep) || nameOrPath.includes("/") || nameOrPath.endsWith(".json")) {
22
+ if (!existsSync(nameOrPath)) {
23
+ throw new Error(`Charset file not found: ${nameOrPath}`);
24
+ }
25
+ return JSON.parse(readFileSync(nameOrPath, "utf-8"));
26
+ }
27
+ const filePath = join(getCharsetsDir(), `${nameOrPath}.json`);
28
+ if (!existsSync(filePath)) {
29
+ throw new Error(`Unknown charset: ${nameOrPath}`);
30
+ }
31
+ return JSON.parse(readFileSync(filePath, "utf-8"));
32
+ }
33
+ function parseRanges(entries) {
34
+ return entries.map((e) => [parseInt(e.start, 16), parseInt(e.end, 16)]);
35
+ }
36
+ function parseDangerous(entries) {
37
+ const map = /* @__PURE__ */ new Map();
38
+ for (const e of entries) {
39
+ map.set(parseInt(e.code, 16), e.name);
40
+ }
41
+ return map;
42
+ }
43
+ function resolveCharsets(charsetNames, charsetFiles) {
44
+ const base = loadCharset("base");
45
+ const rangesSet = /* @__PURE__ */ new Map();
46
+ const dangerous = parseDangerous(base.dangerous ?? []);
47
+ function addRanges(entries) {
48
+ for (const [s, e] of parseRanges(entries)) {
49
+ rangesSet.set(`${s}-${e}`, [s, e]);
50
+ }
75
51
  }
76
- for (const [start, end] of ALL_RANGES) {
77
- if (code >= start && code <= end) {
78
- return true;
52
+ addRanges(base.emoji_ranges ?? []);
53
+ addRanges(base.extra_ranges ?? []);
54
+ for (const name of charsetNames) {
55
+ const data = loadCharset(name);
56
+ addRanges(data.emoji_ranges ?? []);
57
+ addRanges(data.extra_ranges ?? []);
58
+ if (data.dangerous) {
59
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
60
+ dangerous.set(code, dname);
61
+ }
79
62
  }
80
63
  }
64
+ for (const path2 of charsetFiles) {
65
+ const data = loadCharset(path2);
66
+ addRanges(data.emoji_ranges ?? []);
67
+ addRanges(data.extra_ranges ?? []);
68
+ if (data.dangerous) {
69
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
70
+ dangerous.set(code, dname);
71
+ }
72
+ }
73
+ }
74
+ const ranges = [...rangesSet.values()].sort((a, b) => a[0] - b[0]);
75
+ return { ranges, dangerous };
76
+ }
77
+ var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
78
+ var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
79
+ function isInRanges(code, ranges) {
80
+ if (code <= 127) return true;
81
+ for (const [start, end] of ranges) {
82
+ if (code >= start && code <= end) return true;
83
+ }
81
84
  return false;
82
85
  }
86
+ var _baseRanges = null;
87
+ var _baseDangerous = null;
88
+ function getBaseDefaults() {
89
+ if (!_baseRanges || !_baseDangerous) {
90
+ const defaults = resolveCharsets([], []);
91
+ _baseRanges = defaults.ranges;
92
+ _baseDangerous = defaults.dangerous;
93
+ }
94
+ return { ranges: _baseRanges, dangerous: _baseDangerous };
95
+ }
96
+ function isAllowedChar(c) {
97
+ const { ranges, dangerous } = getBaseDefaults();
98
+ const code = c.codePointAt(0);
99
+ if (dangerous.has(code)) return false;
100
+ return isInRanges(code, ranges);
101
+ }
83
102
  function isDangerousChar(c) {
84
- return DANGEROUS_CODEPOINTS.has(c.codePointAt(0));
103
+ const { dangerous } = getBaseDefaults();
104
+ return dangerous.has(c.codePointAt(0));
85
105
  }
86
106
  function computeCommentMask(content, suffix) {
87
107
  if (PYTHON_SUFFIXES.has(suffix)) {
@@ -200,8 +220,13 @@ function computeCommentMaskJs(content) {
200
220
  }
201
221
  return mask;
202
222
  }
203
- function checkFile(filePath, maxProblems = 5) {
223
+ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
204
224
  const problems = [];
225
+ if (!extraRanges || !dangerousMap) {
226
+ const defaults = getBaseDefaults();
227
+ extraRanges ??= defaults.ranges;
228
+ dangerousMap ??= defaults.dangerous;
229
+ }
205
230
  try {
206
231
  const content = readFileSync(filePath, "utf-8");
207
232
  const suffix = extname(filePath).toLowerCase();
@@ -211,15 +236,15 @@ function checkFile(filePath, maxProblems = 5) {
211
236
  for (const char of content) {
212
237
  position++;
213
238
  const code = char.codePointAt(0);
214
- if (isDangerousChar(char)) {
239
+ if (dangerousMap.has(code)) {
215
240
  if (!commentMask.has(offset)) {
216
- const name = DANGEROUS_CODEPOINTS.get(code);
241
+ const name = dangerousMap.get(code);
217
242
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
218
243
  problems.push(
219
244
  `Dangerous character in code at position ${position}: U+${hex} (${name})`
220
245
  );
221
246
  }
222
- } else if (!isAllowedChar(char)) {
247
+ } else if (!isInRanges(code, extraRanges)) {
223
248
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
224
249
  problems.push(
225
250
  `Illegal character at position ${position}: ${JSON.stringify(char)} (U+${hex})`
@@ -230,15 +255,15 @@ function checkFile(filePath, maxProblems = 5) {
230
255
  }
231
256
  offset += char.length;
232
257
  }
233
- } catch {
234
- problems.push(`Failed to read file: ${filePath}`);
258
+ } catch (e) {
259
+ problems.push(`Failed to read file: ${filePath} (${e})`);
235
260
  }
236
261
  return problems;
237
262
  }
238
- function checkPaths(paths) {
263
+ function checkPaths(paths, extraRanges, dangerousMap) {
239
264
  let hasError = false;
240
265
  for (const path2 of paths) {
241
- const problems = checkFile(path2);
266
+ const problems = checkFile(path2, 5, extraRanges, dangerousMap);
242
267
  if (problems.length > 0) {
243
268
  hasError = true;
244
269
  console.log(`
@@ -253,7 +278,7 @@ ${path2} contains illegal characters:`);
253
278
 
254
279
  // src/check-imports.ts
255
280
  import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
256
- import { join, relative, sep, extname as extname2, basename } from "path";
281
+ import { join as join2, relative, sep as sep2, extname as extname2, basename } from "path";
257
282
  import ts from "typescript";
258
283
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
259
284
  ".ts",
@@ -273,7 +298,7 @@ var INDEX_BASENAMES = /* @__PURE__ */ new Set([
273
298
  ]);
274
299
  function fileToModule(filePath, srcDir) {
275
300
  const rel = relative(srcDir, filePath);
276
- const parts = rel.split(sep);
301
+ const parts = rel.split(sep2);
277
302
  const last = parts[parts.length - 1];
278
303
  if (last === "index.ts" || last === "index.js" || last === "index.tsx" || last === "index.jsx") {
279
304
  parts.pop();
@@ -346,7 +371,7 @@ function findSourceFiles(dir) {
346
371
  if (entry === "node_modules" || entry === "dist" || entry === ".git") {
347
372
  continue;
348
373
  }
349
- const full = join(d, entry);
374
+ const full = join2(d, entry);
350
375
  const stat = statSync(full);
351
376
  if (stat.isDirectory()) {
352
377
  walk(full);
@@ -463,10 +488,10 @@ Found ${cycles.length} circular import(s):
463
488
 
464
489
  // src/config.ts
465
490
  import { readFileSync as readFileSync3 } from "fs";
466
- import { join as join2 } from "path";
491
+ import { join as join3 } from "path";
467
492
  function loadConfig(projectDir) {
468
493
  const dir = projectDir ?? process.cwd();
469
- const pkgPath = join2(dir, "package.json");
494
+ const pkgPath = join3(dir, "package.json");
470
495
  let raw;
471
496
  try {
472
497
  raw = readFileSync3(pkgPath, "utf-8");
@@ -483,12 +508,12 @@ function loadConfig(projectDir) {
483
508
 
484
509
  // src/index.ts
485
510
  import { readFileSync as readFileSync4 } from "fs";
486
- import { dirname, join as join3 } from "path";
487
- import { fileURLToPath as fileURLToPath2 } from "url";
511
+ import { dirname as dirname2, join as join4 } from "path";
512
+ import { fileURLToPath as fileURLToPath3 } from "url";
488
513
  function readVersion() {
489
- const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
514
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
490
515
  try {
491
- const pkg = JSON.parse(readFileSync4(join3(thisDir, "..", "package.json"), "utf-8"));
516
+ const pkg = JSON.parse(readFileSync4(join4(thisDir, "..", "package.json"), "utf-8"));
492
517
  return pkg.version ?? "0.0.0";
493
518
  } catch {
494
519
  return "0.0.0";
@@ -503,6 +528,9 @@ export {
503
528
  fileToModule,
504
529
  findCycles,
505
530
  isAllowedChar,
531
+ isDangerousChar,
532
+ loadCharset,
506
533
  loadConfig,
534
+ resolveCharsets,
507
535
  version
508
536
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apdev-js",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -14,11 +14,12 @@
14
14
  }
15
15
  },
16
16
  "bin": {
17
- "apdev": "./dist/cli.js"
17
+ "apdev-js": "./dist/cli.js"
18
18
  },
19
19
  "files": [
20
20
  "dist",
21
- "release.sh"
21
+ "release.sh",
22
+ "src/charsets"
22
23
  ],
23
24
  "scripts": {
24
25
  "build": "tsup",
@@ -46,12 +47,20 @@
46
47
  "url": "https://github.com/aipartnerup/apdev/issues"
47
48
  },
48
49
  "dependencies": {
49
- "commander": "^13.0.0",
50
- "typescript": "^5.7.0"
50
+ "commander": "^13.0.0"
51
+ },
52
+ "peerDependencies": {
53
+ "typescript": "^5.0.0"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "typescript": {
57
+ "optional": true
58
+ }
51
59
  },
52
60
  "devDependencies": {
53
61
  "@types/node": "^22.0.0",
54
62
  "tsup": "^8.0.0",
63
+ "typescript": "^5.7.0",
55
64
  "vitest": "^3.0.0"
56
65
  }
57
66
  }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "base",
3
+ "description": "Default: ASCII + emoji + technical symbols",
4
+ "emoji_ranges": [
5
+ { "start": "0x1F300", "end": "0x1F5FF", "name": "Symbols and Pictographs" },
6
+ { "start": "0x1F600", "end": "0x1F64F", "name": "Emoticons" },
7
+ { "start": "0x1F680", "end": "0x1F6FF", "name": "Transport and Map Symbols" },
8
+ { "start": "0x1F780", "end": "0x1F7FF", "name": "Geometric Shapes Extended" },
9
+ { "start": "0x1F900", "end": "0x1F9FF", "name": "Supplemental Symbols and Pictographs" },
10
+ { "start": "0x2600", "end": "0x26FF", "name": "Miscellaneous Symbols" },
11
+ { "start": "0x2700", "end": "0x27BF", "name": "Dingbats" }
12
+ ],
13
+ "extra_ranges": [
14
+ { "start": "0x0080", "end": "0x00FF", "name": "Latin-1 Supplement" },
15
+ { "start": "0x2000", "end": "0x206F", "name": "General Punctuation" },
16
+ { "start": "0x2100", "end": "0x214F", "name": "Letterlike Symbols" },
17
+ { "start": "0x2190", "end": "0x21FF", "name": "Arrows" },
18
+ { "start": "0x2200", "end": "0x22FF", "name": "Mathematical Operators" },
19
+ { "start": "0x2300", "end": "0x23FF", "name": "Miscellaneous Technical" },
20
+ { "start": "0x2500", "end": "0x257F", "name": "Box Drawing" },
21
+ { "start": "0x2580", "end": "0x259F", "name": "Block Elements" },
22
+ { "start": "0x25A0", "end": "0x25FF", "name": "Geometric Shapes" },
23
+ { "start": "0x2800", "end": "0x28FF", "name": "Braille Patterns" },
24
+ { "start": "0x2B00", "end": "0x2BFF", "name": "Miscellaneous Symbols and Arrows" },
25
+ { "start": "0xFE00", "end": "0xFE0F", "name": "Variation Selectors" }
26
+ ],
27
+ "dangerous": [
28
+ { "code": "0x202A", "name": "LEFT-TO-RIGHT EMBEDDING" },
29
+ { "code": "0x202B", "name": "RIGHT-TO-LEFT EMBEDDING" },
30
+ { "code": "0x202C", "name": "POP DIRECTIONAL FORMATTING" },
31
+ { "code": "0x202D", "name": "LEFT-TO-RIGHT OVERRIDE" },
32
+ { "code": "0x202E", "name": "RIGHT-TO-LEFT OVERRIDE" },
33
+ { "code": "0x2066", "name": "LEFT-TO-RIGHT ISOLATE" },
34
+ { "code": "0x2067", "name": "RIGHT-TO-LEFT ISOLATE" },
35
+ { "code": "0x2068", "name": "FIRST STRONG ISOLATE" },
36
+ { "code": "0x2069", "name": "POP DIRECTIONAL ISOLATE" },
37
+ { "code": "0x200B", "name": "ZERO WIDTH SPACE" },
38
+ { "code": "0x200C", "name": "ZERO WIDTH NON-JOINER" },
39
+ { "code": "0x200D", "name": "ZERO WIDTH JOINER" },
40
+ { "code": "0x200E", "name": "LEFT-TO-RIGHT MARK" },
41
+ { "code": "0x200F", "name": "RIGHT-TO-LEFT MARK" },
42
+ { "code": "0x2060", "name": "WORD JOINER" }
43
+ ]
44
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "chinese",
3
+ "description": "Chinese: CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
6
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
7
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
8
+ ]
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "japanese",
3
+ "description": "Japanese: Hiragana, Katakana, CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
6
+ { "start": "0x3040", "end": "0x309F", "name": "Hiragana" },
7
+ { "start": "0x30A0", "end": "0x30FF", "name": "Katakana" },
8
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
9
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "korean",
3
+ "description": "Korean: Hangul Jamo, Hangul Syllables, CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x1100", "end": "0x11FF", "name": "Hangul Jamo" },
6
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
7
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
8
+ { "start": "0xAC00", "end": "0xD7AF", "name": "Hangul Syllables" },
9
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
10
+ ]
11
+ }