diff --git a/en/00_Getting_Started/00_Server_Requirements.md b/en/00_Getting_Started/00_Server_Requirements.md
index 6ead9ab61..cfc4e9925 100644
--- a/en/00_Getting_Started/00_Server_Requirements.md
+++ b/en/00_Getting_Started/00_Server_Requirements.md
@@ -94,7 +94,7 @@ relevant i18n `lang` directories.
Silverstripe CMS allows CMS authors to upload files into the `public/assets/` folder, which should be served by your
webserver. **No PHP execution should be allowed in this folder**. This is configured for Apache by default
-via `public/assets/.htaccess`. The file is generated dynamically during the `dev/build` stage.
+via `public/assets/.htaccess`. The file is generated dynamically when building the database.
Additionally, access is whitelisted by file extension through a dynamically generated whitelist based on
the `File.allowed_extensions` setting
diff --git a/en/00_Getting_Started/03_Environment_Management.md b/en/00_Getting_Started/03_Environment_Management.md
index 5cd17a036..d7d9e6a31 100644
--- a/en/00_Getting_Started/03_Environment_Management.md
+++ b/en/00_Getting_Started/03_Environment_Management.md
@@ -130,5 +130,5 @@ Silverstripe core environment variables are listed here, though you're free to d
| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a `Psr\Cache\CacheItemPoolInterface`, `Psr\SimpleCache\CacheInterface`, or `SilverStripe\Core\Cache\CacheFactory` class implementation. |
| `SS_IGNORE_DOT_ENV` | If set, the `.env` file will be ignored. This is good for live to mitigate any performance implications of loading the `.env` file. |
| `SS_BASE_URL` | The URL to use when it isn't determinable by other means (for example for CLI commands). Should either start with a protocol (e.g. `https://www.example.com`) or with a double forward slash (e.g. `//www.example.com`). |
-| `SS_FLUSH_ON_DEPLOY` | Try to detect deployments through file system modifications and flush on the first request after every deploy. Does not run "dev/build" - only "flush". Possible values are `true` (check for a framework PHP file modification time), `false` (no checks, skip deploy detection) or a path to a specific file or folder to be checked. See [DeployFlushDiscoverer](api:SilverStripe\Core\Startup\DeployFlushDiscoverer) for more details.
False by default. |
+| `SS_FLUSH_ON_DEPLOY` | Try to detect deployments through file system modifications and flushes the cache on the first request after every deploy. Note this does not trigger buildin the database. Possible values are `true` (check for a framework PHP file modification time), `false` (no checks, skip deploy detection) or a path to a specific file or folder to be checked. See [DeployFlushDiscoverer](api:SilverStripe\Core\Startup\DeployFlushDiscoverer) for more details.
False by default. |
| `SS_TEMP_PATH` | File storage used for the default cache adapters in [Manifests](/developer_guides/execution_pipeline/manifests), [Object Caching](/developer_guides/performance/caching) and [Partial Template Caching](/developer_guides/templates/partial_template_caching). Can be an absolute path (with a leading `/`), or a path relative to the project root. Defaults to creating a sub-directory of PHP's built-in `sys_get_temp_dir()` or using the `silverstripe-cache` directory relative to the project root if one is present. |
diff --git a/en/00_Getting_Started/index.md b/en/00_Getting_Started/index.md
index bae6ed0a3..52c25068f 100644
--- a/en/00_Getting_Started/index.md
+++ b/en/00_Getting_Started/index.md
@@ -48,13 +48,15 @@ SS_ENVIRONMENT_TYPE=""
Now you should be able to build your database by running this command:
```bash
-vendor/bin/sake dev/build
+vendor/bin/sake db:build
```
+> [!TIP]
+> Check out [Sake](/developer_guides/cli/sake) for more details about using Sake on the command line
+
Your website should be available on your domain now (e.g. `https://www.example.com`). The CMS login can be accessed at `/admin` (e.g. `http://www.example.com/admin`).
-For more information on how to maintain your installation or install projects, check
-out [Using Silverstripe with Composer](composer).
+For more information on how to maintain your installation or install projects, check out [Using Silverstripe with Composer](composer).
## Keep learning
diff --git a/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md b/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md
index 450a70b33..100d95e05 100644
--- a/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md
+++ b/en/02_Developer_Guides/00_Model/01_Data_Model_and_ORM.md
@@ -52,10 +52,10 @@ so on. After writing this class, we need to regenerate the database schema.
After adding, modifying or removing `DataObject` subclasses, make sure to rebuild your Silverstripe CMS database. The
database schema is generated automatically by visiting `/dev/build` (e.g. `https://www.example.com/dev/build`) in your browser
-while authenticated as an administrator, or by running `sake dev/build` on the command line (see [Command Line Interface](/developer_guides/cli/) to learn more about `sake`).
+while authenticated as an administrator, or by running `sake db:build` on the command line (see [Sake](/developer_guides/cli/sake/) to learn more about using Sake).
> [!NOTE]
-> In "dev" mode, you do not need to be authenticated to run `/dev/build`. See [Environment Types](/developer_guides/debugging/environment_types) for more information.
+> In "dev" mode, you do not need to be authenticated to visit `/dev/build`. See [Environment Types](/developer_guides/debugging/environment_types) for more information.
This script will analyze the existing schema, compare it to what's required by your data classes, and alter the schema
as required.
diff --git a/en/02_Developer_Guides/00_Model/02_Relations.md b/en/02_Developer_Guides/00_Model/02_Relations.md
index 933adb131..55b541020 100644
--- a/en/02_Developer_Guides/00_Model/02_Relations.md
+++ b/en/02_Developer_Guides/00_Model/02_Relations.md
@@ -1191,7 +1191,7 @@ for displaying the objects contained in the relation.
The [`RelationValidationService`](api:SilverStripe\Dev\Validation\RelationValidationService) can be used to check if your relations are set up according to best practices, and is very useful for debugging unexpected behaviour with relations. It is disabled by default.
-To enable this service, set the following YAML configuration, which will give you validation output every time you run `dev/build`.
+To enable this service, set the following YAML configuration, which will give you validation output every time you run `sake db:build`.
```yml
SilverStripe\Dev\Validation\RelationValidationService:
@@ -1228,7 +1228,7 @@ SilverStripe\Dev\Validation\RelationValidationService:
- 'App\Model\Player.Teams'
```
-### Validating relations outside dev/build
+### Validating relations any time
If you want to, you can invoke the `RelationValidationService` at any time in PHP code.
diff --git a/en/02_Developer_Guides/00_Model/12_Indexes.md b/en/02_Developer_Guides/00_Model/12_Indexes.md
index 29fc00d1d..d7572ea76 100644
--- a/en/02_Developer_Guides/00_Model/12_Indexes.md
+++ b/en/02_Developer_Guides/00_Model/12_Indexes.md
@@ -96,8 +96,8 @@ other columns. If this is indexed, smaller and reasonably unique it might be fas
## Index creation/destruction
-Indexes are generated and removed automatically during a `dev/build`. Caution if you're working with large tables and
-modify an index as the next `dev/build` will `DROP` the index, and then `ADD` it.
+Indexes are generated and removed automatically when building the database. Caution if you're working with large tables and
+modify an index as the next time the database is built it will `DROP` the index, and then `ADD` it.
## API documentation
diff --git a/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md b/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md
index 17931ba15..1a1dc00fd 100644
--- a/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md
+++ b/en/02_Developer_Guides/00_Model/How_Tos/Dynamic_Default_Fields.md
@@ -104,7 +104,7 @@ class Dog extends DataObject
## Static default records
-The [DataObject::$default_records](api:SilverStripe\ORM\DataObject::$default_records) array allows you to specify default records created on dev/build.
+The [DataObject::$default_records](api:SilverStripe\ORM\DataObject::$default_records) array allows you to specify default records created when the database is built.
A simple example of this is having a region model and wanting a list of regions created when the site is built:
diff --git a/en/02_Developer_Guides/03_Forms/How_Tos/05_Simple_Contact_Form.md b/en/02_Developer_Guides/03_Forms/How_Tos/05_Simple_Contact_Form.md
index b701a438b..e5118ae74 100644
--- a/en/02_Developer_Guides/03_Forms/How_Tos/05_Simple_Contact_Form.md
+++ b/en/02_Developer_Guides/03_Forms/How_Tos/05_Simple_Contact_Form.md
@@ -85,7 +85,7 @@ To show the form on the page, we need to render it in our template. We do this b
The reason it's standard practice to name the form function 'Form' is so that we don't have to create a separate template for each page with a form. By adding $Form to the generic Page.ss template, all pages with a form named 'Form' will have their forms shown.
-If you now create a ContactPage in the CMS (making sure you have rebuilt the database and flushed the templates /dev/build?flush=all) and visit the page, you will now see a contact form.
+If you now create a ContactPage in the CMS (making sure you have rebuilt the database and flushed the templates e.g. `sake db:build --flush`) and visit the page, you will now see a contact form.
![a form with three text fields ("name", "email", and "message") and a submit button](../../../_images/howto_contactForm.jpg)
diff --git a/en/02_Developer_Guides/04_Configuration/01_SiteConfig.md b/en/02_Developer_Guides/04_Configuration/01_SiteConfig.md
index a63f2e1f8..fede04e1b 100644
--- a/en/02_Developer_Guides/04_Configuration/01_SiteConfig.md
+++ b/en/02_Developer_Guides/04_Configuration/01_SiteConfig.md
@@ -68,8 +68,7 @@ Silverstripe\SiteConfig\SiteConfig:
```
> [!WARNING]
-> After adding the class and the YAML change, make sure to rebuild your database by visiting `https://www.example.com/dev/build`.
-> You may also need to reload the screen with a `?flush=1` i.e.`https://www.example.com/admin/settings?flush=1`.
+> After adding the class and the YAML change, make sure to rebuild your database and flush the cache by running `sake db:build --flush`.
You can define as many extensions for `SiteConfig` as you need. For example, if you're developing a module and want to
provide the users a place to configure site-wide settings then the `SiteConfig` panel is the place to go it.
diff --git a/en/02_Developer_Guides/05_Extending/00_Modules.md b/en/02_Developer_Guides/05_Extending/00_Modules.md
index 1014e20c3..66fd36f3c 100644
--- a/en/02_Developer_Guides/05_Extending/00_Modules.md
+++ b/en/02_Developer_Guides/05_Extending/00_Modules.md
@@ -52,7 +52,7 @@ To lock down to a specific version, branch or commit, read up on
["lock" files](https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control).
> [!WARNING]
-> After you add or remove modules, make sure you rebuild the database, class and configuration manifests by going to `https://www.example.com/dev/build?flush=1`
+> After you add or remove modules, make sure you rebuild the database and flush the cache by running `sake db:build --flush`
## Creating a module {#create}
diff --git a/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md b/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md
index 405c5194c..7cf928561 100644
--- a/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md
+++ b/en/02_Developer_Guides/06_Testing/00_Unit_Testing.md
@@ -72,7 +72,7 @@ application database. To provide seed data use a [Fixture](fixtures) file.
> [!WARNING]
> The test database is rebuilt every time one of the test methods is run and is removed afterwards. If the test is interrupted, the database will not be removed. Over time, you may have several hundred test
-> databases on your machine. To get rid of them, run `sake dev/tasks/CleanupTestDatabasesTask`.
+> databases on your machine. To get rid of them, run `sake tasks:CleanupTestDatabasesTask`.
## Custom PHPUnit configuration
diff --git a/en/02_Developer_Guides/07_Debugging/02_URL_Variable_Tools.md b/en/02_Developer_Guides/07_Debugging/02_URL_Variable_Tools.md
index f74967450..e465bcb2f 100644
--- a/en/02_Developer_Guides/07_Debugging/02_URL_Variable_Tools.md
+++ b/en/02_Developer_Guides/07_Debugging/02_URL_Variable_Tools.md
@@ -70,7 +70,7 @@ Redirections](/developer_guides/controllers/redirection) for more information an
| URL Variable | Values | Description |
| ------------ | ------ | ----------- |
| quiet | 1 | Don't show messages during build |
- | dont_populate | 1 | Don't run **requireDefaultRecords()** on the models when building. This will build the table but not insert any records |
+ | no-populate | 1 | Don't run **requireDefaultRecords()** on the models when building. This will build the table but not insert any records |
## Config diagnostic URLs
diff --git a/en/02_Developer_Guides/08_Performance/06_ORM.md b/en/02_Developer_Guides/08_Performance/06_ORM.md
index 09ba16850..699ff6948 100644
--- a/en/02_Developer_Guides/08_Performance/06_ORM.md
+++ b/en/02_Developer_Guides/08_Performance/06_ORM.md
@@ -25,12 +25,12 @@ SilverStripe\Forms\TreeDropdownField:
See [SearchFilter Modifiers](/developer_guides/model/searchfilters/) for more information about search filters.
-## Skipping check and repair during dev/build {#skip-check-and-repair}
+## Skipping check and repair when the database is built {#skip-check-and-repair}
-When you run `dev/build`, there is a step that checks the integrity of the database tables (via `CHECK TABLE`) and repairs issues (via `REPAIR TABLE`) if possible.
+When you run `sake db:build`, there is a step that checks the integrity of the database tables (via `CHECK TABLE`) and repairs issues (via `REPAIR TABLE`) if possible.
For tables with many records (tens/hundreds of thousands) this can be slow. If you identify that you have some specific `DataObject` models with lots of records
-which are slowing down your `dev/build`, you might want to explicitly skip checks for those:
+which are slowing down building the database, you might want to explicitly skip checks for those:
```yml
SilverStripe\ORM\Connect\DBSchemaManager:
diff --git a/en/02_Developer_Guides/09_Security/00_Member.md b/en/02_Developer_Guides/09_Security/00_Member.md
index 69ae9f796..a34126f99 100644
--- a/en/02_Developer_Guides/09_Security/00_Member.md
+++ b/en/02_Developer_Guides/09_Security/00_Member.md
@@ -175,23 +175,24 @@ For example:
namespace App\Task;
use App\Model\DataRecord;
-use BadMethodCallException;
-use SilverStripe\Control\Director;
use SilverStripe\Dev\BuildTask;
+use SilverStripe\PolyExecution\PolyOutput;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
class CleanRecordsTask extends BuildTask
{
- public function run($request)
+ private static bool $can_run_in_browser = false;
+
+ protected function execute(InputInterface $input, PolyOutput $output): int
{
- if (!Director::is_cli()) {
- throw new BadMethodCallException('This task only runs on CLI');
- }
$admin = Security::findAnAdministrator();
Member::actAs($admin, function () {
DataRecord::get()->filter('Dirty', true)->removeAll();
});
+ return Command::SUCCESS;
}
}
```
diff --git a/en/02_Developer_Guides/13_i18n/index.md b/en/02_Developer_Guides/13_i18n/index.md
index 24da44f0f..2a0abbc87 100644
--- a/en/02_Developer_Guides/13_i18n/index.md
+++ b/en/02_Developer_Guides/13_i18n/index.md
@@ -294,32 +294,23 @@ otherwise it won't pick up locale changes.
## Collecting text
-To collect all the text in code and template files we have just to visit: `https://www.example.com/dev/tasks/i18nTextCollectorTask`
+To collect all the text in code and template files we have just to visit `https://www.example.com/dev/tasks/i18nTextCollectorTask` or run `sake tasks:i18nTextCollectorTask` on the command line.
Text collector will then read the files, build the string table for each module where it finds calls to the
underscore function, and tell you about the created files and any possible entity redeclaration.
If you want to run the text collector for just one module you can use the 'module' parameter:
-`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=silverstripe%2Fcms`
+`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=silverstripe%2Fcms` or `sake tasks:i18nTextCollectorTask --module=silverstripe/cms`
You can also run the text collector against multiple specific modules by separating the module names with a comma:
-`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=silverstripe%2Fcms,silverstripe%2Fframework`
+`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=silverstripe%2Fcms,silverstripe%2Fframework` or `sake tasks:i18nTextCollectorTask --module=silverstripe/cms,silverstripe/framework`
> [!NOTE]
> The `%2F` in `silverstripe%2Fcms` is a `/` which has been encoded for use in a URL in a non-ambiguous way.
The text collector also collects text for themes - if you want to run text collection on a specific theme, reference the theme
with prefix `themes:`, e.g:
-`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=themes:my-theme`
-
-> [!TIP]
-> You can also run this task via the command line using sake, e.g:
->
-> ```bash
-> sake dev/tasks/i18nTextCollectorTask module=themes:my-theme,silverstripe/framework
-> ```
->
-> See [the sake documentation](/developer_guides/cli/) for details about using sake.
+`https://www.example.com/dev/tasks/i18nTextCollectorTask/?module=themes:my-theme` or `sake tasks:i18nTextCollectorTask --module="themes:my-theme"`
## Module priority
diff --git a/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md b/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md
index d981616ad..8126848b1 100644
--- a/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md
+++ b/en/02_Developer_Guides/15_Customising_the_Admin_Interface/How_Tos/Extend_CMS_Interface.md
@@ -138,7 +138,7 @@ SilverStripe\CMS\Model\SiteTree:
- App\Extension\BookmarkedPageExtension
```
-In order to add the field to the database, run a `dev/build/?flush=all`.
+In order to add the field to the database, run `sake db:build --flush`.
Refresh the CMS, open a page for editing and you should see the new checkbox.
## Retrieve the list of bookmarks from the database
diff --git a/en/02_Developer_Guides/17_CLI/01_Sake.md b/en/02_Developer_Guides/17_CLI/01_Sake.md
new file mode 100644
index 000000000..5e9b6f2cf
--- /dev/null
+++ b/en/02_Developer_Guides/17_CLI/01_Sake.md
@@ -0,0 +1,104 @@
+---
+title: Sake
+summary: Run commands against your Silverstripe CMS project on the command line
+icon: terminal
+---
+
+# Sake
+
+Sake is a CLI application powered by [`symfony/console`](https://symfony.com/doc/current/console.html).
+
+## Installation
+
+Sake can be invoked using `vendor/bin/sake` in your terminal of choice with no additional actions required.
+
+If you only have a single Silverstripe CMS project running on your server (for example in a production environment), you might want to add the `vendor/bin/` directory to your `$PATH` so you can invoke it simply as `sake`. Most documentation will assume you have done this, and will say to run `sake`, rather than `vendor/bin/sake`.
+
+You can get auto completion when typing commands if you are using a supported shell in your terminal. All commands support name and option completion, and some can even complete argument values. Run `sake completion --help` for instructions. You should only need to do this once (per user or per server, depending on how you configure it) and it will work for all projects on that server.
+
+After setting up completion (or if you never intend to set it up) you might want to hide the `completion` command from Sake's command list. You can do that with this YAML configuration:
+
+```yml
+SilverStripe\Cli\Sake:
+ hide_completion_command: true
+```
+
+## Configuration
+
+### Base URL
+
+Sometimes Silverstripe CMS needs to know the URL of your site. For example, when sending an email or generating static
+files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
+command line, it has no way of knowing.
+
+You can use the `SS_BASE_URL` environment variable to specify this.
+
+```bash
+SS_BASE_URL="https://www.example.com"
+```
+
+### Showing or hiding tasks from the command list
+
+Projects often end up with a lot of tasks, which can clutter the command list. Sake will automatically hide tasks from the main command list when there are too many tasks.
+
+You can configure the threshold for this behaviour with the [`Sake.max_tasks_to_display`](api:SilverStripe\Cli\Sake->max_tasks_to_display) configuration property:
+
+```yml
+SilverStripe\Cli\Sake:
+ max_tasks_to_display: 30
+```
+
+When there are more than that many tasks, the tasks will be hidden. Run `sake tasks` to see the full list at any time.
+
+You can set the value to `0` to *always* display the tasks in the main command list.
+
+### Adding commands
+
+There are two types of commands that can be added to Sake:
+
+- regular symfony commands
+- [`PolyCommand`](api:SilverStripe\PolyExecution\PolyCommand) subclasses
+
+Both of those can be added to Sake with the below configuration, but `PolyCommand` subclasses can also be added in a few other ways depending on their purpose. See [`PolyCommand`](/developer_guides/cli/polycommand) for more information about those.
+
+To add commands to Sake, add them to the [`Sake.commands`](api:SilverStripe\Cli\Sake->commands) configuration property:
+
+```yml
+SilverStripe\Cli\Sake:
+ commands:
+ - 'App\Cli\Command\MyCommand'
+```
+
+See [symfony/console documentation](https://symfony.com/doc/current/console.html#creating-a-command) for details about how to create a symfony command (though note the information about "registering the command" and "running the command" in that documentation doesn't apply to Sake).
+
+## Usage
+
+Run `sake help` at any time for information about how to use Sake.
+
+Here are some common commands you can run with Sake:
+
+```bash
+# list available commands
+sake # or `sake list`
+
+# list available tasks
+sake tasks
+
+# build the database
+sake db:build
+
+# flush the cache
+sake flush # or use the `--flush` flag with any other command
+
+# get help info about a command (including tasks)
+sake --help # e.g. `sake db:build --help`
+```
+
+> [!CAUTION]
+> You should run `sake` with the same system user that runs your web server. Otherwise you will have a separate filesystem cache for CLI and you won't be able to flush or warm your webserver cache using Sake.
+
+Sake doesn't use your project's routing and controllers for normal execution, but if you do specifically need to access an HTTP route in your application from the CLI, you can use the `sake navigate` command.
+
+```bash
+sake navigate about-us/teams
+```
diff --git a/en/02_Developer_Guides/17_CLI/02_Hybrid_Commands.md b/en/02_Developer_Guides/17_CLI/02_Hybrid_Commands.md
new file mode 100644
index 000000000..16e6f09b7
--- /dev/null
+++ b/en/02_Developer_Guides/17_CLI/02_Hybrid_Commands.md
@@ -0,0 +1,172 @@
+---
+title: PolyCommand
+summary: Code that can be run both from the command line and via HTTP requests
+icon: tasks-alt
+---
+
+# `PolyCommand` {#polycommand}
+
+Some code needs to be accessible both via HTTP requests over the web and via CLI in your terminal. Common examples of this are building the database and flushing cache.
+
+The [`PolyCommand`](api:SilverStripe\PolyExecution\PolyCommand) class provides a consistent API for getting input and providing output regardless of the context where the code is run. The API for this class is intentionally similar to the API for the `Command` class provided by `symfony/console`.
+
+> [!TIP]
+> See [the symfony/console input documentation](https://symfony.com/doc/current/console/input.html#using-command-options) for more specific details about using `InputInterface` and `InputOption`. Note that we use the [`getOptions()`](api:SilverStripe\PolyExecution\PolyCommand::getOptions()) method instead of `configure()` to return an array of `InputOption` objects. `PolyCommand` does not allow arguments.
+>
+> See [the symfony/console colouring documentation](https://symfony.com/doc/current/console/coloring.html) for information about styling output. Note that all output (including outputting for HTTP requests) as well as the command description and help info can use the `symfony/console` styling format.
+
+```php
+namespace App\Cli\Command;
+
+use SilverStripe\PolyExecution\PolyCommand;
+use SilverStripe\PolyExecution\PolyOutput;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+
+class MyPolyCommand extends PolyCommand
+{
+ protected static string $commandName = 'app:my-command';
+
+ protected string $title = 'My command';
+
+ protected static string $description = 'A command that does something';
+
+ public function run(InputInterface $input, PolyOutput $output): int
+ {
+ if ($input->getOption('do-action')) {
+ $output->writeln('Doing something...');
+ }
+ return Command::SUCCESS;
+ }
+
+ public function getOptions(): array
+ {
+ return [
+ new InputOption('do-action', null, InputOption::VALUE_NONE, 'do something specific'),
+ ];
+ }
+}
+```
+
+> [!NOTE]
+> You can also optionally implement the static [`getHelp()`](api:SilverStripe\PolyExecution\PolyCommand::getHelp()) method to provide additional context about the command. This helps to keep the description short.
+
+Once you've set up your command in PHP, you can add an HTTP route to is using the regular [`Director` routing rules](/developer_guides/controllers/routing/), and add it to Sake with the [`Sake.commands`](api:SilverStripe\Cli\Sake->commands) configuration property. Note that this isn't necessary for the special subclasses mentioned in the sections below.
+
+```yml
+---
+Name: polycommands
+---
+SilverStripe\Control\Director:
+ rules:
+ my-command: 'App\Cli\Command\MyPolyCommand'
+
+SilverStripe\Cli\Sake:
+ commands:
+ - 'App\Cli\Command\MyPolyCommand'
+```
+
+This command could then be used by visiting `https://www.example.com/my-command` in a browser or running `sake app:my-command` in CLI.
+
+## Configuration
+
+For every `PolyCommand` subclass, including the special kinds listed below, you can set the following configuration:
+
+- [`can_run_in_cli`](api:SilverStripe\PolyExecution\PolyCommand->can_run_in_cli): Whether the command can be run in CLI
+- [`can_run_in_browser`](api:SilverStripe\PolyExecution\PolyCommand->can_run_in_browser): Whether the command can be run in HTTP requests
+- [`permissions_for_browser_execution`](api:SilverStripe\PolyExecution\PolyCommand->permissions_for_browser_execution): An array of permissions a user must have to run the command in an HTTP request
+
+These can espcially be useful for defining how commands provided by modules can be executed.
+
+## `BuildTask`
+
+`BuildTask` is a `PolyCommand` subclass which is often used for one-off tasks. Common examples include:
+
+- Migrating data from an old database schema after deploying an updated codebase.
+- Migrating data from an old database schema after updating a module.
+- Updating many values in the database at once after a change in business logic.
+
+You can see the list of available tasks by either navigating to `/dev/tasks` in your browser or running `sake tasks` in a terminal.
+
+You can disable a task completely by setting the [`is_enabled`](api:SilverStripe\Dev\BuildTask->is_enabled) configuration property to `false`. This is useful when third-party modules add tasks that you don't want to use in your project.
+
+You can also set the `can_run_in_cli` and `can_run_in_browser` configuration properties as mentioned in [`PolyCommand`](#polycommand) above.
+
+The API for a `BuildTask` is effectively identical to other `PolyCommand` subclasses. Note that instead of implementing `run()` directly, you should implement the `execute()` method. There is some logic in [`BuildTask::run()`](api:SilverStripe\Dev\BuildTask::run()) which will output the title of your command before calling `execute()`. Once your task has finished, it will also output whether it was successful or not (based on the return value) and how long it took.
+
+> [!WARNING]
+> Unlike other `PolyCommand` subclasses, you cannot include a namespace for tasks. This is because the command name is also used as the URL segment for HTTP execution of the task, and in the CLI the `tasks:` namespace is automatically applied.
+
+```php
+namespace App\Tasks;
+
+use SilverStripe\Dev\BuildTask;
+use SilverStripe\PolyExecution\PolyOutput;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+
+class MyCustomTask extends BuildTask
+{
+ protected static string $commandName = 'my-custom-task';
+
+ protected string $title = 'My custom task';
+
+ protected static string $description = 'A task that does something custom';
+
+ protected function execute(InputInterface $input, PolyOutput $output): int
+ {
+ if ($input->getOption('do-action')) {
+ $output->writeln('Doing something...');
+ }
+ return Command::SUCCESS;
+ }
+
+ public function getOptions(): array
+ {
+ return [
+ new InputOption('do-action', null, InputOption::VALUE_NONE, 'do something specific'),
+ ];
+ }
+}
+```
+
+You don't need to do anything to register a `BuildTask` - it will be given an HTTP route and be registered with Sake simply by existing.
+
+The above command would be accessible via the browser at `/dev/tasks/my-custom-task` and via CLI with `sake tasks:my-custom-task`.
+
+### Running regular tasks with cron
+
+On a UNIX machine, you can typically run a scheduled task with a [cron job](https://en.wikipedia.org/wiki/Cron). Run
+your task in as a cron job using `sake`.
+
+For example, the following will run `MyTask` every minute.
+
+```bash
+* * * * * /your/site/folder/vendor/bin/sake tasks:my-task
+```
+
+## `DevCommand` and `/dev/*` {#dev-commands}
+
+There are a set of repeatable actions that developers often need to run which are accessible by browser and in CLI by default, so that developers aren't hindered by restrictions imposed by their hosting provider. These include building the database and viewing the values of all configuration properties.
+
+These commands can be seen by visiting `/dev` in your browser. There is no specific namespace or command to list these in Sake, as their command names vary based on their purpose.
+
+These commands are all subclasses of [`DevCommand`](api:SilverStripe\Dev\Command\DevCommand), which is itself a subclass of `PolyCommand`. The `DevCommand` API is identical to `BuildTask` above, with these exceptions:
+
+- Unlike tasks you can include a namespace in the `commandName` if you want to.
+- A description, title, and command name are mandatory for dev commands but are given default values for tasks.
+- There is no single configuration property for disabling dev commands.
+
+Like tasks, when a dev command is executed, the title of the command will be executed first. There is no default output for after a dev command has finished executing.
+
+These are registered by adding them to the [`DevelopmentAdmin.commands`](api:SilverStripe\Dev\DevelopmentAdmin->commands) configuration property. The key is the URL segment for the command over HTTP requests.
+
+```yml
+SilverStripe\Dev\DevelopmentAdmin:
+ commands:
+ my-dev-command: 'App\Dev\MyDevCommand'
+```
+
+For the above example, the command would be accessible over HTTP by visiting `/dev/my-dev-command`. The CLI command name is based on the `commandName` property for that class.
diff --git a/en/02_Developer_Guides/17_CLI/index.md b/en/02_Developer_Guides/17_CLI/index.md
index 016dba788..16fb1289c 100644
--- a/en/02_Developer_Guides/17_CLI/index.md
+++ b/en/02_Developer_Guides/17_CLI/index.md
@@ -1,172 +1,19 @@
---
-title: Command Line Interface
+title: Command Line Interface (CLI)
summary: Automate Silverstripe CMS, run Cron Jobs or sync with other platforms through the Command Line Interface.
introduction: Automate Silverstripe CMS, run Cron Jobs or sync with other platforms through the Command Line Interface.
icon: terminal
---
-# Command line interface
+# Command line interface (CLI)
-Silverstripe CMS can call [Controllers](../controllers) through a command line interface (CLI) just as easily as through a
-web browser. This functionality can be used to automate tasks with cron jobs, run unit tests, or anything else that
-needs to interface over the command line.
+Silverstripe CMS comes with a CLI application powered by [`symfony/console`](https://symfony.com/doc/current/console.html). The applicaiton is called "Sake".
-The main entry point for any command line execution is `cli-script.php` in the framework module.
-For example, to run a database rebuild from the command line, use this command:
-
-```bash
-cd your-project-root/
-php vendor/silverstripe/framework/cli-script.php dev/build
-```
+This application comes with several useful commands out-of-the-box, and can be customised by projects and modules with additional functionality.
> [!WARNING]
> Your command line PHP version is likely to use a different configuration as your webserver (run `php -i` to find out
> more). This can be a good thing, your CLI can be configured to use higher memory limits than you would want your website
> to have.
-## Sake - Silverstripe CMS make
-
-Sake is a simple wrapper around `cli-script.php`. It also tries to detect which `php` executable to use if more than one
-are available. It is accessible via `vendor/bin/sake`.
-
-> [!NOTE]
-> If you are using a Debian server: Check you have the php-cli package installed for sake to work. If you get an error
-> when running the command PHP -v, then you may not have php-cli installed so sake won't work.
-
-### Installation
-
-`sake` can be invoked using `./vendor/bin/sake`. For easier access, copy the `sake` file into `/usr/bin/sake`.
-
-```bash
-cd your-project-root/
-sudo ./vendor/bin/sake installsake
-```
-
-> [!WARNING]
-> This currently only works on UNIX like systems, not on Windows.
-
-### Configuration
-
-Sometimes Silverstripe CMS needs to know the URL of your site. For example, when sending an email or generating static
-files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
-command line, it has no way of knowing.
-
-You can use the `SS_BASE_URL` environment variable to specify this.
-
-```bash
-SS_BASE_URL="https://www.example.com/base-url"
-```
-
-### Usage
-
-`sake` can run any controller by passing the relative URL to that controller.
-
-```bash
-sake /
-# returns the homepage
-
-sake dev/
-# shows a list of development operations
-```
-
-`sake` is particularly useful for running build tasks.
-
-```bash
-sake dev/build "flush=1"
-```
-
-> [!CAUTION]
-> You have to run "sake" with the same system user that runs your web server,
-> otherwise "flush" won't be able to clean the cache properly.
-
-It can also be handy if you have a long running script..
-
-```bash
-sake dev/tasks/MyReallyLongTask
-```
-
-### Running processes
-
-`sake` can be used to make daemon processes for your application.
-
-Make a task or controller class that runs a loop. To avoid memory leaks, you should make the PHP process exit when it
-hits some reasonable memory limit. Sake will automatically restart your process whenever it exits.
-
-Include some appropriate `sleep()`s so that your process doesn't hog the system. The best thing to do is to have a short
-sleep when the process is in the middle of doing things, and a long sleep when doesn't have anything to do.
-
-This code provides a good template:
-
-```php
-namespace App\CLI;
-
-use SilverStripe\Control\Controller;
-
-class MyProcess extends Controller
-{
- private static $url_segment = 'my_process';
-
- private static $allowed_actions = [
- 'index',
- ];
-
- public function index()
- {
- // This isn't allowed to be accessed via the browser
- if (!Director::is_cli()) {
- $this->httpError(401);
- }
-
- set_time_limit(0);
-
- while (memory_get_usage() < 32 * 1024 * 1024) {
- if ($this->somethingToDo()) {
- $this->doSomething();
- sleep(1);
- } else {
- sleep(300);
- }
- }
- }
-
- // ...
-}
-```
-
-Make sure you [route the controller](/developer_guides/controllers/routing/).
-
-Then the process can be managed through `sake`
-
-```bash
-sake -start my_process
-sake -stop my_process
-```
-
-> [!WARNING]
-> `sake` stores `pid` and log files in the project root directory.
-
-## Arguments
-
-Parameters can be added to the command. All parameters will be available in `$_GET` array on the server.
-
-```bash
-cd your-project-root/
-php vendor/silverstripe/framework/cli-script.php myurl myparam=1 myotherparam=2
-```
-
-Or if you're using `sake`
-
-```bash
-vendor/bin/sake myurl "myparam=1&myotherparam=2"
-```
-
-## Running regular tasks with cron
-
-On a UNIX machine, you can typically run a scheduled task with a [cron job](https://en.wikipedia.org/wiki/Cron). Run
-`BuildTask` in Silverstripe CMS as a cron job using `sake`.
-
-The following will run `MyTask` every minute.
-
-```bash
-* * * * * /your/site/folder/vendor/bin/sake dev/tasks/MyTask
-```
+[CHILDREN includeFolders]
diff --git a/en/02_Developer_Guides/18_Cookies_And_Sessions/03_Managing_Sessions.md b/en/02_Developer_Guides/18_Cookies_And_Sessions/03_Managing_Sessions.md
index 8e69b32b0..9ba11b1b6 100644
--- a/en/02_Developer_Guides/18_Cookies_And_Sessions/03_Managing_Sessions.md
+++ b/en/02_Developer_Guides/18_Cookies_And_Sessions/03_Managing_Sessions.md
@@ -56,8 +56,8 @@ It does not use changes to this metadata to invalidate sessions.
Logged in users have the ability to see their own active sessions across all devices
and browsers where they have logged in, and can choose to log out any of those sessions.
-Administrators can revoke *all* active sessions for *all* users by triggering the `dev/tasks/InvalidateAllSessions`
-task either in the browser or via the CLI. Note that this will also revoke the session
+Administrators can revoke *all* active sessions for *all* users by visiting `/dev/tasks/InvalidateAllSessions`
+ or running `sake tasks:InvalidateAllSessions` via the CLI. Note that this will also revoke the session
of the user activating the task, so if this is triggered via the browser, that user
will need to log back in to perform further actions.
@@ -171,14 +171,14 @@ SilverStripe\SessionManager\Services\GarbageCollectionService:
#### Via `symbiote/silverstripe-queuedjobs` (recommended)
-If you have the `symbiote/silverstripe-queuedjobs` module installed and configured, garbage collection will run automatically every 1 day via `GarbageCollectionJob`, and no further action is required. This job will be automatically created if it does not exist on dev/build.
+If you have the `symbiote/silverstripe-queuedjobs` module installed and configured, garbage collection will run automatically every 1 day via `GarbageCollectionJob`, and no further action is required. This job will be automatically created if it does not exist when building the database.
#### Via `LoginSessionGarbageCollectionTask`
Alternatively, you can create a system cron entry to run the `LoginSessionGarbageCollectionTask` directly on a regular cadence:
```text
-`*/5 * * * * /path/to/webroot/vendor/bin/sake dev/tasks/LoginSessionGarbageCollectionTask
+`*/5 * * * * /path/to/webroot/vendor/bin/sake tasks:LoginSessionGarbageCollectionTask
```
### Anonymize IP
diff --git a/en/08_Changelogs/6.0.0.md b/en/08_Changelogs/6.0.0.md
index d15135d6b..6e626a904 100644
--- a/en/08_Changelogs/6.0.0.md
+++ b/en/08_Changelogs/6.0.0.md
@@ -7,6 +7,7 @@ title: 6.0.0 (unreleased)
## Overview
- [Features and enhancements](#features-and-enhancements)
+ - [Changes to `sake`, `BuildTask`, CLI interaction in general](#cli-changes)
- [Run `CanonicalURLMiddleware` in all environments by default](#url-middleware)
- [Changes to default cache adapters](#caching)
- [Changes to scaffolded form fields](#scaffolded-fields)
@@ -29,6 +30,242 @@ title: 6.0.0 (unreleased)
## Features and enhancements
+### Changes to `sake`, `BuildTask`, and CLI interaction in general {#cli-changes}
+
+Until now, running `sake` on the command line has executed a simulated HTTP request to your Silverstripe CMS project, using the routing and controllers that your web application uses to handle HTTP requests. This resulted in both a non-standard CLI experience and added confusion about when an [`HTTPRequest`](api:SilverStripe\Control\HTTPRequest) actually represented an HTTP request.
+
+We've rebuilt Sake using [`symfony/console`](https://symfony.com/doc/current/console.html) - the same package that powers Composer.
+
+Here are some common commands you can run with Sake:
+
+```bash
+# list available commands
+sake # or `sake list`
+
+# list available tasks
+sake tasks
+
+# build the database
+sake db:build
+
+# flush the cache
+sake flush # or use the `--flush` flag with any other command
+
+# get help info about a command (including tasks)
+sake --help # e.g. `sake db:build --help`
+```
+
+> [!NOTE]
+> To reduce upgrade pains we've retained backwards compatability with the legacy syntax for `dev/*` routed actions (e.g. `sake dev/build flush=1` will still work). This allows you to continue using existing scripts and cron jobs.
+> This legacy syntax is deprecated, and will stop working in a future major release.
+
+If for some reason you specifically need to access an HTTP route in your project using Sake, you can use the new `sake navigate` command, e.g. `sake navigate about-us/teams`.
+
+See [sake](/developer_guides/cli/sake) for more information about using and configuring `sake`, including how to register your own custom commands.
+
+There is also a new [`PolyCommand`](api:SilverStripe\PolyExecution\PolyCommand) class which provides a standardised API for code that needs to be accessible from both an HTTP request and CLI. This is used for `BuildTask` and other code accessed via `/dev/*` as mentioned below.
+
+See [`PolyCommand`](/developer_guides/cli/polycommand) for more details about the `PolyCommand` API.
+
+#### Changes to `BuildTask` {#cli-buildtask}
+
+##### Change to API {#cli-buildtask-api}
+
+The [`BuildTask`](api:SilverStripe\Dev\BuildTask) class is now a subclass of `PolyCommand`.
+
+As a result of this, any `BuildTask` implementations in your project or module will need to be updated. The upgrade will likely look like this in most cases:
+
+```diff
+ namespace App\Tasks;
+
+-use SilverStripe\Control\Director;
+-use SilverStripe\Control\HTTPRequest;
+ use SilverStripe\Dev\BuildTask;
++use SilverStripe\PolyExecution\PolyOutput;
++use Symfony\Component\Console\Command\Command;
++use Symfony\Component\Console\Input\InputInterface;
++use Symfony\Component\Console\Input\InputOption;
+
+ class MyCustomTask extends BuildTask;
+ {
+- private static $segment = 'my-custom-task';
++ protected static string $commandName = 'my-custom-task';
+
+- protected $title = 'My custom task';
++ protected string $title = 'My custom task';
+
+- protected $description = 'A task that does something custom';
++ protected static string $description = 'A task that does something custom';
+
+- public function run(HTTPRequest $request)
++ protected function execute(InputInterface $input, PolyOutput $output): int
+ {
++ if ($input->getOption('do-action')) {
+- if ($request->getVar('do-action')) {
+- if (Director::is_cli()) {
+- echo "Doing something...\n"
+- } else {
+- echo "Doing something...
\n";
+- }
++ $output->writeln('Doing something...');
+ }
+- echo "Done\n";
++ return Command::SUCCESS;
+ }
++
++ public function getOptions(): array
++ {
++ return [
++ new InputOption('do-action', null, InputOption::VALUE_NONE, 'do something specific'),
++ ];
++ }
+ }
+```
+
+Note that you should no longer output "done" or some equivalent message at the end of the task. Any time a task finishes executing, the output will include a message about whether the task completed successfully or failed (based on the return value) and how long it took.
+
+See [`PolyCommand`](/developer_guides/cli/polycommand#buildtask) for more details about the `BuildTask` API.
+
+##### Change to names {#cli-buildtask-names}
+
+Some tasks were relying on the FQCN instead of having an explicit name. This means the name used for both the URL and the CLI command were excessively long.
+
+The way these are displayed in the new `sake tasks` list doesn't suit long names, so we have given explicit names to these tasks in order to make them shorter. If you have scripts or cron jobs that reference these you will need to update them to use the new name.
+
+For example, you used to navigate to `/dev/tasks/` or use `sake dev/tasks/`. Now you will need to navigate to `/dev/tasks/` or use `sake tasks:`.
+
+|class|old name|new name|
+|---|---|---|
+|[`ContentReviewEmails`](api:SilverStripe\ContentReview\Tasks\ContentReviewEmails)|`SilverStripe-ContentReview-Tasks-ContentReviewEmails`|`content-review-emails`|
+|[`DeleteAllJobsTask`](api:Symbiote\QueuedJobs\Tasks\DeleteAllJobsTask)|`Symbiote-QueuedJobs-Tasks-DeleteAllJobsTask`|`delete-queued-jobs`|
+|[`MigrateContentToElement`](api:DNADesign\Elemental\Tasks\MigrateContentToElement)|`DNADesign-Elemental-Tasks-MigrateContentToElement`|`elemental-migrate-content`|
+|[`UserFormsColumnCleanTask`](api:SilverStripe\UserForms\Task\UserFormsColumnCleanTask)|`SilverStripe-UserForms-Task-UserFormsColumnCleanTask`|`userforms-column-clean`|
+|[`StaticCacheFullBuildTask`](api:SilverStripe\StaticPublishQueue\Task\StaticCacheFullBuildTask)|`SilverStripe-StaticPublishQueue-Task-StaticCacheFullBuildTask`|`static-cache-full-build`|
+
+#### Changes to `/dev/*` actions {#cli-dev}
+
+With the changes to `sake` come changes to the way `dev/*` actions are handled. Most of these are now subclasses of the new [`DevCommand`](api:SilverStripe\Dev\Command\DevCommand) class which is itself a subclass of `PolyCommand`.
+
+If you have custom actions registered under `DevelopmentAdmin.registered_controllers` you'll need to update the YAML configuration for these. If you want them to be accessible via CLI, you'll also have to update the PHP code.
+
+With the below example, there are two custom actions displayed in the list at `/dev`:
+
+- `/dev/my-action`: intended for use in the browser only, but you'd have to add custom logic in `init()` to disallow its use in CLI until now
+- `/dev/my-other-action`: intended for use both in CLI and in the browser.
+
+For actions that should only be accessible in the browser, you only need to change how these are registered. Move them from `DevelopmentAdmin.registered_controllers` to the new [`DevelopmentAdmin.controllers`](api:SilverStripe\Dev\DevelopmentAdmin->controllers) configuration property.
+
+Controllers added to `DevelopmentAdmin.registered_controllers` can only be accessed via HTTP requests, so you can remove any logic around CLI usage.
+
+For actions that should be accessible in the browser *and* via CLI, you will need to change these from being a `Controller` to subclassing `DevCommand`. These get registered to the new [`DevelopmentAdmin.commands`](api:SilverStripe\Dev\DevelopmentAdmin->commands) configuration property
+
+```diff
+ SilverStripe\Dev\DevelopmentAdmin:
+- registered_controllers:
+- my-action:
+- controller: 'App\Dev\MyActionController'
+- links:
+- my-action: 'Perform my custom action in dev/my-action (do not run in CLI)'
+- my-other-action:
+- controller: 'App\Dev\MyOtherActionController'
+- links:
+- my-other-action: 'Perform my custom action in dev/my-other-action'
++ controllers:
++ my-action:
++ class: 'App\Dev\MyActionController'
++ description: 'Perform my custom action in dev/my-action'
++ commands:
++ my-other-action: 'App\Dev\MyOtherActionCommand'
+```
+
+```diff
+ namespace App\Dev;
+
+-use SilverStripe\Control\Controller;
+-use SilverStripe\Control\Director;
+-use SilverStripe\Control\HTTPRequest;
++use SilverStripe\Dev\Command\DevCommand;
+-use SilverStripe\Dev\DevelopmentAdmin;
++use SilverStripe\PolyExecution\PolyOutput;
+-use SilverStripe\Security\Permission;
+ use SilverStripe\Security\PermissionProvider;
+-use SilverStripe\Security\Security;
++use Symfony\Component\Console\Command\Command;
++use Symfony\Component\Console\Input\InputInterface;
++use Symfony\Component\Console\Input\InputOption;
+
+-class MyOtherActionController extends Controller implements PermissionProvider
++class MyOtherActionController extends DevCommand implements PermissionProvider
+ {
++ protected static string $commandName = 'app:my-other-action';
++
++ protected static string $description = 'Perform my custom action in dev/my-other-action or via sake app:my-other-action';
++
++ private static array $permissions_for_browser_execution = [
++ 'MY_CUSTOM_PERMISSION',
++ ];
++
++ public function getTitle(): string
++ {
++ return 'My other action';
++ }
++
+- protected function init(): void
+- {
+- parent::init();
+-
+- if (!$this->canInit()) {
+- Security::permissionFailure($this);
+- }
+- }
+-
+- public function index(HTTPRequest $request)
++ protected function execute(InputInterface $input, PolyOutput $output): int
+ {
+- $someVar = $request->getVar('some-var');
++ $input->getOption('some-var');
+- if (Director::is_cli()) {
+- $body = "some output\n";
+- } else {
+- $body = "some output
\n";
+- }
++ $output->writeln('some output');
+-
+- return $this->getResponse()->setBody($body);
++ return Command::SUCCESS;
+ }
+
++ public function getOptions(): array
++ {
++ return [
++ new InputOption('some-var', null, InputOption::VALUE_NONE, 'some get variable'),
++ ];
++ }
+- public function canInit(): bool
+- {
+- return (
+- Director::isDev()
+- || (Director::is_cli() && DevelopmentAdmin::config()->get('allow_all_cli'))
+- || Permission::check('MY_CUSTOM_PERMISSION')
+- );
+- }
+
+ // ...
+ }
+```
+
+You would now access the `/dev/my-action` action via an HTTP request only. The `/dev/my-other-action` action can be access via an HTTP request, or by using `sake app:my-other-action` on the command line.
+
+The `some-var` option can be used in a query string when running the action via an HTTP request, or as a flag (e.g. `sake app:my-other-action --some-var`) in CLI.
+
+See [`PolyCommand`](/developer_guides/cli/polycommand#dev-commands) for more details about the `DevCommand` API.
+
+#### `sake -start` and `sake -stop` have been removed {#cli-daemon}
+
+Sake used to have functionality to make daemon processes for your application. This functionality was managed with `sake -start my-process` and `sake -stop my-process`.
+
+We've removed this functionality. Please use an appropriate daemon tool such as `systemctl` to manage these instead.
+
### Run `CanonicalURLMiddleware` in all environments by default {#url-middleware}
In Silverstripe CMS 5 [`CanonicalURLMiddleware`](api:SilverStripe\Control\Middleware\CanonicalURLMiddleware) only runs in production by default. This lead to issues with `fetch` and APIs behaving differently in production environments to development. Silverstripe 6.0 changes this default to run the rules in `dev`, `test`, and `live` by default.