apdev-js 0.2.0 → 0.2.2

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
@@ -1,6 +1,6 @@
1
1
  # apdev
2
2
 
3
- Shared development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more.
3
+ General-purpose development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more.
4
4
 
5
5
  ## Installation
6
6
 
package/dist/cli.js CHANGED
@@ -15,7 +15,7 @@ import { execFileSync } from "child_process";
15
15
  import { Command } from "commander";
16
16
 
17
17
  // src/check-chars.ts
18
- import { readFileSync, existsSync } from "fs";
18
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
19
19
  import { extname, dirname, join, sep } from "path";
20
20
  import { fileURLToPath as fileURLToPath2 } from "url";
21
21
  function getCharsetsDir() {
@@ -259,10 +259,164 @@ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
259
259
  }
260
260
  return problems;
261
261
  }
262
+ var SKIP_SUFFIXES = /* @__PURE__ */ new Set([
263
+ // Bytecode
264
+ ".pyc",
265
+ ".pyo",
266
+ // Images
267
+ ".png",
268
+ ".jpg",
269
+ ".jpeg",
270
+ ".gif",
271
+ ".bmp",
272
+ ".ico",
273
+ ".svg",
274
+ ".webp",
275
+ // Fonts
276
+ ".ttf",
277
+ ".otf",
278
+ ".woff",
279
+ ".woff2",
280
+ ".eot",
281
+ // Archives
282
+ ".zip",
283
+ ".tar",
284
+ ".gz",
285
+ ".bz2",
286
+ ".xz",
287
+ ".7z",
288
+ // Compiled / binary
289
+ ".so",
290
+ ".dylib",
291
+ ".dll",
292
+ ".exe",
293
+ ".o",
294
+ ".a",
295
+ ".whl",
296
+ ".egg",
297
+ // Media
298
+ ".mp3",
299
+ ".mp4",
300
+ ".wav",
301
+ ".avi",
302
+ ".mov",
303
+ ".flac",
304
+ ".ogg",
305
+ // Documents
306
+ ".pdf",
307
+ ".doc",
308
+ ".docx",
309
+ ".xls",
310
+ ".xlsx",
311
+ ".ppt",
312
+ ".pptx",
313
+ // Data
314
+ ".db",
315
+ ".sqlite",
316
+ ".sqlite3",
317
+ ".pickle",
318
+ ".pkl"
319
+ ]);
320
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
321
+ "__pycache__",
322
+ "node_modules",
323
+ ".git",
324
+ ".venv",
325
+ "venv",
326
+ ".tox",
327
+ ".mypy_cache",
328
+ ".pytest_cache",
329
+ ".ruff_cache",
330
+ "dist",
331
+ "build"
332
+ ]);
333
+ var DEFAULT_DIRS = ["src", "tests", "examples"];
334
+ var DEFAULT_GLOBS = ["*.md", "*.yml", "*.yaml", "*.json", ".gitignore"];
335
+ function walkDir(directory) {
336
+ const files = [];
337
+ let entries;
338
+ try {
339
+ entries = readdirSync(directory).sort();
340
+ } catch {
341
+ return files;
342
+ }
343
+ for (const name of entries) {
344
+ if (name.startsWith(".")) continue;
345
+ const fullPath = join(directory, name);
346
+ let stat;
347
+ try {
348
+ stat = statSync(fullPath);
349
+ } catch {
350
+ continue;
351
+ }
352
+ if (stat.isDirectory()) {
353
+ if (SKIP_DIRS.has(name) || name.endsWith(".egg-info")) continue;
354
+ files.push(...walkDir(fullPath));
355
+ } else if (stat.isFile()) {
356
+ if (SKIP_SUFFIXES.has(extname(name).toLowerCase())) continue;
357
+ files.push(fullPath);
358
+ }
359
+ }
360
+ return files;
361
+ }
362
+ function defaultProjectFiles() {
363
+ const cwd = process.cwd();
364
+ const files = [];
365
+ for (const dirname3 of DEFAULT_DIRS) {
366
+ const d = join(cwd, dirname3);
367
+ if (existsSync(d) && statSync(d).isDirectory()) {
368
+ files.push(...walkDir(d));
369
+ }
370
+ }
371
+ for (const pattern of DEFAULT_GLOBS) {
372
+ if (pattern.startsWith("*.")) {
373
+ const suffix = pattern.slice(1);
374
+ try {
375
+ for (const name of readdirSync(cwd).sort()) {
376
+ if (name.endsWith(suffix) && statSync(join(cwd, name)).isFile()) {
377
+ files.push(join(cwd, name));
378
+ }
379
+ }
380
+ } catch {
381
+ }
382
+ } else {
383
+ const fullPath = join(cwd, pattern);
384
+ if (existsSync(fullPath) && statSync(fullPath).isFile()) {
385
+ files.push(fullPath);
386
+ }
387
+ }
388
+ }
389
+ return files;
390
+ }
391
+ function resolvePaths(paths) {
392
+ if (paths.length === 0) {
393
+ return defaultProjectFiles();
394
+ }
395
+ const result = [];
396
+ for (const p of paths) {
397
+ try {
398
+ if (statSync(p).isDirectory()) {
399
+ result.push(...walkDir(p));
400
+ } else {
401
+ result.push(p);
402
+ }
403
+ } catch {
404
+ result.push(p);
405
+ }
406
+ }
407
+ return result;
408
+ }
262
409
  function checkPaths(paths, extraRanges, dangerousMap) {
410
+ const resolved = resolvePaths(paths);
411
+ if (resolved.length === 0) {
412
+ console.log("No files to check.");
413
+ return 0;
414
+ }
263
415
  let hasError = false;
264
- for (const path2 of paths) {
416
+ let checked = 0;
417
+ for (const path2 of resolved) {
265
418
  const problems = checkFile(path2, 5, extraRanges, dangerousMap);
419
+ checked++;
266
420
  if (problems.length > 0) {
267
421
  hasError = true;
268
422
  console.log(`
@@ -272,13 +426,27 @@ ${path2} contains illegal characters:`);
272
426
  }
273
427
  }
274
428
  }
429
+ if (!hasError) {
430
+ console.log(`All ${checked} files passed.`);
431
+ }
275
432
  return hasError ? 1 : 0;
276
433
  }
