App-GHGen
view release on metacpan or search on metacpan
use App::GHGen::Generator qw(generate_workflow list_workflow_types);
use App::GHGen::Analyzer qw(analyze_workflow find_workflows);
use App::GHGen::Detector qw(detect_project_type get_project_indicators);
use App::GHGen::Interactive qw(customize_workflow);
use App::GHGen::PerlCustomizer qw(generate_custom_perl_workflow);
use App::GHGen::Fixer qw(fix_workflow can_auto_fix);
use App::GHGen::CostEstimator qw(estimate_current_usage estimate_savings);
our $VERSION = '0.05';
my %opts = (
help => 0,
version => 0,
list => 0,
timeout => undef,
);
GetOptions(
'help|h' => \$opts{help},
'version|v' => \$opts{version},
'type|t=s' => \$opts{type},
'output|o=s' => \$opts{output},
'list|l' => \$opts{list},
'interactive|i' => \$opts{interactive},
'auto|a' => \$opts{auto},
'customize|c' => \$opts{customize},
'fix|f' => \$opts{fix},
'estimate|e' => \$opts{estimate},
'timeout=i' => \$opts{timeout},
) or pod2usage(2);
pod2usage(1) if $opts{help};
if ($opts{version}) {
say "ghgen version $VERSION";
exit 0;
}
my $command = shift @ARGV // '';
if ($command eq 'generate') {
cmd_generate();
} elsif ($command eq 'analyze') {
cmd_analyze();
} else {
pod2usage(-message => "Unknown command: $command", -exitval => 1);
}
sub cmd_generate() {
if ($opts{list}) {
list_types();
return;
}
my $type = $opts{type};
my $config;
# Auto-detect if requested
if ($opts{auto} || (!$type && !$opts{interactive} && !$opts{customize})) {
$type = auto_detect_with_prompt();
return unless $type; # User cancelled
}
# Interactive mode
if ($opts{interactive} && !$type) {
$type = interactive_select_type();
}
unless ($type) {
die colored(['red'], "Error: ") .
"Please specify --type, use --auto, or use --interactive\n";
}
# Customization mode
if ($opts{customize}) {
$config = customize_workflow($type);
}
# Generate workflow with optional customization
my $yaml;
if ($config && $type eq 'perl') {
# Use customized Perl workflow
my @perl_versions = @{$config->{perl_versions} // ['5.40', '5.38', '5.36']};
$yaml = generate_custom_perl_workflow({
min_perl_version => $perl_versions[0],
max_perl_version => $perl_versions[-1],
perl_versions => \@perl_versions,
os => $config->{os} // ['ubuntu-latest', 'macos-latest', 'windows-latest'],
enable_critic => $config->{enable_critic} // 1,
enable_coverage => $config->{enable_coverage} // 1,
timeout => $opts{timeout},
});
} elsif ($type eq 'perl') {
$yaml = generate_custom_perl_workflow({ timeout => $opts{timeout} });
} else {
$yaml = generate_workflow($type);
}
unless ($yaml) {
die colored(['red'], "Error: "), "Unknown project type '$type'. Use --list to see options.\n";
}
# Determine output file
my $output_file = $opts{output};
unless ($output_file) {
my $workflows_dir = path('.github/workflows');
$workflows_dir->mkpath unless $workflows_dir->exists;
$output_file = $workflows_dir->child("$type-ci.yml");
}
# Write the workflow
path($output_file)->spew_utf8($yaml);
say colored(['green'], "â ") . 'Generated workflow: ' . colored(['bold'], $output_file);
say '';
say colored(['cyan'], "Next steps:");
say " 1. Review the generated workflow";
say " 2. Customize it for your project";
say " 3. Commit and push to trigger the workflow";
say '';
say colored(['cyan'], "ð¡ Tip: ") . "Run " . colored(['bold'], "ghgen analyze --estimate") .
" to see potential cost savings";
}
}
exit(@all_issues ? 1 : 0);
}
sub list_types() {
say colored(['bold cyan'], "Available workflow templates:");
say '';
my %types = list_workflow_types();
for my $type (sort keys %types) {
say " " . colored(['green'], sprintf("%-10s", $type)) . " - $types{$type}";
}
say '';
say "Usage: ghgen generate --type=<type> [--output=<file>]";
say " or: ghgen generate --interactive";
}
sub interactive_select_type() {
say colored(['bold cyan'], "GitHub Actions Workflow Generator");
say colored(['cyan'], "=" x 50);
say '';
say "Select a project type:";
say '';
my @types = qw(node python rust go ruby perl java cpp php docker static);
my %descriptions = (
node => 'Node.js/npm',
python => 'Python',
rust => 'Rust',
go => 'Go',
ruby => 'Ruby',
perl => 'Perl',
java => 'Java (Maven/Gradle)',
cpp => 'C++ (CMake)',
php => 'PHP (Composer)',
docker => 'Docker',
static => 'Static site (GitHub Pages)',
);
for my $i (0..$#types) {
say " " . colored(['green'], $i + 1) . ". $descriptions{$types[$i]}";
}
say '';
print "Enter number (1-" . scalar(@types) . "): ";
chomp(my $choice = <STDIN>);
if ($choice =~ /^\d+$/ && $choice >= 1 && $choice <= @types) {
return $types[$choice - 1];
}
die colored(['red'], "Invalid choice\n");
}
sub auto_detect_with_prompt() {
say colored(['bold cyan'], "Auto-detecting project type...");
say '';
my @detections = detect_project_type();
unless (@detections) {
say colored(['yellow'], "â ") . "Could not auto-detect project type.";
say '';
say "Try one of these options:";
say " ⢠" . colored(['cyan'], "ghgen generate --list") . " - see all available types";
say " ⢠" . colored(['cyan'], "ghgen generate --interactive") . " - choose interactively";
say " ⢠" . colored(['cyan'], "ghgen generate --type=<type>") . " - specify type directly";
return undef;
}
# Show top detection
my $top = $detections[0];
if(!defined($top->{type})) {
die "Cound't autodetect the project type";
}
say colored(['green'], "â ") . 'Detected project type: ' . colored(['bold'], uc($top->{type}));
# Show evidence
my $indicators = get_project_indicators($top->{type});
my @found_indicators;
for my $indicator (@$indicators) {
# Simple file check (not glob patterns)
if ($indicator !~ /[\*\?]/ && path($indicator)->exists) {
push @found_indicators, $indicator;
last if @found_indicators >= 3; # Show max 3
}
}
if (@found_indicators) {
say colored(['cyan'], " Evidence: ") . join(', ', @found_indicators);
}
say '';
# Show alternatives if any
if (@detections > 1) {
say colored(['yellow'], "Other possibilities:");
for my $i (1 .. min(2, $#detections)) {
say " ⢠$detections[$i]->{type} (confidence: " . int($detections[$i]->{score} / $top->{score} * 100) . "%)";
}
say '';
}
# Prompt for confirmation
print "Generate " . colored(['bold'], uc($top->{type})) . " workflow? [Y/n]: ";
chomp(my $response = <STDIN>);
if ($response =~ /^(y|yes|)$/i) {
return $top->{type};
} elsif ($response =~ /^(n|no)$/i) {
say '';
say "Cancelled. Use " . colored(['cyan'], "--interactive") . " to choose manually.";
return undef;
} else {
say colored(['red'], "Invalid response. Cancelled.");
return undef;
}
}
sub min($a, $b) {
return $a < $b ? $a : $b;
}
__END__
=head1 NAME
ghgen - GitHub Actions workflow generator and analyzer
=head1 SYNOPSIS
ghgen generate --auto
ghgen generate --type=perl [--output=FILE]
ghgen generate --type=perl --timeout=45
ghgen generate --type=perl --customize
ghgen generate --interactive
ghgen generate --list
ghgen analyze
ghgen analyze --fix
ghgen --help
ghgen --version
=head1 DESCRIPTION
B<ghgen> helps you create and optimize GitHub Actions workflows.
=head1 COMMANDS
=head2 generate
Generate a new GitHub Actions workflow.
ghgen generate --auto
ghgen generate --type=perl
ghgen generate --type=perl --customize
ghgen generate --type=node --output=custom.yml
ghgen generate --interactive
ghgen generate --list
Options:
--auto, -a Auto-detect project type and prompt for confirmation
--type, -t TYPE Project type (perl, node, python, rust, go, ruby, docker, static)
--customize, -c Interactively customize the workflow
--output, -o FILE Output file (default: .github/workflows/TYPE-ci.yml)
--interactive, -i Interactive mode to select type
--list, -l List available workflow types
--timeout MINUTES Set job timeout-minutes for Perl workflows (default: 30)
=head2 analyze
Analyze existing workflows for issues and optimizations.
ghgen analyze
Checks for:
- Missing dependency caching
- Unpinned action versions
- Overly broad triggers
- Missing concurrency controls
- Outdated runner versions
- Missing or excessive job timeouts (use --timeout to control Perl workflows)
=head1 OPTIONS
=over 4
=item B<--help, -h>
Show this help message.
=item B<--version, -v>
Show version information.
=back
=head1 EXAMPLES
Auto-detect and generate workflow:
cd my-project
ghgen generate --auto
Generate a Perl workflow with customization:
ghgen generate --type=perl --customize
Interactively select and generate a workflow:
ghgen generate --interactive
Analyze all workflows in current repository:
ghgen analyze
=head1 AUTHOR
Nigel Horne E<lt>njh@nigelhorne.comE<gt>
L<https://github.com/nigelhorne>
( run in 0.682 second using v1.01-cache-2.11-cpan-6aa56a78535 )