NAME

Punk::Generate - scaffold a new Punk application

SYNOPSIS

punk new MyApp
punk new MyApp --api ./openapi.json

# or from Perl
use Punk::Generate;
my @files = Punk::Generate->new(name => 'MyApp')->run;

DESCRIPTION

The generator behind punk new. It writes a complete, running application: the class with its routes, a controller, Stencil views with a wrapper, config/punk.yml, a psgi entry point and a test that starts the app and requests a page.

The skeleton ships as templates beside this module and is rendered through Template::Stencil - the same engine the generated application uses for its own views.

METHODS

new

Punk::Generate->new(
    name  => 'MyApp',          # required; a legal Perl package name
    dir   => './MyApp',        # default: the name, :: replaced by -
    api   => './openapi.json', # optional; generates the API mount
    force => 0,                # write into a non-empty directory
);

Croaks on a name that is not a legal package name.

run

Writes the tree and returns the list of paths written, relative to dir. Croaks - before writing anything - if the target directory is not empty and force was not given, or if the spec cannot be read.

name / dir / written

The application name, the target directory, and the paths written by the last run.

THE GENERATED APPLICATION

app.psgi                          chdir to the root, then MyApp->to_app
config/punk.yml                   views, static, and a commented database
lib/MyApp.pm                      routes and wiring
lib/MyApp/Controller/Web/Root.pm  the front page
root/templates/layout.tmpl        the wrapper
root/templates/welcome.tmpl       the welcome page
root/static/style.css
t/01-basic.t                      builds the app and requests /
README.md
.gitignore                        including config/punk.local.yml

app.psgi changes directory to the application root before loading the class, because punk.yml carries relative paths; that is what makes plackup app.psgi work from anywhere.

With a spec

--api adds openapi.json at the application root, mounts it under /api with the documentation UI at /docs, and generates one controller per group of operations, each with a method named for its operationId answering 501 until implemented.

Operations are grouped by their first tag when the document uses tags, and by first path segment when it does not.

Every securityScheme the document requires also gets a checker stub, in Controller::API::Auth (or the next free name, if a tag already owns that one), wired into the mount's security option - without which Punk croaks at boot naming the scheme. Those stubs refuse every request until implemented, so the operations the specification protects are not opened by a placeholder. A scheme defined in components but never required needs no checker and gets none.

KITS

A kit is a generator of its own, reached as punk new MyApp --kit NAME, which loads Punk::Kit::<Name> and generates through that instead of the basic skeleton. A distribution ships one to hand somebody a whole working application - authentication, a schema, an admin area - where this module hands them a welcome page.

package Punk::Kit::Diy;
use parent 'Punk::Generate';

sub abstract { 'auth, a schema, an admin area and API keys' }

# Punk::Command option specs, merged into `punk new` for the run;
# `punk new --kit diy --help` lists them.
sub options {
    return ( { spec => 'without=s', arg => 'LIST',
               doc  => 'parts to leave out (comma separated)' } );
}

sub new {
    my ($class, %args) = @_;
    my $self = $class->SUPER::new(%args);
    $self->{without} = { map { $_ => 1 } split /,/, $args{without} || '' };
    return $self;
}

sub run {
    my ($self) = @_;
    $self->SUPER::run;                      # the base tree
    $self->_render('admin.tmpl', 'lib/.../Admin.pm', \%vars);
    return $self->written;
}

sub next_steps { "\n  cd $_[0]{dir}\n  punk sqitch deploy\n" }

new ignores constructor arguments it does not recognise, so a kit reads its own options straight out of %args. Only name is required of it.

Templates

A kit keeps its templates in skel/ beside its own module - lib/Punk/Kit/Diy/skel/ for the class above - and skel_dirs makes the search path from the inheritance chain, most derived first. So _render and render_skel find the kit's template when it has one and Punk's when it does not, and a kit that ships readme_md.tmpl overrides the one this module ships without touching the rest.

Rendering over a path the base skeleton already wrote is how a kit replaces part of the tree; written lists each path once, in the order it was first written.

Two things to know. A template's {% include %} resolves against the directory that template came from, so a kit's template cannot include one of Punk's. And templates are not .pm files, so a distribution shipping them has to add them to PM in its Makefile.PL by hand - MakeMaker finds .pm and .pod and nothing else.

Failure

A kit croaks for the same reasons this module does, and punk strips the class name from the front of the message the same way, so a kit's diagnostics read like Punk's. Anything the kit can reject should be rejected in new, before run writes the first file.

SEE ALSO

Punk, Punk::Controller, Punk::Config, Punk::Mount::OpenAPI.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)