277
434
 
278
435
  // src/check-imports.ts
279
- import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
436
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
280
437
  import { join as join2, relative, sep as sep2, extname as extname2, basename } from "path";
281
- import ts from "typescript";
438
+ var _ts;
439
+ async function loadTS() {
440
+ if (_ts) return _ts;
441
+ try {
442
+ _ts = (await import("typescript")).default;
443
+ return _ts;
444
+ } catch {
445
+ throw new Error(
446
+ "The 'typescript' package is required for circular import detection.\nInstall it with: npm install -g typescript\nOr locally: npm install -D typescript"
447
+ );
448
+ }
449
+ }
282
450
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
283
451
  ".ts",
284
452
  ".tsx",
@@ -307,7 +475,8 @@ function fileToModule(filePath, srcDir) {
307
475
  }
308
476
  return parts.join(".");
309
477
  }
310
- function extractImports(source, fileName) {
478
+ async function extractImports(source, fileName) {
479
+ const ts = await loadTS();
311
480
  const imports = /* @__PURE__ */ new Set();
312
481
  const sourceFile = ts.createSourceFile(
313
482
  fileName,
@@ -365,13 +534,13 @@ function resolveImports(rawImports, basePackage, currentModule, isPackage) {
365
534
  function findSourceFiles(dir) {
366
535
  const results = [];
367
536
  function walk(d) {
368
- const entries = readdirSync(d);
537
+ const entries = readdirSync2(d);
369
538
  for (const entry of entries) {
370
539
  if (entry === "node_modules" || entry === "dist" || entry === ".git") {
371
540
  continue;
372
541
  }
373
542
  const full = join2(d, entry);
374
- const stat = statSync(full);
543
+ const stat = statSync2(full);
375
544
  if (stat.isDirectory()) {
376
545
  walk(full);
377
546
  } else if (SUPPORTED_EXTENSIONS.has(extname2(entry))) {
@@ -382,7 +551,7 @@ function findSourceFiles(dir) {
382
551
  walk(dir);
383
552
  return results;
384
553
  }
385
- function buildDependencyGraph(srcDir, basePackage) {
554
+ async function buildDependencyGraph(srcDir, basePackage) {
386
555
  const graph = /* @__PURE__ */ new Map();
387
556
  const files = findSourceFiles(srcDir);
388
557
  for (const file of files) {
@@ -397,7 +566,7 @@ function buildDependencyGraph(srcDir, basePackage) {
397
566
  }
398
567
  let rawImports;
399
568
  try {
400
- rawImports = extractImports(source, file);
569
+ rawImports = await extractImports(source, file);
401
570
  } catch (e) {
402
571
  console.error(`Warning: could not parse ${file}: ${e}`);
403
572
  continue;
@@ -456,10 +625,10 @@ function findCycles(graph) {
456
625
  }
457
626
  return unique;
458
627
  }
459
- function checkCircularImports(srcDir, basePackage) {
628
+ async function checkCircularImports(srcDir, basePackage) {
460
629
  let stat;
461
630
  try {
462
- stat = statSync(srcDir);
631
+ stat = statSync2(srcDir);
463
632
  } catch {
464
633
  console.error(`Error: ${srcDir}/ directory not found`);
465
634
  return 1;
@@ -468,7 +637,7 @@ function checkCircularImports(srcDir, basePackage) {
468
637
  console.error(`Error: ${srcDir}/ is not a directory`);
469
638
  return 1;
470
639
  }
471
- const graph = buildDependencyGraph(srcDir, basePackage);
640
+ const graph = await buildDependencyGraph(srcDir, basePackage);
472
641
  console.log(`Scanned ${graph.size} modules`);
473
642
  const cycles = findCycles(graph);
474
643
  if (cycles.length > 0) {
@@ -526,7 +695,7 @@ function getReleaseScript() {
526
695
  function buildProgram() {
527
696
  const program2 = new Command();
528
697
  program2.name("apdev").description("Shared development tools for TypeScript/JavaScript projects").version(getVersion());
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) => {
698
+ program2.command("check-chars").description("Validate files contain only allowed characters").argument("[files...]", "Files or directories to check (defaults to src/, tests/, examples/ and config files)").option("--charset <name>", "Extra charset preset (repeatable)", collect, []).option("--charset-file <path>", "Custom charset JSON file (repeatable)", collect, []).action((files, opts) => {
530
699
  let charsetNames = opts.charset;
531
700
  let charsetFiles = opts.charsetFile;
532
701
  if (charsetNames.length === 0 && charsetFiles.length === 0) {
@@ -548,7 +717,7 @@ function buildProgram() {
548
717
  const code = checkPaths(resolved, ranges, dangerous);
549
718
  process.exit(code);
550
719
  });
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) => {
720
+ 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(async (opts) => {
552
721
  const config = loadConfig();
553
722
  const basePackage = opts.package ?? config["base_package"];
554
723
  const srcDir = opts.srcDir ?? config["src_dir"] ?? "src";
@@ -558,7 +727,7 @@ function buildProgram() {
558
727
  );
559
728
  process.exit(1);
560
729
  }
561
- const code = checkCircularImports(resolve(srcDir), basePackage);
730
+ const code = await checkCircularImports(resolve(srcDir), basePackage);
562
731
  process.exit(code);
563
732
  });
564
733
  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) => {
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(src_exports, {
41
41
  loadCharset: () => loadCharset,
42
42
  loadConfig: () => loadConfig,
43
43
  resolveCharsets: () => resolveCharsets,
44
+ resolvePaths: () => resolvePaths,
44
45
  version: () => version
45
46
  });
46
47
  module.exports = __toCommonJS(src_exports);
@@ -304,10 +305,164 @@ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
304
305
  }
305
306
  return problems;
306
307
  }
308
+ var SKIP_SUFFIXES = /* @__PURE__ */ new Set([
309
+ // Bytecode
310
+ ".pyc",
311
+ ".pyo",
312
+ // Images
313
+ ".png",
314
+ ".jpg",
315
+ ".jpeg",
316
+ ".gif",
317
+ ".bmp",
318
+ ".ico",
319
+ ".svg",
320
+ ".webp",
321
+ // Fonts
322
+ ".ttf",
323
+ ".otf",
324
+ ".woff",
325
+ ".woff2",
326
+ ".eot",
327
+ // Archives
328
+ ".zip",
329
+ ".tar",
330
+ ".gz",
331
+ ".bz2",
332
+ ".xz",
333
+ ".7z",
334
+ // Compiled / binary
335
+ ".so",
336
+ ".dylib",
337
+ ".dll",
338
+ ".exe",
339
+ ".o",
340
+ ".a",
341
+ ".whl",
342
+ ".egg",
343
+ // Media
344
+ ".mp3",
345
+ ".mp4",
346
+ ".wav",
347
+ ".avi",
348
+ ".mov",
349
+ ".flac",
350
+ ".ogg",
351
+ // Documents
352
+ ".pdf",
353
+ ".doc",
354
+ ".docx",
355
+ ".xls",
356
+ ".xlsx",
357
+ ".ppt",
358
+ ".pptx",
359
+ // Data
360
+ ".db",
361
+ ".sqlite",
362
+ ".sqlite3",
363
+ ".pickle",
364
+ ".pkl"
365
+ ]);
366
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
367
+ "__pycache__",
368
+ "node_modules",
369
+ ".git",
370
+ ".venv",
371
+ "venv",
372
+ ".tox",
373
+ ".mypy_cache",
374
+ ".pytest_cache",
375
+ ".ruff_cache",
376
+ "dist",
377
+ "build"
378
+ ]);
379
+ var DEFAULT_DIRS = ["src", "tests", "examples"];
380
+ var DEFAULT_GLOBS = ["*.md", "*.yml", "*.yaml", "*.json", ".gitignore"];
381
+ function walkDir(directory) {
382
+ const files = [];
383
+ let entries;
384
+ try {
385
+ entries = (0, import_node_fs.readdirSync)(directory).sort();
386
+ } catch {
387
+ return files;
388
+ }
389
+ for (const name of entries) {
390
+ if (name.startsWith(".")) continue;
391
+ const fullPath = (0, import_node_path.join)(directory, name);
392
+ let stat;
393
+ try {
394
+ stat = (0, import_node_fs.statSync)(fullPath);
395
+ } catch {
396
+ continue;
397
+ }
398
+ if (stat.isDirectory()) {
399
+ if (SKIP_DIRS.has(name) || name.endsWith(".egg-info")) continue;
400
+ files.push(...walkDir(fullPath));
401
+ } else if (stat.isFile()) {
402
+ if (SKIP_SUFFIXES.has((0, import_node_path.extname)(name).toLowerCase())) continue;
403
+ files.push(fullPath);
404
+ }
405
+ }
406
+ return files;
407
+ }
408
+ function defaultProjectFiles() {
409
+ const cwd = process.cwd();
410
+ const files = [];
411
+ for (const dirname3 of DEFAULT_DIRS) {
412
+ const d = (0, import_node_path.join)(cwd, dirname3);
413
+ if ((0, import_node_fs.existsSync)(d) && (0, import_node_fs.statSync)(d).isDirectory()) {
414
+ files.push(...walkDir(d));
415
+ }
416
+ }
417
+ for (const pattern of DEFAULT_GLOBS) {
418
+ if (pattern.startsWith("*.")) {
419
+ const suffix = pattern.slice(1);
420
+ try {
421
+ for (const name of (0, import_node_fs.readdirSync)(cwd).sort()) {
422
+ if (name.endsWith(suffix) && (0, import_node_fs.statSync)((0, import_node_path.join)(cwd, name)).isFile()) {
423
+ files.push((0, import_node_path.join)(cwd, name));
424
+ }
425
+ }
426
+ } catch {
427
+ }
428
+ } else {
429
+ const fullPath = (0, import_node_path.join)(cwd, pattern);
430
+ if ((0, import_node_fs.existsSync)(fullPath) && (0, import_node_fs.statSync)(fullPath).isFile()) {
431
+ files.push(fullPath);
432
+ }
433
+ }
434
+ }
435
+ return files;
436
+ }
437
+ function resolvePaths(paths) {
438
+ if (paths.length === 0) {
439
+ return defaultProjectFiles();
440
+ }
441
+ const result = [];
442
+ for (const p of paths) {
443
+ try {
444
+ if ((0, import_node_fs.statSync)(p).isDirectory()) {
445
+ result.push(...walkDir(p));
446
+ } else {
447
+ result.push(p);
448
+ }
449
+ } catch {
450
+ result.push(p);
451
+ }
452
+ }
453
+ return result;
454
+ }
307
455
  function checkPaths(paths, extraRanges, dangerousMap) {
456
+ const resolved = resolvePaths(paths);
457
+ if (resolved.length === 0) {
458
+ console.log("No files to check.");
459
+ return 0;
460
+ }
308
461
  let hasError = false;
309
- for (const path of paths) {
462
+ let checked = 0;
463
+ for (const path of resolved) {
310
464
  const problems = checkFile(path, 5, extraRanges, dangerousMap);
465
+ checked++;
311
466
  if (problems.length > 0) {
312
467
  hasError = true;
313
468
  console.log(`
@@ -317,13 +472,27 @@ ${path} contains illegal characters:`);
317
472
  }
318
473
  }
319
474
  }
475
+ if (!hasError) {
476
+ console.log(`All ${checked} files passed.`);
477
+ }
320
478
  return hasError ? 1 : 0;
321
479
  }
322
480
 
323
481
  // src/check-imports.ts
324
482
  var import_node_fs2 = require("fs");
325
483
  var import_node_path2 = require("path");
326
- var import_typescript = __toESM(require("typescript"), 1);
484
+ var _ts;
485
+ async function loadTS() {
486
+ if (_ts) return _ts;
487
+ try {
488
+ _ts = (await import("typescript")).default;
489
+ return _ts;
490
+ } catch {
491
+ throw new Error(
492
+ "The 'typescript' package is required for circular import detection.\nInstall it with: npm install -g typescript\nOr locally: npm install -D typescript"
493
+ );
494
+ }
495
+ }
327
496
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
328
497
  ".ts",
329
498
  ".tsx",
@@ -352,26 +521,27 @@ function fileToModule(filePath, srcDir) {
352
521
  }
353
522
  return parts.join(".");
354
523
  }
355
- function extractImports(source, fileName) {
524
+ async function extractImports(source, fileName) {
525
+ const ts = await loadTS();
356
526
  const imports = /* @__PURE__ */ new Set();
357
- const sourceFile = import_typescript.default.createSourceFile(
527
+ const sourceFile = ts.createSourceFile(
358
528
  fileName,
359
529
  source,
360
- import_typescript.default.ScriptTarget.Latest,
530
+ ts.ScriptTarget.Latest,
361
531
  true,
362
- fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? import_typescript.default.ScriptKind.TSX : void 0
532
+ fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : void 0
363
533
  );
364
534
  function visit(node) {
365
- if (import_typescript.default.isImportDeclaration(node) && import_typescript.default.isStringLiteral(node.moduleSpecifier)) {
535
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
366
536
  imports.add(node.moduleSpecifier.text);
367
537
  }
368
- if (import_typescript.default.isExportDeclaration(node) && node.moduleSpecifier && import_typescript.default.isStringLiteral(node.moduleSpecifier)) {
538
+ if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
369
539
  imports.add(node.moduleSpecifier.text);
370
540
  }
371
- if (import_typescript.default.isCallExpression(node) && node.expression.kind === import_typescript.default.SyntaxKind.Identifier && node.expression.text === "require" && node.arguments.length === 1 && import_typescript.default.isStringLiteral(node.arguments[0])) {
541
+ if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.Identifier && node.expression.text === "require" && node.arguments.length === 1 && ts.isStringLiteral(node.arguments[0])) {
372
542
  imports.add(node.arguments[0].text);
373
543
  }
374
- import_typescript.default.forEachChild(node, visit);
544
+ ts.forEachChild(node, visit);
375
545
  }
376
546
  visit(sourceFile);
377
547
  return imports;
@@ -427,7 +597,7 @@ function findSourceFiles(dir) {
427
597
  walk(dir);
428
598
  return results;
429
599
  }
430
- function buildDependencyGraph(srcDir, basePackage) {
600
+ async function buildDependencyGraph(srcDir, basePackage) {
431
601
  const graph = /* @__PURE__ */ new Map();
432
602
  const files = findSourceFiles(srcDir);
433
603
  for (const file of files) {
@@ -442,7 +612,7 @@ function buildDependencyGraph(srcDir, basePackage) {
442
612
  }
443
613
  let rawImports;
444
614
  try {
445
- rawImports = extractImports(source, file);
615
+ rawImports = await extractImports(source, file);
446
616
  } catch (e) {
447
617
  console.error(`Warning: could not parse ${file}: ${e}`);
448
618
  continue;
@@ -501,7 +671,7 @@ function findCycles(graph) {
501
671
  }
502
672
  return unique;
503
673
  }
504
- function checkCircularImports(srcDir, basePackage) {
674
+ async function checkCircularImports(srcDir, basePackage) {
505
675
  let stat;
506
676
  try {
507
677
  stat = (0, import_node_fs2.statSync)(srcDir);
@@ -513,7 +683,7 @@ function checkCircularImports(srcDir, basePackage) {
513
683
  console.error(`Error: ${srcDir}/ is not a directory`);
514
684
  return 1;
515
685
  }
516
- const graph = buildDependencyGraph(srcDir, basePackage);
686
+ const graph = await buildDependencyGraph(srcDir, basePackage);
517
687
  console.log(`Scanned ${graph.size} modules`);
518
688
  const cycles = findCycles(graph);
519
689
  if (cycles.length > 0) {
@@ -577,5 +747,6 @@ var version = readVersion();
577
747
  loadCharset,
578
748
  loadConfig,
579
749
  resolveCharsets,
750
+ resolvePaths,
580
751
  version
581
752
  });
package/dist/index.d.cts CHANGED
@@ -42,6 +42,7 @@ declare function isDangerousChar(c: string): boolean;
42
42
  * Returns a list of problem descriptions (empty if the file is clean).
43
43
  */
44
44
  declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
45
+ declare function resolvePaths(paths: string[]): string[];
45
46
  /**
46
47
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
47
48
  */
@@ -56,11 +57,11 @@ declare function checkPaths(paths: string[], extraRanges?: [number, number][], d
56
57
  /** Convert a file path to a dotted module name (relative to srcDir). */
57
58
  declare function fileToModule(filePath: string, srcDir: string): string;
58
59
  /** Build a module-to-module dependency graph for the given package. */
59
- declare function buildDependencyGraph(srcDir: string, basePackage: string): Map<string, Set<string>>;
60
+ declare function buildDependencyGraph(srcDir: string, basePackage: string): Promise<Map<string, Set<string>>>;
60
61
  /** Find all elementary cycles in the dependency graph using DFS. */
61
62
  declare function findCycles(graph: Map<string, Set<string>>): string[][];
62
63
  /** Run circular import detection. Returns 0 if clean, 1 if cycles found. */
63
- declare function checkCircularImports(srcDir: string, basePackage: string): number;
64
+ declare function checkCircularImports(srcDir: string, basePackage: string): Promise<number>;
64
65
 
65
66
  /**
66
67
  * Configuration loading for apdev.
@@ -75,4 +76,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
75
76
 
76
77
  declare const version: string;
77
78
 
78
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, version };
79
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, resolvePaths, version };
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ declare function isDangerousChar(c: string): boolean;
42
42
  * Returns a list of problem descriptions (empty if the file is clean).
43
43
  */
44
44
  declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
45
+ declare function resolvePaths(paths: string[]): string[];
45
46
  /**
46
47
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
47
48
  */
@@ -56,11 +57,11 @@ declare function checkPaths(paths: string[], extraRanges?: [number, number][], d
56
57
  /** Convert a file path to a dotted module name (relative to srcDir). */
57
58
  declare function fileToModule(filePath: string, srcDir: string): string;
58
59
  /** Build a module-to-module dependency graph for the given package. */
59
- declare function buildDependencyGraph(srcDir: string, basePackage: string): Map<string, Set<string>>;
60
+ declare function buildDependencyGraph(srcDir: string, basePackage: string): Promise<Map<string, Set<string>>>;
60
61
  /** Find all elementary cycles in the dependency graph using DFS. */
61
62
  declare function findCycles(graph: Map<string, Set<string>>): string[][];
62
63
  /** Run circular import detection. Returns 0 if clean, 1 if cycles found. */
63
- declare function checkCircularImports(srcDir: string, basePackage: string): number;
64
+ declare function checkCircularImports(srcDir: string, basePackage: string): Promise<number>;
64
65
 
65
66
  /**
66
67
  * Configuration loading for apdev.
@@ -75,4 +76,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
75
76
 
76
77
  declare const version: string;
77
78
 
78
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, version };
79
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, resolvePaths, version };
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ var getDirname = () => path.dirname(getFilename());
6
6
  var __dirname = /* @__PURE__ */ getDirname();
7
7
 
8
8
  // src/check-chars.ts
9
- import { readFileSync, existsSync } from "fs";
9
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
10
10
  import { extname, dirname, join, sep } from "path";
11
11
  import { fileURLToPath as fileURLToPath2 } from "url";
12
12
  function getCharsetsDir() {
@@ -260,10 +260,164 @@ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
260
260
  }
261
261
  return problems;
262
262
  }
263
+ var SKIP_SUFFIXES = /* @__PURE__ */ new Set([
264
+ // Bytecode
265
+ ".pyc",
266
+ ".pyo",
267
+ // Images
268
+ ".png",
269
+ ".jpg",
270
+ ".jpeg",
271
+ ".gif",
272
+ ".bmp",
273
+ ".ico",
274
+ ".svg",
275
+ ".webp",
276
+ // Fonts
277
+ ".ttf",
278
+ ".otf",
279
+ ".woff",
280
+ ".woff2",
281
+ ".eot",
282
+ // Archives
283
+ ".zip",
284
+ ".tar",
285
+ ".gz",
286
+ ".bz2",
287
+ ".xz",
288
+ ".7z",
289
+ // Compiled / binary
290
+ ".so",
291
+ ".dylib",
292
+ ".dll",
293
+ ".exe",
294
+ ".o",
295
+ ".a",
296
+ ".whl",
297
+ ".egg",
298
+ // Media
299
+ ".mp3",
300
+ ".mp4",
301
+ ".wav",
302
+ ".avi",
303
+ ".mov",
304
+ ".flac",
305
+ ".ogg",
306
+ // Documents
307
+ ".pdf",
308
+ ".doc",
309
+ ".docx",
310
+ ".xls",
311
+ ".xlsx",
312
+ ".ppt",
313
+ ".pptx",
314
+ // Data
315
+ ".db",
316
+ ".sqlite",
317
+ ".sqlite3",
318
+ ".pickle",
319
+ ".pkl"
320
+ ]);
321
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
322
+ "__pycache__",
323
+ "node_modules",
324
+ ".git",
325
+ ".venv",
326
+ "venv",
327
+ ".tox",
328
+ ".mypy_cache",
329
+ ".pytest_cache",
330
+ ".ruff_cache",
331
+ "dist",
332
+ "build"
333
+ ]);
334
+ var DEFAULT_DIRS = ["src", "tests", "examples"];
335
+ var DEFAULT_GLOBS = ["*.md", "*.yml", "*.yaml", "*.json", ".gitignore"];
336
+ function walkDir(directory) {
337
+ const files = [];
338
+ let entries;
339
+ try {
340
+ entries = readdirSync(directory).sort();
341
+ } catch {
342
+ return files;
343
+ }
344
+ for (const name of entries) {
345
+ if (name.startsWith(".")) continue;
346
+ const fullPath = join(directory, name);
347
+ let stat;
348
+ try {
349
+ stat = statSync(fullPath);
350
+ } catch {
351
+ continue;
352
+ }
353
+ if (stat.isDirectory()) {
354
+ if (SKIP_DIRS.has(name) || name.endsWith(".egg-info")) continue;
355
+ files.push(...walkDir(fullPath));
356
+ } else if (stat.isFile()) {
357
+ if (SKIP_SUFFIXES.has(extname(name).toLowerCase())) continue;
358
+ files.push(fullPath);
359
+ }
360
+ }
361
+ return files;
362
+ }
363
+ function defaultProjectFiles() {
364
+ const cwd = process.cwd();
365
+ const files = [];
366
+ for (const dirname3 of DEFAULT_DIRS) {
367
+ const d = join(cwd, dirname3);
368
+ if (existsSync(d) && statSync(d).isDirectory()) {
369
+ files.push(...walkDir(d));
370
+ }
371
+ }
372
+ for (const pattern of DEFAULT_GLOBS) {
373
+ if (pattern.startsWith("*.")) {
374
+ const suffix = pattern.slice(1);
375
+ try {
376
+ for (const name of readdirSync(cwd).sort()) {
377
+ if (name.endsWith(suffix) && statSync(join(cwd, name)).isFile()) {
378
+ files.push(join(cwd, name));
379
+ }
380
+ }
381
+ } catch {
382
+ }
383
+ } else {
384
+ const fullPath = join(cwd, pattern);
385
+ if (existsSync(fullPath) && statSync(fullPath).isFile()) {
386
+ files.push(fullPath);
387
+ }
388
+ }
389
+ }
390
+ return files;
391
+ }
392
+ function resolvePaths(paths) {
393
+ if (paths.length === 0) {
394
+ return defaultProjectFiles();
395
+ }
396
+ const result = [];
397
+ for (const p of paths) {
398
+ try {
399
+ if (statSync(p).isDirectory()) {
400
+ result.push(...walkDir(p));
401
+ } else {
402
+ result.push(p);
403
+ }
404
+ } catch {
405
+ result.push(p);
406
+ }
407
+ }
408
+ return result;
409
+ }
263
410
  function checkPaths(paths, extraRanges, dangerousMap) {
411
+ const resolved = resolvePaths(paths);
412
+ if (resolved.length === 0) {
413
+ console.log("No files to check.");
414
+ return 0;
415
+ }
264
416
  let hasError = false;
265
- for (const path2 of paths) {
417
+ let checked = 0;
418
+ for (const path2 of resolved) {
266
419
  const problems = checkFile(path2, 5, extraRanges, dangerousMap);
420
+ checked++;
267
421
  if (problems.length > 0) {
268
422
  hasError = true;
269
423
  console.log(`
@@ -273,13 +427,27 @@ ${path2} contains illegal characters:`);
273
427
  }
274
428
  }
275
429
  }
430
+ if (!hasError) {
431
+ console.log(`All ${checked} files passed.`);
432
+ }
276
433
  return hasError ? 1 : 0;
277
434
  }
278
435
 
279
436
  // src/check-imports.ts
280
- import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
437
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
281
438
  import { join as join2, relative, sep as sep2, extname as extname2, basename } from "path";
282
- import ts from "typescript";
439
+ var _ts;
440
+ async function loadTS() {
441
+ if (_ts) return _ts;
442
+ try {
443
+ _ts = (await import("typescript")).default;
444
+ return _ts;
445
+ } catch {
446
+ throw new Error(
447
+ "The 'typescript' package is required for circular import detection.\nInstall it with: npm install -g typescript\nOr locally: npm install -D typescript"
448
+ );
449
+ }
450
+ }
283
451
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
284
452
  ".ts",
285
453
  ".tsx",
@@ -308,7 +476,8 @@ function fileToModule(filePath, srcDir) {
308
476
  }
309
477
  return parts.join(".");
310
478
  }
311
- function extractImports(source, fileName) {
479
+ async function extractImports(source, fileName) {
480
+ const ts = await loadTS();
312
481
  const imports = /* @__PURE__ */ new Set();
313
482
  const sourceFile = ts.createSourceFile(
314
483
  fileName,
@@ -366,13 +535,13 @@ function resolveImports(rawImports, basePackage, currentModule, isPackage) {
366
535
  function findSourceFiles(dir) {
367
536
  const results = [];
368
537
  function walk(d) {
369
- const entries = readdirSync(d);
538
+ const entries = readdirSync2(d);
370
539
  for (const entry of entries) {
371
540
  if (entry === "node_modules" || entry === "dist" || entry === ".git") {
372
541
  continue;
373
542
  }
374
543
  const full = join2(d, entry);
375
- const stat = statSync(full);
544
+ const stat = statSync2(full);
376
545
  if (stat.isDirectory()) {
377
546
  walk(full);
378
547
  } else if (SUPPORTED_EXTENSIONS.has(extname2(entry))) {
@@ -383,7 +552,7 @@ function findSourceFiles(dir) {
383
552
  walk(dir);
384
553
  return results;
385
554
  }
386
- function buildDependencyGraph(srcDir, basePackage) {
555
+ async function buildDependencyGraph(srcDir, basePackage) {
387
556
  const graph = /* @__PURE__ */ new Map();
388
557
  const files = findSourceFiles(srcDir);
389
558
  for (const file of files) {
@@ -398,7 +567,7 @@ function buildDependencyGraph(srcDir, basePackage) {
398
567
  }
399
568
  let rawImports;
400
569
  try {
401
- rawImports = extractImports(source, file);
570
+ rawImports = await extractImports(source, file);
402
571
  } catch (e) {
403
572
  console.error(`Warning: could not parse ${file}: ${e}`);
404
573
  continue;
@@ -457,10 +626,10 @@ function findCycles(graph) {
457
626
  }
458
627
  return unique;
459
628
  }
460
- function checkCircularImports(srcDir, basePackage) {
629
+ async function checkCircularImports(srcDir, basePackage) {
461
630
  let stat;
462
631
  try {
463
- stat = statSync(srcDir);
632
+ stat = statSync2(srcDir);
464
633
  } catch {
465
634
  console.error(`Error: ${srcDir}/ directory not found`);
466
635
  return 1;
@@ -469,7 +638,7 @@ function checkCircularImports(srcDir, basePackage) {
469
638
  console.error(`Error: ${srcDir}/ is not a directory`);
470
639
  return 1;
471
640
  }
472
- const graph = buildDependencyGraph(srcDir, basePackage);
641
+ const graph = await buildDependencyGraph(srcDir, basePackage);
473
642
  console.log(`Scanned ${graph.size} modules`);
474
643
  const cycles = findCycles(graph);
475
644
  if (cycles.length > 0) {
@@ -532,5 +701,6 @@ export {
532
701
  loadCharset,
533
702
  loadConfig,
534
703
  resolveCharsets,
704
+ resolvePaths,
535
705
  version
536
706
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apdev-js",
3
- "version": "0.2.0",
4
- "description": "Shared development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more",
3
+ "version": "0.2.2",
4
+ "description": "General-purpose development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -31,9 +31,16 @@
31
31
  },
32
32
  "keywords": [
33
33
  "development-tools",
34
+ "quality-assurance",
35
+ "linting",
36
+ "static-analysis",
34
37
  "character-validation",
35
38
  "circular-imports",
36
- "linting"
39
+ "release-automation",
40
+ "unicode-checker",
41
+ "trojan-source-detection",
42
+ "typescript",
43
+ "javascript"
37
44
  ],
38
45
  "author": "aipartnerup <tercel.yi@gmail.com>",
39
46
  "license": "Apache-2.0",
@@ -63,4 +70,4 @@
63
70
  "typescript": "^5.7.0",
64
71
  "vitest": "^3.0.0"
65
72
  }
66
- }
73
+ }