@doccov/sdk 0.12.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +240 -212
  2. package/dist/index.js +335 -192
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -3501,7 +3501,6 @@ function normalizeDocCovOptions(options = {}) {
3501
3501
  cwd: options.cwd ?? process.cwd()
3502
3502
  };
3503
3503
  }
3504
- var normalizeOpenPkgOptions = normalizeDocCovOptions;
3505
3504
 
3506
3505
  // src/analysis/program.ts
3507
3506
  import * as path7 from "node:path";
@@ -3559,7 +3558,7 @@ function createAnalysisContext({
3559
3558
  options
3560
3559
  }) {
3561
3560
  const baseDir = packageDir ?? path8.dirname(entryFile);
3562
- const normalizedOptions = normalizeOpenPkgOptions(options);
3561
+ const normalizedOptions = normalizeDocCovOptions(options);
3563
3562
  const programResult = createProgram({ entryFile, baseDir, content });
3564
3563
  if (!programResult.sourceFile) {
3565
3564
  throw new Error(`Could not load ${entryFile}`);
@@ -3611,39 +3610,7 @@ function extractParameterDecorators(param) {
3611
3610
  return extractDecorators(param);
3612
3611
  }
3613
3612
 
3614
- // src/utils/parameter-utils.ts
3615
- var MAX_TYPE_DEPTH = DEFAULT_MAX_TYPE_DEPTH;
3616
- function safeTypeToString(typeChecker, type) {
3617
- try {
3618
- return typeChecker.typeToString(type);
3619
- } catch {
3620
- return "unknown";
3621
- }
3622
- }
3623
- var BUILTIN_TYPE_SCHEMAS = {
3624
- Date: { type: "string", format: "date-time" },
3625
- RegExp: { type: "object", description: "RegExp" },
3626
- Error: { type: "object" },
3627
- Promise: { type: "object" },
3628
- Map: { type: "object" },
3629
- Set: { type: "object" },
3630
- WeakMap: { type: "object" },
3631
- WeakSet: { type: "object" },
3632
- Function: { type: "object" },
3633
- ArrayBuffer: { type: "string", format: "binary" },
3634
- ArrayBufferLike: { type: "string", format: "binary" },
3635
- DataView: { type: "string", format: "binary" },
3636
- Uint8Array: { type: "string", format: "byte" },
3637
- Uint16Array: { type: "string", format: "byte" },
3638
- Uint32Array: { type: "string", format: "byte" },
3639
- Int8Array: { type: "string", format: "byte" },
3640
- Int16Array: { type: "string", format: "byte" },
3641
- Int32Array: { type: "string", format: "byte" },
3642
- Float32Array: { type: "string", format: "byte" },
3643
- Float64Array: { type: "string", format: "byte" },
3644
- BigInt64Array: { type: "string", format: "byte" },
3645
- BigUint64Array: { type: "string", format: "byte" }
3646
- };
3613
+ // src/utils/typebox-handler.ts
3647
3614
  var TYPEBOX_PRIMITIVE_MAP = {
3648
3615
  TString: { type: "string" },
3649
3616
  TNumber: { type: "number" },
@@ -3677,7 +3644,31 @@ function unwrapTypeBoxOptional(type) {
3677
3644
  const hadMarker = filtered.length < intersectionType.types.length;
3678
3645
  return { innerTypes: filtered, isOptional: hadMarker };
3679
3646
  }
3680
- function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = MAX_TYPE_DEPTH, typeIds) {
3647
+ function getPropertyType(prop, parentType, typeChecker) {
3648
+ if (prop.valueDeclaration) {
3649
+ return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
3650
+ }
3651
+ const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
3652
+ if (propType) {
3653
+ return propType;
3654
+ }
3655
+ const decl = prop.declarations?.[0];
3656
+ if (decl) {
3657
+ return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
3658
+ }
3659
+ return typeChecker.getAnyType();
3660
+ }
3661
+ var _formatTypeReference = null;
3662
+ function setFormatTypeReference(fn) {
3663
+ _formatTypeReference = fn;
3664
+ }
3665
+ function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
3666
+ if (_formatTypeReference) {
3667
+ return _formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
3668
+ }
3669
+ return { type: "object" };
3670
+ }
3671
+ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
3681
3672
  if (depth > maxDepth) {
3682
3673
  return { type: "unknown" };
3683
3674
  }
@@ -3716,7 +3707,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3716
3707
  const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3717
3708
  properties[propName] = nested ?? { type: "object" };
3718
3709
  } else {
3719
- properties[propName] = formatTypeReference(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3710
+ properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3720
3711
  }
3721
3712
  const { isOptional } = unwrapTypeBoxOptional(propType);
3722
3713
  if (propSymbolName !== "TOptional" && !isOptional) {
@@ -3744,7 +3735,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3744
3735
  type: "object"
3745
3736
  };
3746
3737
  } else {
3747
- items = formatTypeReference(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3738
+ items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3748
3739
  }
3749
3740
  return { type: "array", items };
3750
3741
  }
@@ -3765,7 +3756,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3765
3756
  type: "object"
3766
3757
  });
