File Coverage

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

linestmtbrancondsubtimecode
1package App::GHGen::Generator;
2
3
1
1
462
2
use v5.36;
4
5
1
1
1
2
0
7
use strict;
6
1
1
1
1
1
17
use warnings;
7
8
1
1
1
190
1
32
use App::GHGen::PerlCustomizer qw(detect_perl_requirements generate_custom_perl_workflow);
9
10
1
1
1
2
2
229
use Exporter 'import';
11our @EXPORT_OK = qw(
12        generate_workflow
13        list_workflow_types
14);
15
16our $VERSION = '0.05';
17
18 - 36
=head1 NAME

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

=head1 SYNOPSIS

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

    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
37
38
2
2
2
404
2
1
sub generate_workflow($type) {
39
2
6
    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
3
        return undef unless exists $generators{$type};
54
1
1
        return $generators{$type}->();
55}
56
57 - 61
=head2 list_workflow_types()

Returns a hash of available workflow types and their descriptions.

=cut
62
63
1
1
836
1
sub list_workflow_types() {
64    return (
65
1
3
        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
1
1
sub _generate_perl_workflow() {
82        # Try to detect requirements from project
83
1
1
        my $reqs = detect_perl_requirements();
84
85        # Use detected min version or default to 5.36
86
1
2
        my $min_version = $reqs->{min_version} // '5.36';
87
88        # Generate custom workflow with detected settings
89
1
3
        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() {
99
0
        return <<'YAML';
100---
101name: Node.js CI
102
103'on':
104  push:
105    branches:
106      - main
107      - develop
108  pull_request:
109    branches:
110      - main
111      - develop
112
113concurrency:
114  group: ${{ github.workflow }}-${{ github.ref }}
115  cancel-in-progress: true
116
117permissions:
118  contents: read
119
120jobs:
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
157YAML
158}
159
160
0
0
sub _generate_python_workflow() {
161
0
    return <<'YAML';
162---
163name: Python CI
164
165'on':
166  push:
167    branches:
168      - main
169      - develop
170  pull_request:
171    branches:
172      - main
173      - develop
174
175concurrency:
176  group: ${{ github.workflow }}-${{ github.ref }}
177  cancel-in-progress: true
178
179permissions:
180  contents: read
181
182jobs:
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
219YAML
220}
221
222
0
0
sub _generate_rust_workflow() {
223
0
    return <<'YAML';
224---
225name: Rust CI
226
227'on':
228  push:
229    branches:
230      - main
231  pull_request:
232    branches:
233      - main
234
235concurrency:
236  group: ${{ github.workflow }}-${{ github.ref }}
237  cancel-in-progress: true
238
239permissions:
240  contents: read
241
242jobs:
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
274YAML
275}
276
277
0
0
sub _generate_go_workflow() {
278
0
    return <<'YAML';
279---
280name: Go CI
281
282'on':
283  push:
284    branches:
285      - main
286  pull_request:
287    branches:
288      - main
289
290concurrency:
291  group: ${{ github.workflow }}-${{ github.ref }}
292  cancel-in-progress: true
293
294permissions:
295  contents: read
296
297jobs:
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 ./...
328YAML
329}
330
331
0
0
sub _generate_ruby_workflow() {
332
0
    return <<'YAML';
333---
334name: Ruby CI
335
336'on':
337  push:
338    branches:
339      - main
340  pull_request:
341    branches:
342      - main
343
344concurrency:
345  group: ${{ github.workflow }}-${{ github.ref }}
346  cancel-in-progress: true
347
348permissions:
349  contents: read
350
351jobs:
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
372YAML
373}
374
375
0
0
sub _generate_docker_workflow() {
376
0
    return <<'YAML';
377---
378name: Docker Build
379
380'on':
381  push:
382    branches:
383      - main
384    tags:
385      - v*
386  pull_request:
387    branches:
388      - main
389
390concurrency:
391  group: ${{ github.workflow }}-${{ github.ref }}
392  cancel-in-progress: true
393
394permissions:
395  contents: read
396  packages: write
397
398jobs:
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
430YAML
431}
432
433
0
0
sub _generate_static_workflow() {
434
0
    return <<'YAML';
435---
436name: Deploy Static Site
437
438'on':
439  push:
440    branches:
441      - main
442
443permissions:
444  contents: read
445  pages: write
446  id-token: write
447
448concurrency:
449  group: pages
450  cancel-in-progress: false
451
452jobs:
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
480YAML
481}
482
483
0
0
sub _generate_java_workflow() {
484
0
    return <<'YAML';
485---
486name: Java CI
487
488'on':
489  push:
490    branches:
491      - main
492      - develop
493  pull_request:
494    branches:
495      - main
496      - develop
497
498concurrency:
499  group: ${{ github.workflow }}-${{ github.ref }}
500  cancel-in-progress: true
501
502permissions:
503  contents: read
504
505jobs:
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
541YAML
542}
543
544
0
0
sub _generate_cpp_workflow() {
545
0
    return <<'YAML';
546---
547name: C++ CI
548
549'on':
550  push:
551    branches:
552      - main
553  pull_request:
554    branches:
555      - main
556
557concurrency:
558  group: ${{ github.workflow }}-${{ github.ref }}
559  cancel-in-progress: true
560
561permissions:
562  contents: read
563
564jobs:
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
600YAML
601}
602
603
0
0
sub _generate_php_workflow() {
604
0
    return <<'YAML';
605---
606name: PHP CI
607
608'on':
609  push:
610    branches:
611      - main
612      - develop
613  pull_request:
614    branches:
615      - main
616      - develop
617
618concurrency:
619  group: ${{ github.workflow }}-${{ github.ref }}
620  cancel-in-progress: true
621
622permissions:
623  contents: read
624
625jobs:
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
665YAML
666}
667
668 - 679
=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
680
6811;