メインコンテンツまでスキップ
Version: Fegg 5.x(開発版)

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.

CommandWhat it does
migrationCreate 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 LOGINSet a new password for a CMS user
user:listList the CMS users
user:hashPrint a password hash for FEGG_CMS_INITIAL_PASSWORD_HASH
core:statusShow 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 changed when the database already matches, Fegg-cli migration diff : Finished migration after applying changes.
  • Then creates the first CMS user when FEGG_CMS_INITIAL_LOGIN is set and cms_user has no users at all, with FEGG_CMS_INITIAL_PASSWORD_HASH (or FEGG_CMS_INITIAL_PASSWORD) and FEGG_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:update runs 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 run fegg-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).

Output
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.

Output (example)
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.

OptionRemarks
vX.Y.ZVersion to install. The latest tag when omitted
--dry-runShow what would change without changing anything
--forceOverwrite 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/.htaccess denies access,
  • htdocs/.htaccess has the rule for _fegg/,
  • the upload directory htdocs/up_dir/ has its .htaccess that 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/.

CommandWhat it does
composer testPHPUnit: unit and HTTP integration tests (PHP 7.4 to 8.5). test:unit and test:integration run one suite
composer test:mysqlThe MySQL tests, against the server given in FEGG_TEST_MYSQL_DSN, FEGG_TEST_MYSQL_USER and FEGG_TEST_MYSQL_PASSWORD (skipped without them)
composer analysePHPStan (level 5, with a baseline)
composer cs:checkPSR-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.