Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Version 0.22.1
- feat: Support git references for package analysis
- Supports both HTTPS and SSH git repository formats
- Optional branch/tag/commit specification (e.g., git://https://github.com/user/repo:branch)
- Automatic cloning, dependency resolution, and cleanup
- Formats: git://https://github.com/user/repo or git://[email protected]:user/repo
- fix: package name resolving for types referenced via a package-reference ('package:some_package/some_entrypoint.dart)
- fix: private top level methods where treated as part of the public API (resulting in all types used there treated as part of the public API as well)

## Version 0.22.0
- fix: Fixes an issue if we have to deal with two types with the same name
- feat: add support for @internal annotations
Expand Down
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Usage: dart-apitool extract [arguments]
(e.g. /path/to/package)
- any package from pub
(e.g. pub://package_name/version)
- git repository with optional branch/tag/commit
(e.g. git://https://github.com/user/repo or git://https://github.com/user/repo:branch)
(e.g. git://[email protected]:user/repo or git://[email protected]:user/repo:tag)
-p, --[no-]include-path-dependencies OBSOLETE: Has no effect anymore.
--output Output file for the extracted Package API.
If not specified the extracted API will be printed to the console.
Expand All @@ -78,11 +81,17 @@ Usage: dart-apitool diff [arguments]
(e.g. /path/to/package)
- any package from pub
(e.g. pub://package_name/version)
- git repository with optional branch/tag/commit
(e.g. git://https://github.com/user/repo or git://https://github.com/user/repo:branch)
(e.g. git://[email protected]:user/repo or git://[email protected]:user/repo:tag)
--new (mandatory) New package reference. Package reference can be one of:
- directory path pointing to a package on disk
(e.g. /path/to/package)
- any package from pub
(e.g. pub://package_name/version)
- git repository with optional branch/tag/commit
(e.g. git://https://github.com/user/repo or git://https://github.com/user/repo:branch)
(e.g. git://[email protected]:user/repo or git://[email protected]:user/repo:tag)
-p, --[no-]include-path-dependencies OBSOLETE: Has no effect anymore.
--version-check-mode Defines the mode the versions of the packages shall be compared.
This affects the exit code of this program.
Expand Down Expand Up @@ -110,6 +119,53 @@ Usage: dart-apitool diff [arguments]
Run "dart-apitool help" to see global options.
```

## Package References

Dart API Tool supports three types of package references:

### Directory Path
Points directly to a package directory on your local filesystem:
```bash
dart-apitool extract --input /path/to/my/package
```

### Pub Package
References a published package from pub.dev:
```bash
dart-apitool extract --input pub://package_name/1.0.0
```
If you omit the version, the latest version will be used:
```bash
dart-apitool extract --input pub://package_name
```

### Git Repository
References a package directly from a git repository. Supports both HTTPS and SSH formats:

**HTTPS format:**
```bash
# Clone the default branch
dart-apitool extract --input git://https://github.com/user/repo

# Clone a specific branch, tag, or commit
dart-apitool extract --input git://https://github.com/user/repo:main
dart-apitool extract --input git://https://github.com/user/repo:v1.0.0
dart-apitool extract --input git://https://github.com/user/repo:abc123
```

**SSH format:**
```bash
# Clone the default branch
dart-apitool extract --input git://[email protected]:user/repo

# Clone a specific branch, tag, or commit
dart-apitool extract --input git://[email protected]:user/repo:main
dart-apitool extract --input git://[email protected]:user/repo:v1.0.0
dart-apitool extract --input git://[email protected]:user/repo:abc123
```

Git repositories are automatically cloned to temporary directories and cleaned up after analysis. The tool will run `pub get` to ensure all dependencies are properly resolved.

## Integration

How to best integrate `dart-apitool` in your CI and release process highly depends on your setup.
Expand All @@ -135,6 +191,28 @@ version="${version#v}" # the new tag format (e.g. v0.12.0)
```
You can see this in action in the [workflow](.github/workflows/ci.yml) of this repository.

### Git Repository Examples

Git references are particularly useful when:
- Comparing against unreleased changes in a development branch
- Analyzing forks or private repositories
- Working with packages not published to pub.dev

**Compare current directory against a specific git tag:**
```bash
dart-apitool diff --old git://https://github.com/user/repo:v1.0.0 --new .
```

**Compare two different git branches:**
```bash
dart-apitool diff --old git://https://github.com/user/repo:main --new git://https://github.com/user/repo:develop
```

**Extract API from a private repository using SSH:**
```bash
dart-apitool extract --input git://[email protected]:company/private-repo:main --output api.json
```

For your convenience there is a reusable workflow that you can integrate in your workflow.
```yml
semver:
Expand Down
17 changes: 10 additions & 7 deletions lib/src/analyze/api_relevant_elements_collector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -372,23 +372,25 @@ class APIRelevantElementsCollector extends RecursiveElementVisitor2<void> {

@override
void visitTopLevelFunctionElement(TopLevelFunctionElement element) {
_onVisitFunctionElement(element);
super.visitTopLevelFunctionElement(element);
if (_onVisitFunctionElement(element)) {
super.visitTopLevelFunctionElement(element);
}
}

@override
void visitLocalFunctionElement(LocalFunctionElement element) {
_onVisitFunctionElement(element);
super.visitLocalFunctionElement(element);
if (_onVisitFunctionElement(element)) {
super.visitLocalFunctionElement(element);
}
}

void _onVisitFunctionElement(ExecutableElement2 element) {
bool _onVisitFunctionElement(ExecutableElement2 element) {
_onVisitAnyElement(element);
if (!_isElementAllowedToBeCollected(element)) {
return;
return false;
}
if (!_markElementAsCollected(element)) {
return;
return false;
}
_executableDeclarations
.add(InternalExecutableDeclaration.fromExecutableElement(
Expand All @@ -400,6 +402,7 @@ class APIRelevantElementsCollector extends RecursiveElementVisitor2<void> {
_onTypeUsed(element.returnType, element,
typeUsageKind: TypeUsageKind.output);
}
return true;
}

@override
Expand Down
1 change: 1 addition & 0 deletions lib/src/cli/cli.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'commands/commands.dart';
export 'git_ref.dart';
export 'package_ref.dart';
export 'prepared_package_ref.dart';
91 changes: 74 additions & 17 deletions lib/src/cli/commands/command_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:dart_apitool/api_tool.dart';
import 'package:dart_apitool/src/cli/source_item.dart';
import 'package:path/path.dart' as p;

import '../git_ref.dart';
import '../package_ref.dart';
import '../prepared_package_ref.dart';

Expand All @@ -16,6 +17,9 @@ Package reference can be one of:
(e.g. /path/to/package)
- any package from pub
(e.g. pub://package_name/version)
- git repository with optional branch/tag/commit
(e.g. git://https://github.com/user/repo or git://https://github.com/user/repo:branch)
(e.g. git://[email protected]:user/repo or git://[email protected]:user/repo:tag)
''';

final includePathDependenciesExplanation = '''
Expand Down Expand Up @@ -80,6 +84,18 @@ OBSOLETE: Has no effect anymore.
sourceDir: sourceDir,
isInCache: true,
));
} else if (ref.isGitRef) {
final gitRefObj = ref.gitRef!;
await stdoutSession.writeln('Preparing git repository: ${gitRefObj.uri}');
if (gitRefObj.ref != null) {
await stdoutSession.writeln('Using ref: ${gitRefObj.ref}');
}
// For git repositories, we'll clone directly to the temp directory later
// Store the git info in a special SourceItem
sources.add(SourceItem(
sourceDir: gitRefObj.toInternalString(),
isInCache: false, // Treat git repos like local dirs - they need pub get
));
} else {
throw ArgumentError('Unknown package ref: ${ref.ref}');
}
Expand All @@ -88,45 +104,82 @@ OBSOLETE: Has no effect anymore.
sources.removeWhere((sToRemove) =>
sources.any((s) => p.isWithin(s.sourceDir, sToRemove.sourceDir)));
final tempDir = await Directory.systemTemp.createTemp();
String? gitPackageRelativePath; // Store the relative path for git packages
await Future.forEach<SourceItem>(sources, (sourceItem) async {
await stdoutSession
.writeln('Copying sources from ${sourceItem.sourceDir}');
await _copyPath(sourceItem.sourceDir,
sourceItem.destinationPath(forPrefix: tempDir.path));
if (!sourceItem.isInCache) {
if (sourceItem.sourceDir.startsWith('GIT:')) {
// Handle git repository
final gitRef = GitRef.fromInternalString(sourceItem.sourceDir);

final targetDir = sourceItem.destinationPath(forPrefix: tempDir.path);
final packageDir = await GitInteraction.cloneRepositoryToDirectory(
gitRef.uri,
targetDir,
gitRef.ref,
gitRef.path,
stdoutSession: stdoutSession,
);

// Calculate the relative path from tempDir to packageDir
gitPackageRelativePath = p.relative(packageDir, from: tempDir.path);

String forceUseFlutterSuffix = '';
if (forceUseFlutterTool) {
forceUseFlutterSuffix = ' (forced Flutter)';
}

await stdoutSession.writeln(
'Preparing package dependencies for local package ${sourceItem.sourceDir}$forceUseFlutterSuffix');
'Preparing package dependencies for git package ${gitRef.uri}$forceUseFlutterSuffix');
await PubInteraction.runPubGet(
sourceItem.sourceDir,
packageDir,
stdoutSession: stdoutSession,
forceUseFlutterTool: forceUseFlutterTool,
);
await DartInteraction.transferPackageConfig(
fromPackage: sourceItem.sourceDir,
fromPackage: packageDir,
toPackage: tempDir.path,
stdoutSession: stdoutSession,
);
} else {
await stdoutSession.writeln('Cleaning up local copy of pub package');
// Check if we have a pub package that bundles a pubspec_overrides.yaml (as this most probably destroys pub get)
final pubspecOverrides = File(p.join(
sourceItem.destinationPath(forPrefix: tempDir.path),
'pubspec_overrides.yaml'));
if (await pubspecOverrides.exists()) {
await pubspecOverrides.delete();
await stdoutSession.writeln('- Removed pubspec_overrides.yaml');
// Handle regular directories and pub packages
await stdoutSession
.writeln('Copying sources from ${sourceItem.sourceDir}');
await _copyPath(sourceItem.sourceDir,
sourceItem.destinationPath(forPrefix: tempDir.path));
if (!sourceItem.isInCache) {
String forceUseFlutterSuffix = '';
if (forceUseFlutterTool) {
forceUseFlutterSuffix = ' (forced Flutter)';
}

await stdoutSession.writeln(
'Preparing package dependencies for local package ${sourceItem.sourceDir}$forceUseFlutterSuffix');
await PubInteraction.runPubGet(
sourceItem.sourceDir,
stdoutSession: stdoutSession,
forceUseFlutterTool: forceUseFlutterTool,
);
await DartInteraction.transferPackageConfig(
fromPackage: sourceItem.sourceDir,
toPackage: tempDir.path,
stdoutSession: stdoutSession,
);
} else {
await stdoutSession.writeln('Cleaning up local copy of pub package');
// Check if we have a pub package that bundles a pubspec_overrides.yaml (as this most probably destroys pub get)
final pubspecOverrides = File(p.join(
sourceItem.destinationPath(forPrefix: tempDir.path),
'pubspec_overrides.yaml'));
if (await pubspecOverrides.exists()) {
await pubspecOverrides.delete();
await stdoutSession.writeln('- Removed pubspec_overrides.yaml');
}
}
}
});
return PreparedPackageRef(
packageRef: ref,
tempDirectory: tempDir.path,
packageRelativePath: packageRelativePath);
packageRelativePath: gitPackageRelativePath ?? packageRelativePath);
}

/// Analyzes the given prepared Package [ref].
Expand All @@ -151,6 +204,10 @@ OBSOLETE: Has no effect anymore.
preparedRef.packageRef.pubPackage!,
preparedRef.packageRef.pubVersion);
}
if (preparedRef.packageRef.isGitRef) {
// For git references, use the temp directory where the repository was cloned
path = preparedRef.tempDirectory;
}
if (path == null) {
throw ArgumentError(
'Don\'t know how to handle ${preparedRef.packageRef.ref}');
Expand Down
Loading
Loading