3767
3758
  } else {
3768
- members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3759
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3769
3760
  }
3770
3761
  }
3771
3762
  } else if (tupleType.typeArguments) {
@@ -3779,7 +3770,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3779
3770
  type: "object"
3780
3771
  });
3781
3772
  } else {
3782
- members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3773
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3783
3774
  }
3784
3775
  }
3785
3776
  }
@@ -3802,7 +3793,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3802
3793
  type: "object"
3803
3794
  });
3804
3795
  } else {
3805
- members.push(formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3796
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
3806
3797
  }
3807
3798
  }
3808
3799
  }
@@ -3822,7 +3813,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3822
3813
  type: "object"
3823
3814
  };
3824
3815
  }
3825
- return formatTypeReference(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3816
+ return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3826
3817
  }
3827
3818
  case "TLiteral": {
3828
3819
  if (!typeArgs || typeArgs.length === 0) {
@@ -3852,7 +3843,7 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3852
3843
  } else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
3853
3844
  additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
3854
3845
  } else {
3855
- additionalProperties = formatTypeReference(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3846
+ additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
3856
3847
  }
3857
3848
  return { type: "object", additionalProperties };
3858
3849
  }
@@ -3872,6 +3863,32 @@ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visit
3872
3863
  return null;
3873
3864
  }
3874
3865
  }
