fegg-cli
fegg-cli is the command line entry point in the project root. Run it from there:
./fegg-cli <command> [arguments]
Like htdocs/index.php, the file itself only defines the paths (FEGG_HTML_DIR, FEGG_CORE_DIR, FEGG_SITE_DIR). The commands are in htdocs/_fegg/core/bin/. Commands exit with code 1 when they find a problem, so they can be used in CI.
| Command | What it does |
|---|---|
migration | Create or change the tables (SQLite or MySQL) from the YAML schema files |
migration:db2schema <file> | Write the structure of the current database to a YAML file |
db:schema [table] [--json] | List tables and columns from the YAML schema files |
template:lint [file ...] | Check templates without running them |
user:create LOGIN [NAME] | Add a CMS user |
user:password LOGIN | Set a new password for a CMS user |
user:list | List the CMS users |
user:hash | Print a password hash for FEGG_CMS_INITIAL_PASSWORD_HASH |
core:status | Show the core version and core files changed in this project |
core:update [vX.Y.Z] [--dry-run] [--force] | Update the core from the skeleton repository |
doctor [URL] | Check that _fegg/ cannot be read from the web |
Running a command group without a sub command prints its help: ./fegg-cli core, ./fegg-cli db, ./fegg-cli template, ./fegg-cli user, ./fegg-cli migration:help, ./fegg-cli doctor:help.
migration
./fegg-cli migration
Compares the table definitions in the YAML files with the database (SQLite, or MySQL / MariaDB) and applies the difference (adds tables, columns and indexes).
- The core's files (
core/config/database/:cms_user,cms_file,cms_image,cms_login_attempt) are read first, then the project's (site/config/database/). A project file with the same name replaces the core's file. - Starts with one line naming the environment, its file and the database, e.g.
Environment: staging (site/config/env/staging.php, /var/www/vhosts/*/p[0-9]*) / DB: sqlite:...(see Environments and secrets). - Prints
Fegg-cli migration diff : Not changedwhen the database already matches,Fegg-cli migration diff : Finished migrationafter applying changes. - Then creates the first CMS user when
FEGG_CMS_INITIAL_LOGINis set andcms_userhas no users at all, withFEGG_CMS_INITIAL_PASSWORD_HASH(orFEGG_CMS_INITIAL_PASSWORD) andFEGG_CMS_INITIAL_NAME. An existing user is never changed and a deleted one is not created again, so this is safe on every deploy. Without a usable password it exits with 1 after the migration. fegg-cli core:updateruns it automatically after updating the core.- On MySQL the YAML types are mapped to MySQL types and changed tables are altered in place. See Database configuration.
/cms/migrate/in the CMS shows the same pending changes and applies them with its "Run migration" button (POST), for servers where you cannot runfegg-cli.
The YAML files are the single source of truth. A table that exists in the database but in no YAML file is dropped, and a column that is not in the YAML is removed with its data. Run it against a copy first when you are not sure that every table is defined.
To create a YAML file from an existing database (for example when you start writing schema files for an old project):
./fegg-cli migration:db2schema htdocs/_fegg/site/config/database/dump.yaml
All tables are written to the one file. Split it into one file per table if you keep it.
db:schema
./fegg-cli db:schema # every table
./fegg-cli db:schema cms_news # one table
./fegg-cli db:schema --json # as JSON
Lists the tables, columns (type, primary key, autoincrement, not null, default) and indexes defined in core/config/database/ and site/config/database/. It reads the YAML files, not the database, so it works without a database. It also warns when a table is missing one of the columns every table needs (valid, created, modified).
cms_news (htdocs/_fegg/site/config/database/cms_news.yaml)
news_id INTEGER primary key, autoincrement, not null
visible BOOLEAN
category INTEGER
title TEXT
...
valid BOOLEAN
post_date DATETIME
created DATETIME
modified DATETIME
index NEWS_ID unique (news_id)
template:lint
./fegg-cli template:lint # every template in site/template and core/template
./fegg-cli template:lint htdocs/_fegg/site/template/news/detail.tpl
Checks templates without running them and reports each problem with the .tpl file and line: unknown tags, unclosed blocks or extra {{ end }}, missing include / transclude targets, undefined modifiers, and output in URL attributes, <script> or event attributes that is not escaped for that context.
htdocs/_fegg/site/template/news/detail.tpl:12: unknown modifier "nosuch" (define it in site/functions/*.php)
htdocs/_fegg/site/template/news/detail.tpl:20: {{ $page.data.url }} starts a URL: write {{ $value|feggUrl }} so javascript: URLs are refused
NG: 2 problem(s) in 1 template(s)
Prints OK: <n> template(s) when there is nothing to fix. Run it after every template change.
CMS users
./fegg-cli user:create admin "Site administrator" # LOGIN [NAME]; NAME defaults to LOGIN
./fegg-cli user:password admin # set a new password
./fegg-cli user:list # user_id, login and name of every CMS user
Manage the CMS users (table cms_user). user:create and user:password ask for the password twice without showing it. It must have at least 8 characters, and it is stored with password_hash().
The password is never an argument (it would stay in the shell history). To pass it from a script, or where there is no terminal, give it on standard input with --password-stdin:
printf '%s\n' "$CMS_PASSWORD" | ./fegg-cli user:create admin --password-stdin
user:create refuses a login that already exists; use user:password for it.
user:hash prints password_hash() of a password (asked without echo, or --password-stdin) for FEGG_CMS_INITIAL_PASSWORD_HASH in a committed environment file.
Fegg 4 created users with the URL /cms/login/create/<name>/<login>/<password>. It is gone in Fegg 5. /cms/register/ still exists in the CMS, but it now needs a CMS login as well as developer mode (a developer_ip address on a server that is not production), so on a production server use fegg-cli.
core:status
./fegg-cli core:status
Prints the core version and compares the core files with core/manifest.json. No changes to core files. means the core is untouched and can be updated. Otherwise it lists the changed files; move those changes to overrides in site/ (see Directory structure).
core:update
./fegg-cli core:update --dry-run # show the difference only
./fegg-cli core:update # update to the latest release tag
./fegg-cli core:update v5.1.0 # update to a given version
Replaces the core with a release of the skeleton repository. See Updating the core for details.
| Option | Remarks |
|---|---|
vX.Y.Z | Version to install. The latest tag when omitted |
--dry-run | Show what would change without changing anything |
--force | Overwrite core files that were changed in the project |
core:manifest also exists, but it is for skeleton maintainers only (it rebuilds core/manifest.json).
doctor
./fegg-cli doctor # check the .htaccess files on disk
./fegg-cli doctor https://example.com # also request the live site
Without a URL, it checks the files on disk:
htdocs/_fegg/.htaccessdenies access,htdocs/.htaccesshas the rule for_fegg/,- the upload directory
htdocs/up_dir/has its.htaccessthat stops uploaded scripts from running (see Security).
With a URL, it also requests files under _fegg/ and the SQLite database (when it is under the document root) on the live site and reports any that can be read.
Prints OK: https://example.com does not expose _fegg/, or NG with the list of problems (exit code 1). You can run it from your local machine against the production URL, which is the easiest check after an FTP upload.
It also prints Warning: lines, without failing, for outdated rules in htdocs/.htaccess: a <IfModule mod_php5.c> block (ignored by PHP 7 and 8), and Apache 2.2 Order / Deny from lines (an HTTP 500 on Apache 2.4 without mod_access_compat; use Require all denied). Compare the file with core/dist/htaccess.dist.
install:class
./fegg-cli install:class <owner>/<repository>
Clones a Fegg library repository (from GitHub, or a full https:// / git: URL) and copies its src/ directory into htdocs/_fegg/site/lib/. This is the Fegg 4 library mechanism: classes in site/lib/ are only loaded through getClass(), which is deprecated in Fegg 5. For new code, put namespaced classes in site/src/ instead.
Developer commands in the skeleton
The skeleton repository also has Composer scripts for the test suite and code checks. Composer is only used for development; the application never loads vendor/.
| Command | What it does |
|---|---|
composer test | PHPUnit: unit and HTTP integration tests (PHP 7.4 to 8.5). test:unit and test:integration run one suite |
composer test:mysql | The MySQL tests, against the server given in FEGG_TEST_MYSQL_DSN, FEGG_TEST_MYSQL_USER and FEGG_TEST_MYSQL_PASSWORD (skipped without them) |
composer analyse | PHPStan (level 5, with a baseline) |
composer cs:check | PSR-12 check of core/src and the tests (composer cs:fix applies the fixes) |
The integration tests run with FEGG_ENV=development and a throwaway SQLite database, so they never touch site/data/db/db.db.