File Coverage

File:blib/lib/App/GHGen/Generator.pm
Coverage:47.8%

linestmtbrancondsubtimecode
1package App::GHGen::Generator;
2
3
1
1
485
2
use v5.36;
4
5
1
1
1
1
1
7
use strict;
6
1
1
1
1
0
15
use warnings;
7
8
1
1
1
389
5104
32
use Path::Tiny;
9
1
1
1
165
2
32
use App::GHGen::PerlCustomizer qw(detect_perl_requirements generate_custom_perl_workflow);
10
11
1
1
1
2
1
229
use Exporter 'import';
12our @EXPORT_OK = qw(
13        generate_workflow
14        list_workflow_types
15        get_workflow_description
16);
17
18our $VERSION = '0.03';
19
20 - 37
=head1 NAME

App::GHGen::Generator - Generate GitHub Actions workflows

=head1 SYNOPSIS

    use App::GHGen::Generator qw(generate_workflow);

    my $yaml = generate_workflow('perl');
    path('.github/workflows/ci.yml')->spew_utf8($yaml);

=head1 FUNCTIONS

=head2 generate_workflow($type)

Generate a workflow for the specified type. Returns YAML as a string.

=cut
38
39
2
2
2
421
3
0
sub generate_workflow($type) {
40
2
8
    my %generators = (
41        perl   => \&_generate_perl_workflow,
42        node   => \&_generate_node_workflow,
43        python => \&_generate_python_workflow,
44        rust   => \&_generate_rust_workflow,
45        go     => \&_generate_go_workflow,
46        ruby   => \&_generate_ruby_workflow,
47        java   => \&_generate_java_workflow,
48        cpp    => \&_generate_cpp_workflow,
49        php    => \&_generate_php_workflow,
50        docker => \&_generate_docker_workflow,
51        static => \&_generate_static_workflow,
52    );
53
54
2
4
        return undef unless exists $generators{$type};
55
1
1
        return $generators{$type}->();
56}
57
58 - 62
=head2 list_workflow_types()

Returns a hash of available workflow types and their descriptions.

=cut
63
64
1
1
851
1
sub list_workflow_types() {
65    return (
66
1
3
        node   => 'Node.js/npm projects with testing and linting',
67        python => 'Python projects with pytest and coverage',
68        rust   => 'Rust projects with cargo, clippy, and formatting',
69        go     => 'Go projects with testing and race detection',
70        ruby   => 'Ruby projects with bundler and rake',
71        perl   => 'Perl projects with cpanm, prove, and coverage',
72        java   => 'Java projects with Maven or Gradle',
73        cpp    => 'C++ projects with CMake',
74        php    => 'PHP projects with Composer and PHPUnit',
75        docker => 'Docker image build and push workflow',
76        static => 'Static site deployment to GitHub Pages',
77    );
78}
79
80 - 84
=head2 get_workflow_description($type)

Get the description for a specific workflow type.

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

Nigel Horne E<lt>njh@nigelhorne.comE<gt>

L<https://github.com/nigelhorne>

=head1 LICENSE

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
692
6931;