File Coverage

blib/lib/App/GHGen/Generator.pm
Criterion Covered Total %
statement 28 58 48.2
branch 2 2 100.0
condition 1 2 50.0
subroutine 8 18 44.4
pod 2 2 100.0
total 41 82 50.0


line stmt bran cond sub pod time code
1             package App::GHGen::Generator;
2              
3 1     1   1616 use v5.36;
  1         4  
4              
5 1     1   7 use strict;
  1         2  
  1         27  
6 1     1   5 use warnings;
  1         2  
  1         78  
7              
8 1     1   655 use App::GHGen::PerlCustomizer qw(detect_perl_requirements generate_custom_perl_workflow);
  1         4  
  1         90  
9              
10 1     1   8 use Exporter 'import';
  1         6  
  1         868  
11             our @EXPORT_OK = qw(
12             generate_workflow
13             list_workflow_types
14             );
15              
16             our $VERSION = '0.05';
17              
18             =head1 NAME
19              
20             App::GHGen::Generator - Generate GitHub Actions workflows
21              
22             =head1 SYNOPSIS
23              
24             use App::GHGen::Generator qw(generate_workflow);
25             use Path::Tiny;
26              
27             my $yaml = generate_workflow('perl');
28             path('.github/workflows/ci.yml')->spew_utf8($yaml);
29              
30             =head1 FUNCTIONS
31              
32             =head2 generate_workflow($type)
33              
34             Generate a workflow for the specified type. Returns YAML as a string.
35              
36             =cut
37              
38 2     2 1 1386 sub generate_workflow($type) {
  2         6  
  2         4  
39 2         24 my %generators = (
40             perl => \&_generate_perl_workflow,
41             node => \&_generate_node_workflow,
42             python => \&_generate_python_workflow,
43             rust => \&_generate_rust_workflow,
44             go => \&_generate_go_workflow,
45             ruby => \&_generate_ruby_workflow,
46             java => \&_generate_java_workflow,
47             cpp => \&_generate_cpp_workflow,
48             php => \&_generate_php_workflow,
49             docker => \&_generate_docker_workflow,
50             static => \&_generate_static_workflow,
51             );
52              
53 2 100       13 return undef unless exists $generators{$type};
54 1         4 return $generators{$type}->();
55             }
56              
57             =head2 list_workflow_types()
58              
59             Returns a hash of available workflow types and their descriptions.
60              
61             =cut
62              
63 1     1 1 2821 sub list_workflow_types() {
  1         2  
64             return (
65 1         13 node => 'Node.js/npm projects with testing and linting',
66             python => 'Python projects with pytest and coverage',
67             rust => 'Rust projects with cargo, clippy, and formatting',
68             go => 'Go projects with testing and race detection',
69             ruby => 'Ruby projects with bundler and rake',
70             perl => 'Perl projects with cpanm, prove, and coverage',
71             java => 'Java projects with Maven or Gradle',
72             cpp => 'C++ projects with CMake',
73             php => 'PHP projects with Composer and PHPUnit',
74             docker => 'Docker image build and push workflow',
75             static => 'Static site deployment to GitHub Pages',
76             );
77             }
78              
79             # Private workflow generators
80              
81 1     1   2 sub _generate_perl_workflow() {
  1         2  
82             # Try to detect requirements from project
83 1         5 my $reqs = detect_perl_requirements();
84              
85             # Use detected min version or default to 5.36
86 1   50     4 my $min_version = $reqs->{min_version} // '5.36';
87              
88             # Generate custom workflow with detected settings
89 1         9 return generate_custom_perl_workflow({
90             min_perl_version => $min_version,
91             max_perl_version => '5.40',
92             os => ['macos-latest', 'ubuntu-latest', 'windows-latest'],
93             enable_critic => 1,
94             enable_coverage => 1,
95             });
96             }
97              
98 0     0     sub _generate_node_workflow() {
  0            
99 0           return <<'YAML';
100             ---
101             name: Node.js CI
102              
103             'on':
104             push:
105             branches:
106             - main
107             - develop
108             pull_request:
109             branches:
110             - main
111             - develop
112              
113             concurrency:
114             group: ${{ github.workflow }}-${{ github.ref }}
115             cancel-in-progress: true
116              
117             permissions:
118             contents: read
119              
120             jobs:
121             test:
122             runs-on: ubuntu-latest
123             strategy:
124             matrix:
125             node-version:
126             - 18.x
127             - 20.x
128             - 22.x
129             steps:
130             - name: Checkout code
131             uses: actions/checkout@v6
132              
133             - name: Setup Node.js ${{ matrix.node-version }}
134             uses: actions/setup-node@v4
135             with:
136             node-version: ${{ matrix.node-version }}
137              
138             - name: Cache dependencies
139             uses: actions/cache@v5
140             with:
141             path: ~/.npm
142             key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
143             restore-keys: |
144             ${{ runner.os }}-node-
145              
146             - name: Install dependencies
147             run: npm ci
148              
149             - name: Run linter
150             run: npm run lint --if-present
151              
152             - name: Run tests
153             run: npm test
154              
155             - name: Build project
156             run: npm run build --if-present
157             YAML
158             }
159              
160 0     0     sub _generate_python_workflow() {
  0            
161 0           return <<'YAML';
162             ---
163             name: Python CI
164              
165             'on':
166             push:
167             branches:
168             - main
169             - develop
170             pull_request:
171             branches:
172             - main
173             - develop
174              
175             concurrency:
176             group: ${{ github.workflow }}-${{ github.ref }}
177             cancel-in-progress: true
178              
179             permissions:
180             contents: read
181              
182             jobs:
183             test:
184             runs-on: ubuntu-latest
185             strategy:
186             matrix:
187             python-version:
188             - '3.9'
189             - '3.10'
190             - '3.11'
191             - '3.12'
192             steps:
193             - name: Checkout code
194             uses: actions/checkout@v6
195              
196             - name: Set up Python ${{ matrix.python-version }}
197             uses: actions/setup-python@v5
198             with:
199             python-version: ${{ matrix.python-version }}
200              
201             - name: Cache pip packages
202             uses: actions/cache@v5
203             with:
204             path: ~/.cache/pip
205             key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
206             restore-keys: |
207             ${{ runner.os }}-pip-
208              
209             - name: Install dependencies
210             run: |
211             pip install -r requirements.txt
212             pip install pytest pytest-cov flake8
213              
214             - name: Lint with flake8
215             run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
216              
217             - name: Run tests with coverage
218             run: pytest --cov=. --cov-report=xml
219             YAML
220             }
221              
222 0     0     sub _generate_rust_workflow() {
  0            
223 0           return <<'YAML';
224             ---
225             name: Rust CI
226              
227             'on':
228             push:
229             branches:
230             - main
231             pull_request:
232             branches:
233             - main
234              
235             concurrency:
236             group: ${{ github.workflow }}-${{ github.ref }}
237             cancel-in-progress: true
238              
239             permissions:
240             contents: read
241              
242             jobs:
243             test:
244             runs-on: ubuntu-latest
245             steps:
246             - name: Checkout code
247             uses: actions/checkout@v6
248              
249             - name: Setup Rust
250             uses: dtolnay/rust-toolchain@stable
251              
252             - name: Cache cargo
253             uses: actions/cache@v5
254             with:
255             path: |
256             ~/.cargo/bin/
257             ~/.cargo/registry/index/
258             ~/.cargo/registry/cache/
259             ~/.cargo/git/db/
260             target/
261             key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
262              
263             - name: Check formatting
264             run: cargo fmt -- --check
265              
266             - name: Run clippy
267             run: cargo clippy -- -D warnings
268              
269             - name: Run tests
270             run: cargo test --verbose
271              
272             - name: Build release
273             run: cargo build --release --verbose
274             YAML
275             }
276              
277 0     0     sub _generate_go_workflow() {
  0            
278 0           return <<'YAML';
279             ---
280             name: Go CI
281              
282             'on':
283             push:
284             branches:
285             - main
286             pull_request:
287             branches:
288             - main
289              
290             concurrency:
291             group: ${{ github.workflow }}-${{ github.ref }}
292             cancel-in-progress: true
293              
294             permissions:
295             contents: read
296              
297             jobs:
298             test:
299             runs-on: ubuntu-latest
300             steps:
301             - name: Checkout code
302             uses: actions/checkout@v6
303              
304             - name: Setup Go
305             uses: actions/setup-go@v5
306             with:
307             go-version: '1.22'
308              
309             - name: Cache Go modules
310             uses: actions/cache@v5
311             with:
312             path: ~/go/pkg/mod
313             key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
314             restore-keys: |
315             ${{ runner.os }}-go-
316              
317             - name: Download dependencies
318             run: go mod download
319              
320             - name: Run go vet
321             run: go vet ./...
322              
323             - name: Run tests
324             run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
325              
326             - name: Build
327             run: go build -v ./...
328             YAML
329             }
330              
331 0     0     sub _generate_ruby_workflow() {
  0            
332 0           return <<'YAML';
333             ---
334             name: Ruby CI
335              
336             'on':
337             push:
338             branches:
339             - main
340             pull_request:
341             branches:
342             - main
343              
344             concurrency:
345             group: ${{ github.workflow }}-${{ github.ref }}
346             cancel-in-progress: true
347              
348             permissions:
349             contents: read
350              
351             jobs:
352             test:
353             runs-on: ubuntu-latest
354             strategy:
355             matrix:
356             ruby-version:
357             - '3.1'
358             - '3.2'
359             - '3.3'
360             steps:
361             - name: Checkout code
362             uses: actions/checkout@v6
363              
364             - name: Set up Ruby
365             uses: ruby/setup-ruby@v1
366             with:
367             ruby-version: ${{ matrix.ruby-version }}
368             bundler-cache: true
369              
370             - name: Run tests
371             run: bundle exec rake test
372             YAML
373             }
374              
375 0     0     sub _generate_docker_workflow() {
  0            
376 0           return <<'YAML';
377             ---
378             name: Docker Build
379              
380             'on':
381             push:
382             branches:
383             - main
384             tags:
385             - v*
386             pull_request:
387             branches:
388             - main
389              
390             concurrency:
391             group: ${{ github.workflow }}-${{ github.ref }}
392             cancel-in-progress: true
393              
394             permissions:
395             contents: read
396             packages: write
397              
398             jobs:
399             build:
400             runs-on: ubuntu-latest
401             steps:
402             - name: Checkout code
403             uses: actions/checkout@v6
404              
405             - name: Set up Docker Buildx
406             uses: docker/setup-buildx-action@v3
407              
408             - name: Log in to Docker Hub
409             uses: docker/login-action@v3
410             with:
411             username: ${{ secrets.DOCKER_USERNAME }}
412             password: ${{ secrets.DOCKER_PASSWORD }}
413             if: github.event_name != 'pull_request'
414              
415             - name: Extract metadata
416             id: meta
417             uses: docker/metadata-action@v5
418             with:
419             images: your-username/your-image
420              
421             - name: Build and push
422             uses: docker/build-push-action@v5
423             with:
424             context: .
425             push: ${{ github.event_name != 'pull_request' }}
426             tags: ${{ steps.meta.outputs.tags }}
427             labels: ${{ steps.meta.outputs.labels }}
428             cache-from: type=gha
429             cache-to: type=gha,mode=max
430             YAML
431             }
432              
433 0     0     sub _generate_static_workflow() {
  0            
434 0           return <<'YAML';
435             ---
436             name: Deploy Static Site
437              
438             'on':
439             push:
440             branches:
441             - main
442              
443             permissions:
444             contents: read
445             pages: write
446             id-token: write
447              
448             concurrency:
449             group: pages
450             cancel-in-progress: false
451              
452             jobs:
453             build:
454             runs-on: ubuntu-latest
455             steps:
456             - name: Checkout
457             uses: actions/checkout@v6
458              
459             - name: Setup Pages
460             uses: actions/configure-pages@v4
461              
462             - name: Build
463             run: echo "Add your build command here"
464              
465             - name: Upload artifact
466             uses: actions/upload-pages-artifact@v3
467             with:
468             path: ./public
469              
470             deploy:
471             environment:
472             name: github-pages
473             url: ${{ steps.deployment.outputs.page_url }}
474             runs-on: ubuntu-latest
475             needs: build
476             steps:
477             - name: Deploy to GitHub Pages
478             id: deployment
479             uses: actions/deploy-pages@v4
480             YAML
481             }
482              
483 0     0     sub _generate_java_workflow() {
  0            
484 0           return <<'YAML';
485             ---
486             name: Java CI
487              
488             'on':
489             push:
490             branches:
491             - main
492             - develop
493             pull_request:
494             branches:
495             - main
496             - develop
497              
498             concurrency:
499             group: ${{ github.workflow }}-${{ github.ref }}
500             cancel-in-progress: true
501              
502             permissions:
503             contents: read
504              
505             jobs:
506             test:
507             runs-on: ubuntu-latest
508             strategy:
509             matrix:
510             java-version:
511             - '11'
512             - '17'
513             - '21'
514             steps:
515             - name: Checkout code
516             uses: actions/checkout@v6
517              
518             - name: Set up JDK ${{ matrix.java-version }}
519             uses: actions/setup-java@v4
520             with:
521             java-version: ${{ matrix.java-version }}
522             distribution: 'temurin'
523              
524             - name: Cache Maven packages
525             uses: actions/cache@v5
526             with:
527             path: ~/.m2/repository
528             key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
529             restore-keys: |
530             ${{ runner.os }}-maven-
531              
532             - name: Build with Maven
533             run: mvn -B package --file pom.xml
534              
535             - name: Run tests
536             run: mvn -B test
537              
538             - name: Generate test report
539             if: always()
540             run: mvn surefire-report:report
541             YAML
542             }
543              
544 0     0     sub _generate_cpp_workflow() {
  0            
545 0           return <<'YAML';
546             ---
547             name: C++ CI
548              
549             'on':
550             push:
551             branches:
552             - main
553             pull_request:
554             branches:
555             - main
556              
557             concurrency:
558             group: ${{ github.workflow }}-${{ github.ref }}
559             cancel-in-progress: true
560              
561             permissions:
562             contents: read
563              
564             jobs:
565             test:
566             runs-on: ${{ matrix.os }}
567             strategy:
568             matrix:
569             os:
570             - ubuntu-latest
571             - macos-latest
572             - windows-latest
573             build-type:
574             - Debug
575             - Release
576             steps:
577             - name: Checkout code
578             uses: actions/checkout@v6
579              
580             - name: Install CMake
581             uses: lukka/get-cmake@latest
582              
583             - name: Cache build artifacts
584             uses: actions/cache@v5
585             with:
586             path: |
587             build
588             ~/.cache/ccache
589             key: ${{ runner.os }}-${{ matrix.build-type }}-${{ hashFiles('**/CMakeLists.txt') }}
590              
591             - name: Configure CMake
592             run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.build-type }}
593              
594             - name: Build
595             run: cmake --build build --config ${{ matrix.build-type }}
596              
597             - name: Run tests
598             working-directory: build
599             run: ctest -C ${{ matrix.build-type }} --output-on-failure
600             YAML
601             }
602              
603 0     0     sub _generate_php_workflow() {
  0            
604 0           return <<'YAML';
605             ---
606             name: PHP CI
607              
608             'on':
609             push:
610             branches:
611             - main
612             - develop
613             pull_request:
614             branches:
615             - main
616             - develop
617              
618             concurrency:
619             group: ${{ github.workflow }}-${{ github.ref }}
620             cancel-in-progress: true
621              
622             permissions:
623             contents: read
624              
625             jobs:
626             test:
627             runs-on: ubuntu-latest
628             strategy:
629             matrix:
630             php-version:
631             - '8.1'
632             - '8.2'
633             - '8.3'
634             steps:
635             - name: Checkout code
636             uses: actions/checkout@v6
637              
638             - name: Setup PHP ${{ matrix.php-version }}
639             uses: shivammathur/setup-php@v2
640             with:
641             php-version: ${{ matrix.php-version }}
642             extensions: mbstring, xml, ctype, json
643             coverage: xdebug
644              
645             - name: Validate composer.json
646             run: composer validate --strict
647              
648             - name: Cache Composer packages
649             uses: actions/cache@v5
650             with:
651             path: vendor
652             key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
653             restore-keys: |
654             ${{ runner.os }}-php-
655              
656             - name: Install dependencies
657             run: composer install --prefer-dist --no-progress
658              
659             - name: Run PHPUnit tests
660             run: vendor/bin/phpunit --coverage-text
661              
662             - name: Run PHP CodeSniffer
663             run: vendor/bin/phpcs --standard=PSR12 src tests
664             continue-on-error: true
665             YAML
666             }
667              
668             =head1 AUTHOR
669              
670             Nigel Horne Enjh@nigelhorne.comE
671              
672             L
673              
674             =head1 LICENSE
675              
676             This is free software; you can redistribute it and/or modify it under
677             the same terms as the Perl 5 programming language system itself.
678              
679             =cut
680              
681             1;