3866
+
3867
+ // src/utils/schema-builder.ts
3868
+ var BUILTIN_TYPE_SCHEMAS = {
3869
+ Date: { type: "string", format: "date-time" },
3870
+ RegExp: { type: "object", description: "RegExp" },
3871
+ Error: { type: "object" },
3872
+ Promise: { type: "object" },
3873
+ Map: { type: "object" },
3874
+ Set: { type: "object" },
3875
+ WeakMap: { type: "object" },
3876
+ WeakSet: { type: "object" },
3877
+ Function: { type: "object" },
3878
+ ArrayBuffer: { type: "string", format: "binary" },
3879
+ ArrayBufferLike: { type: "string", format: "binary" },
3880
+ DataView: { type: "string", format: "binary" },
3881
+ Uint8Array: { type: "string", format: "byte" },
3882
+ Uint16Array: { type: "string", format: "byte" },
3883
+ Uint32Array: { type: "string", format: "byte" },
3884
+ Int8Array: { type: "string", format: "byte" },
3885
+ Int16Array: { type: "string", format: "byte" },
3886
+ Int32Array: { type: "string", format: "byte" },
3887
+ Float32Array: { type: "string", format: "byte" },
3888
+ Float64Array: { type: "string", format: "byte" },
3889
+ BigInt64Array: { type: "string", format: "byte" },
3890
+ BigUint64Array: { type: "string", format: "byte" }
3891
+ };
3875
3892
  function isObjectLiteralType(type) {
3876
3893
  if (!(type.getFlags() & ts2.TypeFlags.Object)) {
3877
3894
  return false;
@@ -3894,20 +3911,6 @@ function withDescription(schema, description) {
3894
3911
  description
3895
3912
  };
3896
3913
  }
3897
- function getPropertyType(prop, parentType, typeChecker) {
3898
- if (prop.valueDeclaration) {
3899
- return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
3900
- }
3901
- const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
3902
- if (propType) {
3903
- return propType;
3904
- }
3905
- const decl = prop.declarations?.[0];
3906
- if (decl) {
3907
- return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
3908
- }
3909
- return typeChecker.getAnyType();
3910
- }
3911
3914
  function propertiesToSchema(properties, description) {
3912
3915
  const schema = {
3913
3916
  type: "object",
@@ -3944,6 +3947,10 @@ function propertiesToSchema(properties, description) {
3944
3947
  }
3945
3948
  return schema;
3946
3949
  }
3950
+ var _formatTypeReference2 = null;
3951
+ function setSchemaBuilderFormatTypeReference(fn) {
3952
+ _formatTypeReference2 = fn;
3953
+ }
3947
3954
  function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
3948
3955
  if (ts2.isParenthesizedTypeNode(node)) {
3949
3956
  return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
@@ -3973,7 +3980,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
3973
3980
  let schema2 = "any";
3974
3981
  if (member.type) {
3975
3982
  const memberType = typeChecker.getTypeFromTypeNode(member.type);
3976
- const formatted = formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes);
3983
+ const formatted = _formatTypeReference2 ? _formatTypeReference2(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
3977
3984
  if (typeof formatted === "string") {
3978
3985
  if (formatted === "any") {
3979
3986
  schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
@@ -4154,7 +4161,16 @@ function deduplicateSchemas(schemas) {
4154
4161
  }
4155
4162
  return result;
4156
4163
  }
4157
- function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visitedAliases, depth = 0, maxDepth = MAX_TYPE_DEPTH, visitedTypeIds) {
4164
+
4165
+ // src/utils/type-formatter.ts
4166
+ function safeTypeToString(typeChecker, type) {
4167
+ try {
4168
+ return typeChecker.typeToString(type);
4169
+ } catch {
4170
+ return "unknown";
4171
+ }
4172
+ }
4173
+ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visitedAliases, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, visitedTypeIds) {
4158
4174
  if (depth > maxDepth) {
4159
4175
  return { type: "unknown" };
4160
4176
  }
@@ -4351,6 +4367,9 @@ function formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visit
4351
4367
  }
4352
4368
  }
4353
4369
  }
4370
+ setFormatTypeReference(formatTypeReference);
4371
+ setSchemaBuilderFormatTypeReference(formatTypeReference);
4372
+ // src/utils/parameter-utils.ts
4354
4373
  function structureParameter(param, paramDecl, paramType, typeChecker, typeRefs, functionDoc, paramDoc, referencedTypes) {
4355
4374
  const paramName = param.getName();
4356
4375
  const isDestructured = paramName === "__0" || ts2.isObjectBindingPattern(paramDecl.name) || ts2.isArrayBindingPattern(paramDecl.name);
@@ -6147,10 +6166,30 @@ class TypeRegistry {
6147
6166
  }
6148
6167
 
6149
6168
  // src/analysis/spec-builder.ts
6150
- function buildOpenPkgSpec(context, resolveExternalTypes) {
6151
- const { baseDir, checker: typeChecker, sourceFile, program } = context;
6169
+ function createDefaultGenerationInfo(entryFile) {
6170
+ return {
6171
+ timestamp: new Date().toISOString(),
6172
+ generator: {
6173
+ name: "@doccov/sdk",
6174
+ version: "0.0.0"
6175
+ },
6176
+ analysis: {
6177
+ entryPoint: entryFile,
6178
+ entryPointSource: "explicit",
6179
+ isDeclarationOnly: entryFile.endsWith(".d.ts"),
6180
+ resolvedExternalTypes: false
6181
+ },
6182
+ environment: {
6183
+ hasNodeModules: false
6184
+ },
6185
+ issues: []
6186
+ };
6187
+ }
6188
+ function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
6189
+ const { baseDir, checker: typeChecker, sourceFile, program, entryFile } = context;
6152
6190
  const packageJsonPath = path9.join(baseDir, "package.json");
6153
6191
  const packageJson = fs8.existsSync(packageJsonPath) ? JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8")) : {};
6192
+ const generationInfo = generation ?? createDefaultGenerationInfo(path9.relative(baseDir, entryFile));
6154
6193
  const spec = {
6155
6194
  $schema: SCHEMA_URL,
6156
6195
  openpkg: SCHEMA_VERSION,
@@ -6163,7 +6202,8 @@ function buildOpenPkgSpec(context, resolveExternalTypes) {
6163
6202
  ecosystem: "js/ts"
6164
6203
  },
6165
6204
  exports: [],
6166
- types: []
6205
+ types: [],
6206
+ generation: generationInfo
6167
6207
  };
6168
6208
  const typeRegistry = new TypeRegistry;
6169
6209
  const serializerContext = {
@@ -6480,7 +6520,7 @@ function hasExternalImports(sourceFile) {
6480
6520
  });
6481
6521
  return found;
6482
6522
  }
6483
- function runAnalysis(input) {
6523
+ function runAnalysis(input, generationInput) {
6484
6524
  const context = createAnalysisContext(input);
6485
6525
  const { baseDir, options, program } = context;
6486
6526
  const packageJsonPath = findNearestPackageJson(baseDir);
@@ -6493,30 +6533,65 @@ function runAnalysis(input) {
6493
6533
  `);
6494
6534
  return !/allowJs/i.test(msg);
6495
6535
  });
6496
- const spec = buildOpenPkgSpec(context, resolveExternalTypes);
6497
6536
  const specDiagnostics = [];
6537
+ const generationIssues = [];
6498
6538
  if (!hasNodeModules && hasExternalImports(context.sourceFile)) {
6499
- specDiagnostics.push({
6539
+ const issue = {
6540
+ code: "NO_NODE_MODULES",
6500
6541
  message: "External imports detected but node_modules not found.",
6501
6542
  severity: "info",
6502
6543
  suggestion: "Run npm install or bun install for complete type resolution."
6503
- });
6504
- }
6544
+ };
6545
+ specDiagnostics.push(issue);
6546
+ generationIssues.push(issue);
6547
+ }
6548
+ const generation = generationInput ? {
6549
+ timestamp: new Date().toISOString(),
6550
+ generator: {
6551
+ name: generationInput.generatorName,
6552
+ version: generationInput.generatorVersion
6553
+ },
6554
+ analysis: {
6555
+ entryPoint: generationInput.entryPoint,
6556
+ entryPointSource: generationInput.entryPointSource,
6557
+ isDeclarationOnly: generationInput.isDeclarationOnly ?? false,
6558
+ resolvedExternalTypes: resolveExternalTypes,
6559
+ maxTypeDepth: options.maxDepth
6560
+ },
6561
+ environment: {
6562
+ packageManager: generationInput.packageManager,
6563
+ hasNodeModules,
6564
+ isMonorepo: generationInput.isMonorepo,
6565
+ targetPackage: generationInput.targetPackage
6566
+ },
6567
+ issues: generationIssues
6568
+ } : undefined;
6569
+ const spec = buildOpenPkgSpec(context, resolveExternalTypes, generation);
6505
6570
  const danglingRefs = collectDanglingRefs(spec);
6506
6571
  for (const ref of danglingRefs) {
6507
- specDiagnostics.push({
6572
+ const issue = {
6573
+ code: "DANGLING_REF",
6508
6574
  message: `Type '${ref}' is referenced but not defined in types[].`,
6509
6575
  severity: "warning",
6510
6576
  suggestion: hasNodeModules ? "The type may be from an external package. Check import paths." : "Run npm/bun install to resolve external types."
6511
- });
6577
+ };
6578
+ specDiagnostics.push(issue);
6579
+ if (generation) {
6580
+ generation.issues.push(issue);
6581
+ }
6512
6582
  }
6513
6583
  const externalTypes = collectExternalTypes(spec);
6514
6584
  if (externalTypes.length > 0) {
6515
- specDiagnostics.push({
6585
+ const issue = {
6586
+ code: "EXTERNAL_TYPE_STUBS",
6516
6587
  message: `${externalTypes.length} external type(s) could not be fully resolved: ${externalTypes.slice(0, 5).join(", ")}${externalTypes.length > 5 ? "..." : ""}`,
6517
6588
  severity: "warning",
6518
6589
  suggestion: hasNodeModules ? "Types are from external packages. Full resolution requires type declarations." : "Run npm/bun install to resolve external type definitions."
6519
- });
6590
+ };
6591
+ specDiagnostics.push(issue);
6592
+ if (generation) {
6593
+ generation.issues.push(issue);
6594
+ }
6520
6595
  }
6521
6596
  const sourceFiles = program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.startsWith(baseDir)).map((sf) => sf.fileName);
6522
6597
  return {
@@ -8176,7 +8251,7 @@ class DocCov {
8176
8251
  packageDir,
8177
8252
  content: code,
8178
8253
  options: this.options
8179
- });
8254
+ }, analyzeOptions.generationInput);
8180
8255
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8181
8256
  return {
8182
8257
  spec: filterOutcome.spec,
@@ -8211,7 +8286,7 @@ class DocCov {
8211
8286
  packageDir,
8212
8287
  content,
8213
8288
  options: this.options
8214
- });
8289
+ }, analyzeOptions.generationInput);
8215
8290
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8216
8291
  const metadata = this.normalizeMetadata(analysis.metadata);
8217
8292
  const result = {
@@ -8374,7 +8449,6 @@ async function analyze(code, options = {}) {
8374
8449
  async function analyzeFile(filePath, options = {}) {
8375
8450
  return new DocCov().analyzeFile(filePath, options);
8376
8451
  }
8377
- var OpenPkg = DocCov;
8378
8452
  function resolvePackageDir(entryFile) {
8379
8453
  const fallbackDir = path11.dirname(entryFile);
8380
8454
  let currentDir = fallbackDir;
@@ -8486,130 +8560,199 @@ function extractSpecSummary(spec) {
8486
8560
  drift
8487
8561
  };
8488
8562
  }
8489
-
8490
- // src/scan/orchestrator.ts
8491
- class ScanOrchestrator {
8492
- fs;
8493
- options;
8494
- constructor(fs11, options = {}) {
8495
- this.fs = fs11;
8496
- this.options = options;
8497
- }
8498
- emit(event) {
8499
- this.options.onProgress?.(event);
8500
- }
8501
- async detectPackage(packageName) {
8502
- this.emit({ stage: "detecting", message: "Detecting project structure...", progress: 10 });
8503
- const mono = await detectMonorepo(this.fs);
8504
- if (mono.isMonorepo) {
8505
- if (!packageName) {
8506
- const publicPackages = mono.packages.filter((p) => !p.private);
8507
- throw new MonorepoRequiresPackageError(publicPackages.map((p) => p.name));
8508
- }
8509
- const pkg = findPackageByName(mono.packages, packageName);
8510
- if (!pkg) {
8511
- throw new Error(`Package "${packageName}" not found. Available: ${mono.packages.map((p) => p.name).join(", ")}`);
8512
- }
8513
- this.emit({ stage: "detecting", message: `Found package: ${pkg.name}`, progress: 15 });
8514
- return { targetPath: pkg.path, resolvedPackage: pkg.name };
8515
- }
8516
- return { targetPath: "." };
8517
- }
8518
- async detectEntry(targetPath) {
8519
- this.emit({ stage: "detecting", message: "Detecting entry point...", progress: 18 });
8520
- const entry = await detectEntryPoint(this.fs, targetPath);
8521
- const entryFile = targetPath === "." ? entry.path : `${targetPath}/${entry.path}`;
8522
- this.emit({
8523
- stage: "detecting",
8524
- message: `Entry point: ${entry.path} (from ${entry.source})`,
8525
- progress: 20
8526
- });
8527
- return entryFile;
8563
+ // src/scan/github-context.ts
8564
+ function parseGitHubUrl2(url) {
8565
+ try {
8566
+ const { owner, repo } = parseGitHubUrl(url);
8567
+ return { owner, repo };
8568
+ } catch {
8569
+ return null;
8528
8570
  }
8529
- async install(workDir) {
8530
- if (!this.options.commandRunner) {
8531
- return {
8532
- success: false,
8533
- packageManager: "npm",
8534
- error: "No command runner provided"
8535
- };
8571
+ }
8572
+ async function fetchRawFile(owner, repo, ref, path13) {
8573
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path13}`;
8574
+ try {
8575
+ const response = await fetch(url);
8576
+ if (response.ok) {
8577
+ return await response.text();
8536
8578
  }
8537
- this.emit({ stage: "installing", message: "Installing dependencies...", progress: 25 });
8538
- const result = await installDependencies(this.fs, workDir, this.options.commandRunner, {
8539
- onProgress: this.options.onProgress
8540
- });
8541
- if (result.success) {
8542
- this.emit({ stage: "installing", message: "Dependencies installed", progress: 45 });
8543
- } else {
8544
- this.emit({
8545
- stage: "installing",
8546
- message: "Install failed (continuing with limited analysis)",
8547
- progress: 45
8548
- });
8579
+ return null;
8580
+ } catch {
8581
+ return null;
8582
+ }
8583
+ }
8584
+ async function fetchRepoMetadata(owner, repo) {
8585
+ const url = `https://api.github.com/repos/${owner}/${repo}`;
8586
+ const response = await fetch(url, {
8587
+ headers: {
8588
+ Accept: "application/vnd.github.v3+json",
8589
+ "User-Agent": "DocCov-Scanner"
8549
8590
  }
8550
- return result;
8591
+ });
8592
+ if (!response.ok) {
8593
+ throw new Error(`Failed to fetch repository: ${response.status} ${response.statusText}`);
8551
8594
  }
8552
- async build(workDir, targetPath) {
8553
- if (!this.options.commandRunner)
8554
- return;
8555
- const buildInfo = await detectBuildInfo(this.fs, targetPath);
8556
- const buildScript = getPrimaryBuildScript(buildInfo);
8557
- if (!buildScript)
8558
- return;
8559
- this.emit({ stage: "building", message: "Running build...", progress: 50 });
8560
- const result = await this.options.commandRunner("npm", ["run", buildScript], {
8561
- cwd: workDir,
8562
- timeout: 300000
8563
- });
8564
- const buildMessage = result.exitCode === 0 ? "Build complete" : "Build failed (continuing)";
8565
- this.emit({ stage: "building", message: buildMessage, progress: 60 });
8566
- }
8567
- async analyze(entryFile) {
8568
- this.emit({ stage: "analyzing", message: "Analyzing documentation...", progress: 65 });
8569
- const doccov = new DocCov({ resolveExternalTypes: !this.options.skipResolve });
8570
- const result = await doccov.analyzeFileWithDiagnostics(entryFile);
8571
- this.emit({ stage: "analyzing", message: "Analysis complete", progress: 90 });
8572
- return result.spec;
8573
- }
8574
- async scan(options) {
8575
- const parsed = parseGitHubUrl(options.url, options.ref ?? "main");
8576
- this.emit({
8577
- stage: "detecting",
8578
- message: `Scanning ${parsed.owner}/${parsed.repo}...`,
8579
- progress: 5
8580
- });
8581
- const { targetPath, resolvedPackage } = await this.detectPackage(options.package);
8582
- const entryFile = await this.detectEntry(targetPath);
8583
- if (!options.skipInstall && this.options.commandRunner) {
8584
- await this.install(".");
8585
- await this.build(".", targetPath);
8586
- }
8587
- const spec = await this.analyze(entryFile);
8588
- this.emit({ stage: "complete", message: "Extracting results...", progress: 95 });
8589
- const summary = extractSpecSummary(spec);
8590
- this.emit({ stage: "complete", message: "Scan complete", progress: 100 });
8595
+ const data = await response.json();
8596
+ return {
8597
+ owner,
8598
+ repo,
8599
+ defaultBranch: data.default_branch,
8600
+ description: data.description,
8601
+ language: data.language,
8602
+ topics: data.topics ?? [],
8603
+ isPrivate: data.private
8604
+ };
8605
+ }
8606
+ async function detectPackageManager3(owner, repo, ref) {
8607
+ const lockfiles = [
8608
+ { name: "bun.lockb", manager: "bun" },
8609
+ { name: "pnpm-lock.yaml", manager: "pnpm" },
8610
+ { name: "yarn.lock", manager: "yarn" },
8611
+ { name: "package-lock.json", manager: "npm" }
8612
+ ];
8613
+ for (const { name, manager } of lockfiles) {
8614
+ const content = await fetchRawFile(owner, repo, ref, name);
8615
+ if (content !== null) {
8616
+ return { manager, lockfile: { name, content: content.slice(0, 1e4) } };
8617
+ }
8618
+ }
8619
+ return { manager: "unknown" };
8620
+ }
8621
+ async function detectWorkspace(packageJson, owner, repo, ref) {
8622
+ const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml");
8623
+ if (pnpmWorkspace) {
8624
+ const packagesMatch = pnpmWorkspace.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
8625
+ const packages = packagesMatch ? packagesMatch[1].split(`
8626
+ `).map((line) => line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, "")).filter(Boolean) : undefined;
8591
8627
  return {
8592
- owner: parsed.owner,
8593
- repo: parsed.repo,
8594
- ref: parsed.ref,
8595
- packageName: resolvedPackage ?? options.package,
8596
- coverage: summary.coverage,
8597
- exportCount: summary.exportCount,
8598
- typeCount: summary.typeCount,
8599
- driftCount: summary.driftCount,
8600
- undocumented: summary.undocumented,
8601
- drift: summary.drift
8628
+ isMonorepo: true,
8629
+ tool: "pnpm",
8630
+ packages
8602
8631
  };
8603
8632
  }
8633
+ const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json");
8634
+ if (lernaJson) {
8635
+ return { isMonorepo: true, tool: "lerna" };
8636
+ }
8637
+ if (packageJson && typeof packageJson === "object") {
8638
+ const pkg = packageJson;
8639
+ if (pkg.workspaces) {
8640
+ const workspaces = pkg.workspaces;
8641
+ const packages = Array.isArray(workspaces) ? workspaces : workspaces.packages;
8642
+ return {
8643
+ isMonorepo: true,
8644
+ tool: "npm",
8645
+ packages: packages?.filter((p) => typeof p === "string")
8646
+ };
8647
+ }
8648
+ }
8649
+ return { isMonorepo: false };
8604
8650
  }
8605
-
8606
- class MonorepoRequiresPackageError extends Error {
8607
- availablePackages;
8608
- constructor(availablePackages) {
8609
- super(`Monorepo detected with ${availablePackages.length} packages. ` + `Specify target with --package. Available: ${availablePackages.join(", ")}`);
8610
- this.name = "MonorepoRequiresPackageError";
8611
- this.availablePackages = availablePackages;
8651
+ function detectBuildHints(packageJson, tsconfigJson) {
8652
+ const hints = {
8653
+ hasTypeScript: false,
8654
+ hasWasm: false,
8655
+ hasNativeModules: false,
8656
+ hasBuildScript: false,
8657
+ frameworks: []
8658
+ };
8659
+ if (!packageJson || typeof packageJson !== "object") {
8660
+ return hints;
8661
+ }
8662
+ const pkg = packageJson;
8663
+ const deps = {
8664
+ ...typeof pkg.dependencies === "object" ? pkg.dependencies : {},
8665
+ ...typeof pkg.devDependencies === "object" ? pkg.devDependencies : {}
8666
+ };
8667
+ hints.hasTypeScript = "typescript" in deps || tsconfigJson !== null;
8668
+ hints.hasWasm = "wasm-pack" in deps || "@aspect-build/rules_esbuild" in deps;
8669
+ hints.hasNativeModules = "node-gyp" in deps || "prebuild" in deps || "napi-rs" in deps;
8670
+ const scripts = typeof pkg.scripts === "object" ? pkg.scripts : {};
8671
+ if (scripts.build) {
8672
+ hints.hasBuildScript = true;
8673
+ hints.buildScript = scripts.build;
8674
+ }
8675
+ const frameworkDeps = [
8676
+ { dep: "react", name: "React" },
8677
+ { dep: "vue", name: "Vue" },
8678
+ { dep: "svelte", name: "Svelte" },
8679
+ { dep: "next", name: "Next.js" },
8680
+ { dep: "nuxt", name: "Nuxt" },
8681
+ { dep: "astro", name: "Astro" },
8682
+ { dep: "express", name: "Express" },
8683
+ { dep: "fastify", name: "Fastify" },
8684
+ { dep: "hono", name: "Hono" }
8685
+ ];
8686
+ for (const { dep, name } of frameworkDeps) {
8687
+ if (dep in deps) {
8688
+ hints.frameworks.push(name);
8689
+ }
8690
+ }
8691
+ return hints;
8692
+ }
8693
+ async function fetchGitHubContext(repoUrl, ref) {
8694
+ const parsed = parseGitHubUrl2(repoUrl);
8695
+ if (!parsed) {
8696
+ throw new Error(`Invalid GitHub URL: ${repoUrl}`);
8697
+ }
8698
+ const { owner, repo } = parsed;
8699
+ const metadata = await fetchRepoMetadata(owner, repo);
8700
+ const targetRef = ref ?? metadata.defaultBranch;
8701
+ const [packageJsonRaw, tsconfigJsonRaw, pmResult] = await Promise.all([
8702
+ fetchRawFile(owner, repo, targetRef, "package.json"),
8703
+ fetchRawFile(owner, repo, targetRef, "tsconfig.json"),
8704
+ detectPackageManager3(owner, repo, targetRef)
8705
+ ]);
8706
+ let packageJson = null;
8707
+ let tsconfigJson = null;
8708
+ if (packageJsonRaw) {
8709
+ try {
8710
+ packageJson = JSON.parse(packageJsonRaw);
8711
+ } catch {}
8712
+ }
8713
+ if (tsconfigJsonRaw) {
8714
+ try {
8715
+ tsconfigJson = JSON.parse(tsconfigJsonRaw);
8716
+ } catch {}
8717
+ }
8718
+ const workspace = await detectWorkspace(packageJson, owner, repo, targetRef);
8719
+ const buildHints = detectBuildHints(packageJson, tsconfigJson);
8720
+ return {
8721
+ metadata,
8722
+ ref: targetRef,
8723
+ packageManager: pmResult.manager,
8724
+ workspace,
8725
+ buildHints,
8726
+ files: {
8727
+ packageJson: packageJsonRaw ?? undefined,
8728
+ tsconfigJson: tsconfigJsonRaw ?? undefined,
8729
+ lockfile: pmResult.lockfile
8730
+ }
8731
+ };
8732
+ }
8733
+ async function listWorkspacePackages(owner, repo, ref, patterns) {
8734
+ const packages = [];
8735
+ for (const pattern of patterns) {
8736
+ const baseDir = pattern.replace(/\/\*.*$/, "");
8737
+ try {
8738
+ const url = `https://api.github.com/repos/${owner}/${repo}/contents/${baseDir}?ref=${ref}`;
8739
+ const response = await fetch(url, {
8740
+ headers: {
8741
+ Accept: "application/vnd.github.v3+json",
8742
+ "User-Agent": "DocCov-Scanner"
8743
+ }
8744
+ });
8745
+ if (response.ok) {
8746
+ const contents = await response.json();
8747
+ for (const item of contents) {
8748
+ if (item.type === "dir") {
8749
+ packages.push(`${baseDir}/${item.name}`);
8750
+ }
8751
+ }
8752
+ }
8753
+ } catch {}
8612
8754
  }
8755
+ return packages;
8613
8756
  }
8614
8757
  export {
8615
8758
  validateSpecCache,
@@ -8626,6 +8769,7 @@ export {
8626
8769
  runExample,
8627
8770
  resolveTarget,
8628
8771
  readPackageJson,
8772
+ parseGitHubUrl2 as parseScanGitHubUrl,
8629
8773
  parseMarkdownFiles,
8630
8774
  parseMarkdownFile,
8631
8775
  parseListFlag,
@@ -8638,6 +8782,7 @@ export {
8638
8782
  mergeConfig,
8639
8783
  loadSpecCache,
8640
8784
  loadCachedReport,
8785
+ listWorkspacePackages,
8641
8786
  isFixableDrift,
8642
8787
  isExecutableLang,
8643
8788
  isCachedReportValid,
@@ -8676,6 +8821,7 @@ export {
8676
8821
  findDeprecatedReferences,
8677
8822
  fetchSpecFromGitHub,
8678
8823
  fetchSpec,
8824
+ fetchGitHubContext,
8679
8825
  extractSpecSummary,
8680
8826
  extractPackageSpec,
8681
8827
  extractImports,
@@ -8713,15 +8859,12 @@ export {
8713
8859
  analyzeDocsImpact,
8714
8860
  analyze,
8715
8861
  VALIDATION_INFO,
8716
- ScanOrchestrator,
8717
8862
  SandboxFileSystem,
8718
8863
  STYLE_RULES,
8719
8864
  SPEC_CACHE_FILE,
8720
8865
  REPORT_VERSION,
8721
8866
  REPORT_EXTENSIONS,
8722
- OpenPkg,
8723
8867
  NodeFileSystem,
8724
- MonorepoRequiresPackageError,
8725
8868
  DocCov,
8726
8869
  DEFAULT_REPORT_PATH,
8727
8870
  DEFAULT_REPORT_DIR,