ForgePackageManager

The way your app installs, updates, and removes the capabilities it's built from. It's a package manager that behaves like apt, yum, or pacman — and it doesn't use Composer at all.

Overview

Every capability your app uses — a database layer, an ORM, authentication, a view engine — has to get onto disk somehow. ForgePackageManager is that "somehow." It pulls capabilities from the sources you trust, verifies what it downloads, installs them into your module folders, and records exactly what's there so the same app can be rebuilt reproducibly.

Think of it like a trusted supplier system rather than a central marketplace you can't control. You explicitly say which sources you trust — like telling your server which repositories to use — and the manager fetches from those, and only those.

What it gives you

  • Install, list, and remove capabilities
  • Multiple registry source types
  • Integrity verification (SHA-256)
  • Reproducible lock-file installs
  • Trusted-source confirmations
  • Automatic dependency install

It's Not Composer (and that's the point)

If Composer works for you, use it — the package manager isn't competing with it, and you can keep using Composer, Git submodules, or anything else. But if you've ever wished package management felt more like your Linux package manager — sources you define, units you can see, no hidden build steps — this is that.

The differences are deliberate, and they all come back to control:

Composer-style ForgePackageManager
Source code + build steps Ready-made ZIP units, extract and go
A central registry you depend on Sources you define and trust
Needs PHP runtime + tooling on servers CLI-driven, no build tooling required
Packages fetched from one ecosystem Fetch from Git, HTTP, FTP, SFTP, local, or a network share

Use Composer if you want the central registry, the constraint resolution, and that ecosystem. Use this if you want to control where capabilities come from and install complete, immutable units. Both can coexist — it's a choice, not a competition.

Units, Not Source Code

Each ability is shipped as a complete, self-contained ZIP file — a unit. No compilation, no build steps, no "then run this to build it." Download the ZIP, extract it, and it's ready.

Think of it like this: instead of getting ingredients and a recipe (source code + build instructions), you get a finished meal (a complete ZIP file). Just extract and serve.

  • Faster installs: no build time, just extract.
  • No build dependency: no Node, Composer, or other build tools on your server.
  • Reproducible: the same ZIP in is the same result out.
  • Immutable versions: each version is a complete, frozen unit.

Registries & Mirrors

The package manager works like old-school software mirrors: capabilities live in a plain folder layout you can navigate by hand. Every registry follows the same structure:

registry-root/
├── modules.json            # the index of everything available
└── modules/
    └── forge-auth/
        ├── 0.2.4/
        │   └── 0.2.4.zip    # one complete unit per version
        └── 0.2.3/
            └── 0.2.3.zip

Because it's just folders and files, you can browse any registry manually — on GitHub, over HTTP in your browser, or with an FTP client. There's no magic; the manager just automates the navigation and the verification for you.

The modules.json index

The index at the root lists every capability, its versions, where each one lives, and its integrity hash:

{
  "forge-auth": {
    "latest": "1.0.0",
    "versions": {
      "1.0.0": {
        "description": "ForgeAuth 1.0.0",
        "url": "forge-auth/1.0.0",
        "integrity": "sha256-33bd70eead56a2855e..."
      }
    }
  }
}

The url points to the folder relative to modules/; the integrity is a SHA-256 hash of the ZIP. When the manager downloads a unit it records this hash, and when you install from the lock file it re-verifies it — so a tampered or corrupted download is caught.

The Two Files: forge.json & forge-lock.json

Managing capabilities comes down to two files at the root of your app. They mirror what you're probably used to, if not by the same names.

forge.json — what you want

This is the file you edit by hand. Its modules block lists each capability and a version constraint (often just "latest"):

{
  "name": "My App",
  "kernel": { "name": "forge-kernel", "version": "latest" },
  "modules": {
    "forge-storage": "latest",
    "forge-error-handler": "0.1.0",
    "forge-wire": "latest"
  }
}

forge-lock.json — exactly what you got

This is generated for you. Every time you install, the manager pins the resolved version, the registry and source, the module_path, a SHA-256 integrity hash, and a sanitized copy of the source config (secrets stripped):

{
  "modules": {
    "forge-storage": {
      "version": "0.2.0",
      "registry": "kernel-module-registry",
      "module_path": "forge-storage/0.2.0",
      "integrity": "658ee144d77d034f04...",
      "source_type": "git",
      "source_config": {
        "name": "kernel-module-registry",
        "type": "git",
        "url": "https://github.com/forge-kernel/kernel-module-registry",
        "branch": "main",
        "private": false
      }
    }
  }
}

You don't manage this file — it's written on every install and cleaned up on every remove. Commit it to version control so any machine (or your CI/CD) can install the exact same set:

php forge.php package:install-project

package:install-project reads the lock file and reinstalls every pinned unit, verifying each integrity hash along the way — that's your reproducible build.

Trusted Sources

