wireweaver 0.1.0 → 0.1.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.
@@ -287,18 +287,74 @@ function discoverBeanRegistrations(root, implementations, extraScanDirs = []) {
287
287
  return result;
288
288
  }
289
289
  function collectBeanRegistrationsFromDir(root, implementations, result, allowNodeModules) {
290
- const tsconfigPath = import_typescript.default.findConfigFile(root, import_typescript.default.sys.fileExists, "tsconfig.json");
291
- if (!tsconfigPath) return;
292
- const configFile = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
293
- if (configFile.error) return;
294
- const parsedConfig = import_typescript.default.parseJsonConfigFileContent(configFile.config, import_typescript.default.sys, getDirName(tsconfigPath));
295
- const program = import_typescript.default.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options });
296
- for (const sourceFile of program.getSourceFiles()) {
297
- if (sourceFile.isDeclarationFile) continue;
298
- if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
290
+ for (const sourceFile of getScanSourceFiles(root, allowNodeModules)) {
299
291
  collectBeanRegistrationsFromFile(sourceFile, sourceFile.fileName, implementations, result);
300
292
  }
301
293
  }
294
+ function getScanSourceFiles(root, allowNodeModules) {
295
+ const tsconfigPath = allowNodeModules ? findTsconfigWithin(root) : import_typescript.default.findConfigFile(root, import_typescript.default.sys.fileExists, "tsconfig.json");
296
+ if (tsconfigPath) {
297
+ const configFile = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
298
+ if (configFile.error) return [];
299
+ const parsedConfig = import_typescript.default.parseJsonConfigFileContent(configFile.config, import_typescript.default.sys, getDirName(tsconfigPath));
300
+ const program = import_typescript.default.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options });
301
+ const files = [];
302
+ for (const sourceFile of program.getSourceFiles()) {
303
+ if (!allowNodeModules && sourceFile.isDeclarationFile) continue;
304
+ if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
305
+ files.push(sourceFile);
306
+ }
307
+ return files;
308
+ }
309
+ if (!allowNodeModules) return [];
310
+ return parseSourceFilesInDir(root);
311
+ }
312
+ function findTsconfigWithin(dir) {
313
+ const candidate = (0, import_node_path.join)(dir, "tsconfig.json");
314
+ return (0, import_node_fs.existsSync)(candidate) ? candidate : void 0;
315
+ }
316
+ function parseSourceFilesInDir(dir) {
317
+ const sourceFiles = [];
318
+ function walk(current) {
319
+ let names;
320
+ try {
321
+ names = (0, import_node_fs.readdirSync)(current);
322
+ } catch {
323
+ return;
324
+ }
325
+ for (const name of names) {
326
+ const fullPath = (0, import_node_path.join)(current, name);
327
+ let isDir = false;
328
+ try {
329
+ isDir = (0, import_node_fs.statSync)(fullPath).isDirectory();
330
+ } catch {
331
+ continue;
332
+ }
333
+ if (isDir) {
334
+ if (name === "node_modules") continue;
335
+ walk(fullPath);
336
+ continue;
337
+ }
338
+ if (!/\.(ts|tsx)$/.test(name)) continue;
339
+ try {
340
+ const content = (0, import_node_fs.readFileSync)(fullPath, "utf-8");
341
+ sourceFiles.push(
342
+ import_typescript.default.createSourceFile(
343
+ fullPath,
344
+ content,
345
+ import_typescript.default.ScriptTarget.Latest,
346
+ /* setParentNodes */
347
+ true,
348
+ import_typescript.default.ScriptKind.TS
349
+ )
350
+ );
351
+ } catch {
352
+ }
353
+ }
354
+ }
355
+ walk(dir);
356
+ return sourceFiles;
357
+ }
302
358
  function collectBeanRegistrationsFromFile(sourceFile, filePath, globalImplementations, result) {
303
359
  const localImpls = discoverLocalImplementations(sourceFile, filePath);
304
360
  const typeOnlySymbols = collectTypeOnlySymbols(sourceFile);
@@ -339,7 +395,8 @@ function discoverInterfaceImplementations(root, extraScanDirs = []) {
339
395
  scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, false);
340
396
  for (const dir of extraScanDirs) {
341
397
  const packageRoot = findPackageRoot(dir);
342
- scanSourceFilesForImplementations(dir, implementations, allClassHeritage, beanReturnTypes, true, packageRoot);
398
+ const componentNames = collectComponentClassNames(dir);
399
+ scanSourceFilesForImplementations(dir, implementations, allClassHeritage, beanReturnTypes, true, packageRoot, componentNames);
343
400
  }
344
401
  for (const typeName of beanReturnTypes) {
345
402
  const entries = allClassHeritage.get(typeName);
@@ -355,23 +412,8 @@ function discoverInterfaceImplementations(root, extraScanDirs = []) {
355
412
  }
356
413
  return implementations;
357
414
  }
358
- function scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, allowNodeModules, packageRoot) {
359
- const tsconfigPath = import_typescript.default.findConfigFile(root, import_typescript.default.sys.fileExists, "tsconfig.json");
360
- if (!tsconfigPath) return;
361
- const configFile = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
362
- if (configFile.error) return;
363
- const parsedConfig = import_typescript.default.parseJsonConfigFileContent(
364
- configFile.config,
365
- import_typescript.default.sys,
366
- getDirName(tsconfigPath)
367
- );
368
- const program = import_typescript.default.createProgram({
369
- rootNames: parsedConfig.fileNames,
370
- options: parsedConfig.options
371
- });
372
- for (const sourceFile of program.getSourceFiles()) {
373
- if (sourceFile.isDeclarationFile) continue;
374
- if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
415
+ function scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, allowNodeModules, packageRoot, componentNames = /* @__PURE__ */ new Set()) {
416
+ for (const sourceFile of getScanSourceFiles(root, allowNodeModules)) {
375
417
  import_typescript.default.forEachChild(sourceFile, (node) => {
376
418
  if (import_typescript.default.isClassDeclaration(node) && node.name) {
377
419
  const importSpecifier = packageRoot ? resolvePackageImportSpecifier(node.name.text, packageRoot) : void 0;
@@ -385,7 +427,7 @@ function scanSourceFilesForImplementations(root, implementations, allClassHerita
385
427
  heritageEntries.push({ interfaceName, filePath: sourceFile.fileName, importSpecifier });
386
428
  allClassHeritage.set(node.name.text, heritageEntries);
387
429
  }
388
- if (!hasServiceDecorator(node)) continue;
430
+ if (!hasServiceDecorator(node) && !componentNames.has(node.name.text)) continue;
389
431
  const existing = implementations.get(interfaceName) ?? [];
390
432
  if (existing.some((impl) => impl.className === node.name.text && normalizeFilePath(impl.filePath) === normalizeFilePath(sourceFile.fileName))) {
391
433
  continue;
@@ -496,14 +538,17 @@ function buildImportInsertions(code, sourceFile, importsToInject, sideEffectImpo
496
538
  if (unresolved.length === 0) continue;
497
539
  const existing = findImportByModule(sourceFile, importPath);
498
540
  if (existing && existing.importClause?.namedBindings && import_typescript.default.isNamedImports(existing.importClause.namedBindings)) {
499
- const existingNames = new Set(existing.importClause.namedBindings.elements.map((element) => element.name.text));
541
+ const namedImports = existing.importClause.namedBindings;
542
+ const existingNames = new Set(namedImports.elements.map((element) => element.name.text));
500
543
  const missing = unresolved.filter((name) => !existingNames.has(name));
501
544
  if (missing.length === 0) continue;
502
- const namedImports = existing.importClause.namedBindings;
545
+ const hasElements = namedImports.elements.length > 0;
546
+ const insertionPoint2 = namedImports.getEnd() - 1;
547
+ const prefix = hasElements ? ", " : "";
503
548
  replacements.push({
504
- start: namedImports.getStart(sourceFile) + 1,
505
- end: namedImports.getEnd() - 1,
506
- text: `${[...existingNames, ...missing].sort().join(", ")}`
549
+ start: insertionPoint2,
550
+ end: insertionPoint2,
551
+ text: `${prefix}${missing.sort().join(", ")}`
507
552
  });
508
553
  continue;
509
554
  }
@@ -610,6 +655,51 @@ function resolveExtraScanDirs(root, extraScanDirs) {
610
655
  if (!extraScanDirs || extraScanDirs.length === 0) return [];
611
656
  return extraScanDirs.map((dir) => (0, import_node_path.isAbsolute)(dir) ? dir : (0, import_node_path.resolve)(root, dir));
612
657
  }
658
+ function collectComponentClassNames(dir) {
659
+ const names = /* @__PURE__ */ new Set();
660
+ const stage3 = /_([A-Za-z_$][\w$]*)_decorators\s*=\s*\[([^\]]*)\]/g;
661
+ const legacy = /__decorate\(\s*\[([^\]]*?)\]\s*,\s*([A-Za-z_$][\w$]*)/g;
662
+ function containsComponentDecorator(decoratorList) {
663
+ return /\b(Component|Service)\s*\(/.test(decoratorList);
664
+ }
665
+ function walk(current) {
666
+ let entries;
667
+ try {
668
+ entries = (0, import_node_fs.readdirSync)(current);
669
+ } catch {
670
+ return;
671
+ }
672
+ for (const name of entries) {
673
+ const fullPath = (0, import_node_path.join)(current, name);
674
+ let isDir = false;
675
+ try {
676
+ isDir = (0, import_node_fs.statSync)(fullPath).isDirectory();
677
+ } catch {
678
+ continue;
679
+ }
680
+ if (isDir) {
681
+ if (name === "node_modules") continue;
682
+ walk(fullPath);
683
+ continue;
684
+ }
685
+ if (!/\.(js|cjs|mjs)$/.test(name)) continue;
686
+ let content;
687
+ try {
688
+ content = (0, import_node_fs.readFileSync)(fullPath, "utf-8");
689
+ } catch {
690
+ continue;
691
+ }
692
+ for (const match of content.matchAll(stage3)) {
693
+ if (containsComponentDecorator(match[2])) names.add(match[1]);
694
+ }
695
+ for (const match of content.matchAll(legacy)) {
696
+ if (containsComponentDecorator(match[1])) names.add(match[2]);
697
+ }
698
+ }
699
+ }
700
+ walk(dir);
701
+ return names;
702
+ }
613
703
  function findPackageRoot(startDir) {
614
704
  let current = startDir;
615
705
  while (true) {
@@ -2,7 +2,7 @@
2
2
  import ts from "typescript";
3
3
  import { readFile } from "fs/promises";
4
4
  import { isAbsolute, resolve as resolvePath, join as joinPath, dirname } from "path";
5
- import { existsSync, readFileSync } from "fs";
5
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
6
6
  function wireWeaverPlugin(options = {}) {
7
7
  let implementations = /* @__PURE__ */ new Map();
8
8
  let beanRegistrations = /* @__PURE__ */ new Map();
@@ -250,18 +250,74 @@ function discoverBeanRegistrations(root, implementations, extraScanDirs = []) {
250
250
  return result;
251
251
  }
252
252
  function collectBeanRegistrationsFromDir(root, implementations, result, allowNodeModules) {
253
- const tsconfigPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
254
- if (!tsconfigPath) return;
255
- const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
256
- if (configFile.error) return;
257
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, getDirName(tsconfigPath));
258
- const program = ts.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options });
259
- for (const sourceFile of program.getSourceFiles()) {
260
- if (sourceFile.isDeclarationFile) continue;
261
- if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
253
+ for (const sourceFile of getScanSourceFiles(root, allowNodeModules)) {
262
254
  collectBeanRegistrationsFromFile(sourceFile, sourceFile.fileName, implementations, result);
263
255
  }
264
256
  }
257
+ function getScanSourceFiles(root, allowNodeModules) {
258
+ const tsconfigPath = allowNodeModules ? findTsconfigWithin(root) : ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
259
+ if (tsconfigPath) {
260
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
261
+ if (configFile.error) return [];
262
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, getDirName(tsconfigPath));
263
+ const program = ts.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options });
264
+ const files = [];
265
+ for (const sourceFile of program.getSourceFiles()) {
266
+ if (!allowNodeModules && sourceFile.isDeclarationFile) continue;
267
+ if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
268
+ files.push(sourceFile);
269
+ }
270
+ return files;
271
+ }
272
+ if (!allowNodeModules) return [];
273
+ return parseSourceFilesInDir(root);
274
+ }
275
+ function findTsconfigWithin(dir) {
276
+ const candidate = joinPath(dir, "tsconfig.json");
277
+ return existsSync(candidate) ? candidate : void 0;
278
+ }
279
+ function parseSourceFilesInDir(dir) {
280
+ const sourceFiles = [];
281
+ function walk(current) {
282
+ let names;
283
+ try {
284
+ names = readdirSync(current);
285
+ } catch {
286
+ return;
287
+ }
288
+ for (const name of names) {
289
+ const fullPath = joinPath(current, name);
290
+ let isDir = false;
291
+ try {
292
+ isDir = statSync(fullPath).isDirectory();
293
+ } catch {
294
+ continue;
295
+ }
296
+ if (isDir) {
297
+ if (name === "node_modules") continue;
298
+ walk(fullPath);
299
+ continue;
300
+ }
301
+ if (!/\.(ts|tsx)$/.test(name)) continue;
302
+ try {
303
+ const content = readFileSync(fullPath, "utf-8");
304
+ sourceFiles.push(
305
+ ts.createSourceFile(
306
+ fullPath,
307
+ content,
308
+ ts.ScriptTarget.Latest,
309
+ /* setParentNodes */
310
+ true,
311
+ ts.ScriptKind.TS
312
+ )
313
+ );
314
+ } catch {
315
+ }
316
+ }
317
+ }
318
+ walk(dir);
319
+ return sourceFiles;
320
+ }
265
321
  function collectBeanRegistrationsFromFile(sourceFile, filePath, globalImplementations, result) {
266
322
  const localImpls = discoverLocalImplementations(sourceFile, filePath);
267
323
  const typeOnlySymbols = collectTypeOnlySymbols(sourceFile);
@@ -302,7 +358,8 @@ function discoverInterfaceImplementations(root, extraScanDirs = []) {
302
358
  scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, false);
303
359
  for (const dir of extraScanDirs) {
304
360
  const packageRoot = findPackageRoot(dir);
305
- scanSourceFilesForImplementations(dir, implementations, allClassHeritage, beanReturnTypes, true, packageRoot);
361
+ const componentNames = collectComponentClassNames(dir);
362
+ scanSourceFilesForImplementations(dir, implementations, allClassHeritage, beanReturnTypes, true, packageRoot, componentNames);
306
363
  }
307
364
  for (const typeName of beanReturnTypes) {
308
365
  const entries = allClassHeritage.get(typeName);
@@ -318,23 +375,8 @@ function discoverInterfaceImplementations(root, extraScanDirs = []) {
318
375
  }
319
376
  return implementations;
320
377
  }
321
- function scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, allowNodeModules, packageRoot) {
322
- const tsconfigPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
323
- if (!tsconfigPath) return;
324
- const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
325
- if (configFile.error) return;
326
- const parsedConfig = ts.parseJsonConfigFileContent(
327
- configFile.config,
328
- ts.sys,
329
- getDirName(tsconfigPath)
330
- );
331
- const program = ts.createProgram({
332
- rootNames: parsedConfig.fileNames,
333
- options: parsedConfig.options
334
- });
335
- for (const sourceFile of program.getSourceFiles()) {
336
- if (sourceFile.isDeclarationFile) continue;
337
- if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
378
+ function scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, allowNodeModules, packageRoot, componentNames = /* @__PURE__ */ new Set()) {
379
+ for (const sourceFile of getScanSourceFiles(root, allowNodeModules)) {
338
380
  ts.forEachChild(sourceFile, (node) => {
339
381
  if (ts.isClassDeclaration(node) && node.name) {
340
382
  const importSpecifier = packageRoot ? resolvePackageImportSpecifier(node.name.text, packageRoot) : void 0;
@@ -348,7 +390,7 @@ function scanSourceFilesForImplementations(root, implementations, allClassHerita
348
390
  heritageEntries.push({ interfaceName, filePath: sourceFile.fileName, importSpecifier });
349
391
  allClassHeritage.set(node.name.text, heritageEntries);
350
392
  }
351
- if (!hasServiceDecorator(node)) continue;
393
+ if (!hasServiceDecorator(node) && !componentNames.has(node.name.text)) continue;
352
394
  const existing = implementations.get(interfaceName) ?? [];
353
395
  if (existing.some((impl) => impl.className === node.name.text && normalizeFilePath(impl.filePath) === normalizeFilePath(sourceFile.fileName))) {
354
396
  continue;
@@ -459,14 +501,17 @@ function buildImportInsertions(code, sourceFile, importsToInject, sideEffectImpo
459
501
  if (unresolved.length === 0) continue;
460
502
  const existing = findImportByModule(sourceFile, importPath);
461
503
  if (existing && existing.importClause?.namedBindings && ts.isNamedImports(existing.importClause.namedBindings)) {
462
- const existingNames = new Set(existing.importClause.namedBindings.elements.map((element) => element.name.text));
504
+ const namedImports = existing.importClause.namedBindings;
505
+ const existingNames = new Set(namedImports.elements.map((element) => element.name.text));
463
506
  const missing = unresolved.filter((name) => !existingNames.has(name));
464
507
  if (missing.length === 0) continue;
465
- const namedImports = existing.importClause.namedBindings;
508
+ const hasElements = namedImports.elements.length > 0;
509
+ const insertionPoint2 = namedImports.getEnd() - 1;
510
+ const prefix = hasElements ? ", " : "";
466
511
  replacements.push({
467
- start: namedImports.getStart(sourceFile) + 1,
468
- end: namedImports.getEnd() - 1,
469
- text: `${[...existingNames, ...missing].sort().join(", ")}`
512
+ start: insertionPoint2,
513
+ end: insertionPoint2,
514
+ text: `${prefix}${missing.sort().join(", ")}`
470
515
  });
471
516
  continue;
472
517
  }
@@ -573,6 +618,51 @@ function resolveExtraScanDirs(root, extraScanDirs) {
573
618
  if (!extraScanDirs || extraScanDirs.length === 0) return [];
574
619
  return extraScanDirs.map((dir) => isAbsolute(dir) ? dir : resolvePath(root, dir));
575
620
  }
621
+ function collectComponentClassNames(dir) {
622
+ const names = /* @__PURE__ */ new Set();
623
+ const stage3 = /_([A-Za-z_$][\w$]*)_decorators\s*=\s*\[([^\]]*)\]/g;
624
+ const legacy = /__decorate\(\s*\[([^\]]*?)\]\s*,\s*([A-Za-z_$][\w$]*)/g;
625
+ function containsComponentDecorator(decoratorList) {
626
+ return /\b(Component|Service)\s*\(/.test(decoratorList);
627
+ }
628
+ function walk(current) {
629
+ let entries;
630
+ try {
631
+ entries = readdirSync(current);
632
+ } catch {
633
+ return;
634
+ }
635
+ for (const name of entries) {
636
+ const fullPath = joinPath(current, name);
637
+ let isDir = false;
638
+ try {
639
+ isDir = statSync(fullPath).isDirectory();
640
+ } catch {
641
+ continue;
642
+ }
643
+ if (isDir) {
644
+ if (name === "node_modules") continue;
645
+ walk(fullPath);
646
+ continue;
647
+ }
648
+ if (!/\.(js|cjs|mjs)$/.test(name)) continue;
649
+ let content;
650
+ try {
651
+ content = readFileSync(fullPath, "utf-8");
652
+ } catch {
653
+ continue;
654
+ }
655
+ for (const match of content.matchAll(stage3)) {
656
+ if (containsComponentDecorator(match[2])) names.add(match[1]);
657
+ }
658
+ for (const match of content.matchAll(legacy)) {
659
+ if (containsComponentDecorator(match[1])) names.add(match[2]);
660
+ }
661
+ }
662
+ }
663
+ walk(dir);
664
+ return names;
665
+ }
576
666
  function findPackageRoot(startDir) {
577
667
  let current = startDir;
578
668
  while (true) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "wireweaver",
3
3
  "description": "A lightweight TypeScript Dependency Injection Library",
4
4
  "productName": "WireWeaver",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
8
8
  "module": "./dist/index.js",
@@ -24,6 +24,7 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "build": "tsup",
27
+ "ver": "npm --no-git-tag-version version",
27
28
  "build:dev": "tsc -p tsconfig.build.json",
28
29
  "test": "vitest run"
29
30
  },