Skip to content

Forge a new module

$ grim make:module Admissions --with-crud=Entry --no-repo

The project needs something no existing module does: a list of admissions, a price calculator, a connector to somebody's API. A module is where that code lives, and an empty one is a provider, routes, language stubs and admin pages that all have to agree on a name. grim make:module writes them, so the first thing you type is the part that is actually yours.

By default the module arrives with a complete admin CRUD for one model and with a private GitHub repository of its own. Both can be switched off, and for behaviour only this project needs the repository is the one to switch off: --no-repo makes a local module, part of the project and committed with it.

Usage

$ grim make:module Admissions --no-repo            # a local module, CRUD for a model named Admission
$ grim make:module Hooks --no-crud --no-repo       # a bare local module
$ grim make:module Admissions --with-crud=Entry    # a unit: CRUD for Entry and a repository on GitHub

Run it from the project directory. The name is PascalCase; admissions or price-list are accepted and turned into Admissions and PriceList. Creating the repository needs a stored GitHub login that may create repositories in the target organisation. --no-repo needs no login at all.

Arguments and options

Scaffold a new module under src/{Name}/ (optionally with CRUD + GitHub repo)

Usage

grim make:module [-d|--description DESCRIPTION] [--disabled] [--with-crud [WITH-CRUD]] [--no-crud] [--plural PLURAL] [--crud-slug CRUD-SLUG] [--table TABLE] [--menu-group MENU-GROUP] [--no-repo] [--private] [--public] [--org ORG] [--use-existing] [--skip-push] [--] <name>

Arguments

Argument Description
name Module name in PascalCase (e.g. Admissions) (required)

Options

Option Description
-d, --description=DESCRIPTION Short description for module.json
--disabled Skip enabling the module in modules-enabled
--with-crud[=WITH-CRUD] Scaffold full CRUD (Model+Repository+Pages+register* methods). Pass a value to override the model name; defaults to singular of module name.
--no-crud Produce a minimal module without CRUD/Model/Pages
--plural=PLURAL Override the derived plural form of the model name
--crud-slug=CRUD-SLUG Override the CRUD slug (default: {module-kebab}-{models-kebab}, e.g. admissions-entries)
--table=TABLE Override the table name (default: {module_snake}_{models_snake})
--menu-group=MENU-GROUP Admin menu group label for the registerMenu() call (default: Obchod)
--no-repo Local module: no GitHub repository and no .github/; listed in grim.json "local" and committed in the instance
--private Create the GitHub repo as private (default)
--public Create the GitHub repo as public
--org=ORG GitHub org (or user) to create the repo under (default: grimoiry)
--use-existing If the remote repo already exists, reuse it instead of failing
--skip-push Init local git + attach remote but do not push

What it actually does

Each step is printed with a number and its duration. The first one that fails stops the run.

  1. Scaffolds src/<Name>/. module.json, <Name>Provider.php, routes.php, config/, helpers.php, hooks.php, index.ts, Czech and English language files, a public Index action and an empty Database/Migrations/. The version in module.json is the version of the core the project is on.
  2. Adds the CRUD, unless --no-crud: a model, a repository, the Index, Create, Edit and Form pages under Pages/<Models>/, and a provider that registers the CRUD, its permissions and an admin menu entry.
  3. Copies the validation workflow to .github/workflows/validate.yml inside the module, or with --no-repo lists the module in grim.json local instead. The workflow checks the manifest on every tag pushed to the module's repository; a local module has none, so it gets no .github/. Listing it also writes config/modules-available.php again. A name the project already requires or has in grim.lock is refused: a local module needs a name of its own.
  4. Enables the module in storage/config/modules-enabled.php, unless --disabled.
  5. Commits the scaffold in a copy, unless --no-repo. The copy sits in the system temp directory: git init, one commit, Initial scaffold from grim-cli. src/<Name>/ itself stays a plain directory of the project with no .git inside, and the copy is thrown away when the command ends.
  6. Creates the GitHub repository grim-<kebab-name> under --org, private unless --public, attaches it to the copy as origin over HTTPS and pushes main. --skip-push creates the repository and pushes nothing; the copy is thrown away all the same, so the repository stays empty and its first commit is yours to push from a clone, as in Create a module. No tag is pushed: the first release is yours to cut, see Create a module.
  7. Prints a summary and the next steps. Path, namespace, provider, model, plural, CRUD slug, table and the repository URL.