Like apt, the manager won't run post-install steps from a source you haven't explicitly trusted. When you install something, it tells you which registry it came from and asks whether to trust that source for future installations. Once trusted, that registry's post-install commands run without asking each time.

Trust is recorded per registry (by name) in storage/framework/trusted_sources.json. You keep full control — nothing runs automatically that you didn't approve, and you can choose to trust or not each time. For automation, the --trust-source flag (or --non-interactive/--auto) skips the prompt up front.

Separate from trust, integrity is always enforced with SHA-256 — trusting a source never disables integrity checking.

Commands

Four commands cover the whole lifecycle. All of them are registered as core commands, so there's no prefix to remember:

package:install-module

Install one or more capabilities. With no arguments it starts an interactive wizard — browse your registries, pick modules and versions (git registries show commit messages), and choose options.

php forge.php package:install-module [--module=<name[@version]>] [name[@version] ...] [--force]
php forge.php package:install-module --module=forge-wire
php forge.php package:install-module --module=forge-auth@1.2.0
php forge.php package:install-module forge-auth forge-storage forge-logger
php forge.php package:install-module forge-auth@1.2.0 forge-storage --force

Options: --force bypass the download cache · --debug show debug info · --non-interactive/--auto skip prompts · --trust-source auto-trust the source · --replace auto-replace existing modules · --config-mode=defaults|publish|env · --category=module|capability

package:remove-module

Remove an installed capability. It warns you up front — if the module ships migrations, seeders, or assets, it tells you these will be rolled back, lost, or unlinked before asking for confirmation.

php forge.php package:remove-module [--module=<name>] [name ...] [--force] [--debug]
php forge.php package:remove-module                    # interactive
php forge.php package:remove-module --module=forge-welcome
php forge.php package:remove-module forge-welcome forge-saas --force

package:list-modules

List the capabilities available in your configured registries, with descriptions and available versions.

php forge.php package:list-modules

package:install-project

Install everything from forge-lock.json — the reproducible install you run on a fresh checkout or in CI/CD. It verifies each pinned unit's integrity as it goes.

php forge.php package:install-project

Custom Registries

Your trusted sources live in config/source_list.php — created for you on first use, pre-filled with the official registry. Add your own registries there so you can fetch private or internal capabilities.

<?php

return [
    'registry' => [
        // The official registry (pre-filled for you)
        [
            'name' => 'kernel-module-registry',
            'type' => 'git',
            'url' => 'https://github.com/forge-kernel/kernel-module-registry',
            'branch' => 'main',
            'private' => false,
            'description' => 'Forge Kernel Official Modules',
            'personal_token' => env('GITHUB_TOKEN'), // only for private repos
        ],
        // Your own private registry
        [
            'name' => 'my-company-modules',
            'type' => 'git',
            'url' => 'https://github.com/mycompany/private-modules',
            'branch' => 'main',
            'private' => true,
            'personal_token' => env('GITHUB_TOKEN'),
        ],
    ],
    'cache_ttl' => 3600, // how long registry listings stay cached (seconds)
];

The name is what's used in the trust flow and stored in your lock file. Keep tokens in your .env and reference them with env() so secrets never sit in source_list.php.

Config & Source Types

The manager understands several kinds of sources, so you can host capabilities wherever works for you. Each registry entry's type picks the transport:

  • git — GitHub, GitLab, Bitbucket, or any Git repository (with a branch and optional token).
  • http — plain HTTP/HTTPS download (base URL, optional basic auth).
  • ftp / sftp — classic FTP/SFTP servers (host, port, credentials, base path).
  • local — a path on the local filesystem.
  • network — a local network file share.

When a capability declares configuration defaults, the manager can write that config out for you after install, controlled by --config-mode:

defaults — keep the module's built-in defaults; no config file is written.

publish — publish a config/<name>.php you can edit, reading values from env with fallbacks.

env — add the module's settings as .env overrides (for example, forge_wire.use_minified becomes FORGE_WIRE_USE_MINIFIED). This is the default when you run non-interactively.

Dependencies

Capabilities can declare what they need. A #[Requires(module: ..., version: ...)] attribute on a module says "bring these along," and the manager installs them automatically before the module itself:

  • Already-installed dependencies are skipped.
  • Circular chains are detected and rejected with a clear error.
  • Dependencies install recursively, so a required module's own requirements are resolved too.

This stays simple by design — flat units plus explicit requirements — rather than the heavy constraint-resolution engine of a tool like Composer.

Suggestions

These are just things that tend to work well. You do you — build however makes sense for your app.

  • Commit forge-lock.json to version control for consistent deployments.
  • Keep tokens out of source_list.php — read them from .env.
  • Use a version (not just "latest") in forge.json when a capability matters to reproducibility.
  • Test module installs in CI/CD to catch issues early.
  • Keep your registries organized and documented for your team.