Skip to content
Draft
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
4 changes: 4 additions & 0 deletions cli/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
include: package:very_good_analysis/analysis_options.yaml
analyzer:
errors:
public_member_api_docs: ignore
avoid_catches_without_on_clauses: ignore
83 changes: 80 additions & 3 deletions cli/bin/shadcn.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,82 @@
import 'package:shadcn/shadcn.dart' as shadcn;
import 'dart:async';
import 'dart:io';

void main(List<String> arguments) {
print('Hello world: ${shadcn.calculate()}!');
import 'package:cli_tools/cli_tools.dart' hide Logger;
import 'package:cli_util/cli_logging.dart';
import 'package:shadcn/shadcn.dart';

late Logger logger;

enum Options implements OptionDefinition<String> {
component(
StringOption(
argName: 'component',
helpText: 'The component to add',
argPos: 0,
allowedValues: components,
mandatory: true,
),
);

const Options(this.option);

@override
final ConfigOptionBase<String> option;
}

class AddComponentCommand extends BetterCommand<Options, void> {
AddComponentCommand() : super(options: Options.values);

@override
String get name => 'add';

@override
String get description => 'Add a component to the project.';

@override
Future<void> runWithConfig(Configuration<Options> commandConfig) async {
final component = commandConfig.value(Options.component);
logger.progress('Adding component: $component');
// final result = await GitOperations.fetchFileFromGitHub(
// path: 'lib/src/components/$component.dart',
// );
final button = File(
'/Users/ale/github/flutter-shadcn-ui/lib/src/components/button.dart',
);
final bytes = await button.readAsBytes();
final localHash = GitOperations.calculateHashOfFile(bytes);
print('localHash: $localHash');
// print(result);
logger.stdout('finish');
}
}

Future<int> main(List<String> arguments) async {
final verbose = arguments.contains('-v');
logger = Logger.verbose(logTime: verbose);

final requirements = Requirements([
IsGitInstalledRequirement(),
IsGitRepositoryRequirement(),
GitTopLevelRepositoryRequirement(),
PubspecExistsRequirement(),
MinFlutterVersionRequirement(),
]);

final result = await requirements.checkAll();
if (!result.success) {
logger.stderr(result.errorMessage!);
if (result.internalError != null && verbose) {
logger.stderr(result.internalError!.toString());
}
return 1;
}

final runner = BetterCommandRunner<OptionDefinition<dynamic>, dynamic>(
'shadcn',
'A CLI for installing Flutter shadcn components',
globalOptions: [StandardGlobalOption.quiet, StandardGlobalOption.verbose],
)..addCommand(AddComponentCommand());
await runner.run(arguments);
return 0;
}
11 changes: 8 additions & 3 deletions cli/lib/shadcn.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
int calculate() {
return 6 * 7;
}
export 'src/constants.dart';
export 'src/git_operations.dart';
export 'src/models/pubspec.dart';
export 'src/pubspec_parser.dart';
export 'src/requirements/git.dart';
export 'src/requirements/min_flutter_version.dart';
export 'src/requirements/pubspec_exists.dart';
export 'src/requirements/requirements.dart';
38 changes: 38 additions & 0 deletions cli/lib/src/constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:version/version.dart';

final kMinFlutterVersion = Version(3, 29, 0);

const components = [
'accordion',
'alert',
'avatar',
'badge',
'button',
'calendar',
'card',
'checkbox',
'context-menu',
'date-picker',
'dialog',
'disabled',
'icon-button',
'input-otp',
'input',
'menubar',
'popover',
'progress',
'radio',
'resizable',
'select',
'separator',
'sheet',
'slider',
'sonner',
'switch',
'table',
'tabs',
'textarea',
'time_picker',
'toast',
'tooltip',
];
76 changes: 76 additions & 0 deletions cli/lib/src/git_operations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'dart:convert';
import 'dart:io';

import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;

abstract class GitOperations {
static String getGitRepositoryPath() {
final path = Process.runSync('git', ['rev-parse', '--show-toplevel']);
return path.stdout.toString().trim();
}

static Future<String> fetchFileFromGitHub({
required String path,
String? token,
String branch = 'main', // Default branch
}) async {
final url =
'https://api.github.com/repos/nank1ro/flutter-shadcn-ui/contents/$path?ref=$branch';
final headers = {
'Accept': 'application/vnd.github.v3+json',
if (token != null) 'Authorization': 'Bearer $token',
};

try {
final response = await http.get(Uri.parse(url), headers: headers);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
print('json: ${json['sha']}');
if (json['content'] != null) {
// Decode Base64 content
final content = base64Decode(
(json['content'] as String).replaceAll('\n', ''),
);
return utf8.decode(content);
} else {
throw Exception('File content not found in response');
}
} else if (response.statusCode == 404) {
throw Exception(
'File not found at $path in nank1ro/flutter-shadcn-ui ($branch branch)',
);
} else if (response.statusCode == 401 || response.statusCode == 403) {
throw Exception(
'Authentication failed. Invalid token or insufficient permissions',
);
} else {
throw Exception(
'Failed to fetch file: ${response.statusCode} ${response.reasonPhrase}',
);
}
} catch (e) {
throw Exception('Error fetching file from GitHub: $e');
}
}

static String calculateHashOfFile(List<int> contents) {
try {
// Create "blob <size>" prefix
final prefix = utf8.encode('blob ${contents.length}');

// Combine prefix, NULL byte, and contents
final combined = [...prefix, 0, ...contents];

// Compute SHA-1 hash
final hashBytes = sha1.convert(combined);

// Convert to hex string
return hashBytes.bytes
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join();
} catch (e) {
throw Exception('Failed to calculate hash: $e');
}
}
}
24 changes: 24 additions & 0 deletions cli/lib/src/models/pubspec.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This file is "model.dart"
import 'package:dart_mappable/dart_mappable.dart';

// Will be generated by dart_mappable
part 'pubspec.mapper.dart';

@MappableClass()
class Pubspec with PubspecMappable {
const Pubspec({
this.name,
this.version,
this.description,
this.dependencies,
this.devDependencies,
this.environment,
});

final String? name;
final String? version;
final String? description;
final Map<String, dynamic>? dependencies;
final Map<String, dynamic>? devDependencies;
final Map<String, dynamic>? environment;
}
Loading