A local module or a unit

The project repository holds its whole tree, so a module needs no repository to be kept, reviewed and shared. Choose by who else needs it.

Local module Unit
Made with grim make:module <Name> --no-repo grim make:module <Name>
For Behaviour only this project needs A module other projects will install
Lives In the project's git, nowhere else In grimoiry/grim-<name>, a copy in each project
In grim.json local Yes No
In grim.lock No Yes, after grim require
A change goes Into a commit in the project; grim push says so Where it is released from, with grim push <Name>

Both have "type": "module" in module.json; what tells them apart is local in grim.json against the entry in grim.lock. grim require refuses a name listed in local. A module with "type": "core-module" belongs to core, and a change to one goes with grim push --core.

Commit a local module like any other code of the project, together with the two files the command changed.

$ git add src/Hooks grim.json config/modules-available.php
$ git commit -m "feat(hooks): scaffold the hooks module"

grim.json lists a local module under local, and config/modules-available.php carries its provider, so grim install enables it on a fresh machine. A machine whose storage/config/modules-enabled.php already exists needs the provider line added once; storage/ is not tracked. Prune never deletes a local module, and grim install and grim update resolve the units it requires. A local module made by an older grim still carries .github/ and is missing from local: delete .github/, commit, and the next grim prune offers to list it.

The names it derives

One module name becomes five others. Check them in the summary, because renaming later means touching every file.

Name Derived as Admissions --with-crud=Entry Override
Model Singular of the module name Entry --with-crud=<Model>
Plural Plural of the model Entries --plural
CRUD slug <module>/<models>, kebab-case admissions/entries --crud-slug
Table <module>_<models>, snake_case admissions_entries --table
Repository grim-<module>, kebab-case acme/grim-admissions with --org=acme --org

The slash in the slug is deliberate. It makes entries a sub-resource of admissions, and it is what lets the admin find the pages under Pages/Entries/.

Pitfalls

The pluralizer is naive

It knows the regular English endings and a handful of irregular words, nothing more. Look at the Plural row of the summary before you build on it. If it reads wrong, delete src/<Name>/ and run again with --plural.

A failed step leaves the directory behind

When the GitHub step fails, the module is already scaffolded and enabled; only the copy outside the project is gone. A second run stops at Module directory already exists: src/Admissions/. Either finish by hand inside src/Admissions/, or delete the directory and start over with the cause fixed.

The repository name is already taken

Repository acme/grim-admissions already exists. Pass --use-existing to reuse it (skips creation, just attaches as remote). The flag attaches the existing repository as origin. The push that follows fails if that repository already has history on main, so use it for an empty one.

The name of a core module

Heads up: 'Users' is a core module name. is a warning, not a refusal. A local module with the name of a core one may shadow it. Pick another name unless that is what you want.

After it finishes

The module is enabled but its table does not exist yet. The summary ends with the same list.

  1. Fill in fields() and grid() on src/Admissions/Models/Entry.php.
  2. Write a migration for admissions_entries into src/Admissions/Database/Migrations/.
  3. Adjust the menu group and the permissions in AdmissionsProvider.php.
  4. Run the migration inside the container.
$ grim shell
$ php artisan migrate

Then commit src/<Name>/ in the project. For a unit, the recipe Create a module goes on to its first release and grim require.

grim manifest:validate confirms the manifest still holds after you edit it.

Recipes that use it

The idea underneath