{"id":20323885,"url":"https://github.com/flarum/cli","last_synced_at":"2025-04-09T14:12:56.163Z","repository":{"id":40465038,"uuid":"345688823","full_name":"flarum/cli","owner":"flarum","description":"A CLI helper for developing Flarum extensions.","archived":false,"fork":false,"pushed_at":"2024-10-27T08:57:21.000Z","size":4691,"stargazers_count":39,"open_issues_count":24,"forks_count":2,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-10-30T04:30:02.269Z","etag":null,"topics":["cli","flarum","generator"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flarum.png","metadata":{"funding":{"github":"flarum","open_collective":"flarum"},"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-08T14:44:26.000Z","updated_at":"2024-10-08T14:12:40.000Z","dependencies_parsed_at":"2024-05-29T01:59:56.674Z","dependency_job_id":"789fdf4b-6cc3-4e69-a250-e09ece617fa4","html_url":"https://github.com/flarum/cli","commit_stats":{"total_commits":464,"total_committers":12,"mean_commits":"38.666666666666664","dds":"0.24137931034482762","last_synced_commit":"42418e7740cb916b86141e808b36b799d7b85f67"},"previous_names":["flarum/flarum-cli"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flarum%2Fcli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flarum%2Fcli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flarum%2Fcli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flarum%2Fcli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flarum","download_url":"https://codeload.github.com/flarum/cli/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247565554,"owners_count":20959232,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","flarum","generator"],"created_at":"2024-11-14T19:30:11.810Z","updated_at":"2025-04-09T14:12:56.144Z","avatar_url":"https://github.com/flarum.png","language":"TypeScript","funding_links":["https://github.com/sponsors/flarum","https://opencollective.com/flarum"],"categories":[],"sub_categories":[],"readme":"\u003cp align=center\u003e\u003cimg alt=\"flarum-cli\" width=100 src=\"https://user-images.githubusercontent.com/20267363/127189519-ed809abd-5990-40b1-96a7-8d3156b78c3e.png\"\u003e\u003c/p\u003e\n\u003ch1 align=center\u003eFlarum CLI\u003c/h1\u003e\u003cp align=center\u003e\nA CLI for developing Flarum extensions\u003c/p\u003e\n\n\u003cp align=center\u003e\n\u003ca href=\"https://oclif.io\"\u003e\u003cimg alt\"oclif\" src=\"https://img.shields.io/badge/cli-oclif-brightgreen.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://npmjs.org/package/@flarum/cli\"\u003e\u003cimg alt\"Version\" src=\"https://img.shields.io/npm/v/@flarum/cli.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://npmjs.org/package/@flarum/cli\"\u003e\u003cimg alt\"Downloads/week\" src=\"https://img.shields.io/npm/dw/@flarum/cli.svg\"\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/flarum/cli/blob/master/package.json\"\u003e\u003cimg alt\"License\" src=\"https://img.shields.io/npm/l/@flarum/cli.svg\"\u003e\u003c/a\u003e\n\u003cbr\u003e\n\u003cimg width=720 src=\"https://sycho9.github.io/flarum-cli.svg\" alt=\"terminal_example\"\u003e\n\u003c/p\u003e\n\n- [Introduction](#introduction)\n- [Installation](#installation)\n- [Updating](#updating)\n- [Usage](#usage)\n- [Commands](#commands)\n\n## Introduction\n\nOne of our core values is **_Framework First_**, Flarum is as much a framework for extension development as it is a forum platform.\n\nThis tool was built to simplify the development of Flarum extensions by automating some repetitive and menial tasks. It allows to generate boilerplate skeleton for a new extension, common backend classes and frontend components, extenders, as well as other maintenance tasks.\n\n## Installation\n\n\u003c!-- installation --\u003e\n\n```sh-session\n$ npm install -g @flarum/cli\n```\n\n\u003c!-- installationstop --\u003e\n\n## Updating\n\n\u003c!-- updating --\u003e\n\n```sh-session\n$ npm update -g @flarum/cli\n```\n\n\u003c!-- updatingstop --\u003e\n\n## Usage\n\n\u003c!-- usage --\u003e\n```sh-session\n$ npm install -g @flarum/cli\n$ flarum-cli COMMAND\nrunning command...\n$ flarum-cli (-v|--version|version)\n@flarum/cli/2.0.0-beta.17 linux-x64 node-v16.13.2\n$ flarum-cli --help [COMMAND]\nUSAGE\n  $ flarum-cli COMMAND\n...\n```\n\u003c!-- usagestop --\u003e\n\nYou can also use `fl` instead of `flarum-cli` as a shorthand.\n\nTo see a list of available commands, run:\n\n```sh-session\nflarum-cli\n```\n\nor\n\n```sh-session\nflarum-cli --help\n```\n\n## Commands\n\nThe CLI has different types of commands for different tasks:\n\n**Initialisation**\n\n- `flarum-cli init [PATH]`: Generates a blank extension skeleton, including all recommended infrastructure.\n\n**Infrastructure**: See the [infrastructure](#infrastructure-modules) section for more information.\n\n- `flarum-cli infra [MODULE] [PATH]`: Adds (or updates) infrastructure for some part of extension infrastructure. You can see all available modules by running `fl-dev infra --help`.\n\n**Audit**: These commands help you make sure your extension is up to date.\n\n- `flarum-cli audit infra [--monorepo] [--fix]` Check that infrastructure files are up to date for all enabled modules.\n\n**Backend Boilerplate Generation**: Generates different types of backend classes and/or extenders, ready to be used.\n\n- `flarum-cli make backend api-controller [PATH]`\n- `flarum-cli make backend api-serializer [PATH]`\n- `flarum-cli make backend api-serializer-attributes [PATH]`\n- `flarum-cli make backend command [PATH]`\n- `flarum-cli make backend event-listener [PATH]`\n- `flarum-cli make backend handler [PATH]`\n- `flarum-cli make backend integration-test [PATH]`\n- `flarum-cli make backend job [PATH]`\n- `flarum-cli make backend migration [PATH]`\n- `flarum-cli make backend model [PATH]`\n- `flarum-cli make backend policy [PATH]`\n- `flarum-cli make backend repository [PATH]`\n- `flarum-cli make backend route [PATH]`\n- `flarum-cli make backend service-provider [PATH]`\n- `flarum-cli make backend validator [PATH]`\n\n**Frontend Boilerplate Generation**: Generate frontend components/classes, ready to be used.\n\n- `flarum-cli make frontend component [PATH]`\n- `flarum-cli make frontend modal [PATH]`\n- `flarum-cli make frontend model [PATH]`\n\n**Code Updates**: These commands help update extensions for newer versions of Flarum.\n\n- `flarum-cli update js-imports [PATH]`: Adds admin/forum/common namespaces to all JS imports from flarum core.\n\n_And of course, you can always use the help command to see a list of all available commands with their descriptions:_\n\n- `flarum-cli help [COMMAND]`\n\nAll commands can use a `--no-interaction` flag to proceed with default values for prompts when possible.\n\n## 🔥 The Most Powerful Commands\n\nOf all the aforementioned commands, the two most powerful ones that will make a huge difference, are the extension initialisation command and the backend **model** generation command. The former obviously allows to kickstart the extension with the recommended skeleton from the Core Dev team, while the latter not only creates the backend model, it allows to create all the classes related to the model, from just its name:\n\n- Table migration\n- Policy\n- API Serializer\n- CRUD API Controllers\n- CRUD Handlers\n- Repository\n- Validator\n- Routes\n- Related Extenders\n\n[center]\n![terminal_example](https://sycho9.github.io/flarum-cli.svg)\n![example_project_with_model_command](https://lh3.googleusercontent.com/-fUnfqQ7rwyo/YQVwwm0sa0I/AAAAAAAAFfE/-o9B30M2gE8y6d3NWaVgBhYa8xEwqLuNwCLcBGAsYHQ/s16000/Screenshot%2Bfrom%2B2021-07-31%2B16-46-38.png)\n[/center]\n\n## Infrastructure Modules\n\nIf you maintain more than just a few extensions, keeping the infrastructure up to date can be very tedious.\nWe heavily value developer experience, and make it a point to support automated testing, static analysis, formatting, and other tools\nthat help you work more happily and efficiently. However, all this means that there's a lot of config files to keep track of.\n\nThe idea behind Flarum CLI's infrastructure modules is simple: the config for some feature is associated with a \"module\".\nFor example, the \"typescript\" module includes:\n\n- The `js/tsconfig.json` file\n- Dependency reqs for `typescript`, `typescript-coverage-report`, and `flarum-tsconfig` in the `package.json` file.\n- Script configuration to check type safety and type coverage in the `package.json` file.\n\nSo to enable the `typescript` module, all Flarum CLI has to do is update those files and JSON config keys to the latest version.\n\nPlease note that running `flarum-cli infra` or `flarum-cli audit infra --fix` will only add new files/keys and overwrite existing ones; it will not delete old files or config keys.\n\nAlso, we strongly recommend carefully looking over the changes made by these commands before committing them: they should work the vast majority of the time, but there could be some corner cases.\n\n### Excluding Files\n\nSometimes, you'll want to customize some of these configuration files, and prevent `flarum-cli infra [MODULE]` or `flarum-cli audit infra --fix` from overriding your changes. You can also exclude any files from these updates by adding their relative path to the \"extra.flarum-cli.excludeScaffolding\" key's array in your extension's `composer.json` file. For example, if you wanted to exclude your tsconfig file from any updates by the infra system, your `composer.json` should look as follows:\n\n```json\n{\n  ...\n  \"extra\": {\n    \"flarum-cli\": {\n      \"excludeScaffolding\": [\n        \"js/tsconfig.json\"\n      ],\n      \"excludeScaffoldingConfigKeys\": {\n        \"composer.json\": [\n          \"scripts.test:setup\"\n        ],\n      ...\n    },\n    ...\n  }\n  ...\n}\n```\n\n## For Maintainers\n\nThis section is for maintainers of the flarum-cli codebase.\n\n### Key Concepts\n\nCLI commands are implemented by [oclif](https://oclif.io/) commands. Reference their documentation for information about the command layer.\n\nSince there's a wide range of stuff that we might want to generate, in Flarum CLI, commands are just a layer for user interaction.\nThe base command is implemented at [src/base-command.ts](https://github.com/flarum/cli/blob/3c90d6321f4750aef954c9d6157b13603d9cfdef/src/base-command.ts#L28-L28), and as you can see, the basic process of all commands is:\n\n1. Show a welcome message\n2. Get the path of the parent Flarum extension, or fail if not in one\n3. Confirm we are in the right directory\n4. Confirm that files will be overwritten / clear files as needed\n5. Run a set of registered steps\n6. Tell the user which steps ran, or display any errors\n7. Show a goodbye message\n\nActual functionality is implemented at the step layer.\n\n### PHP Subsystem\n\nAs a quick side note, the `php-subsystem` subdirectory contains a small PHP project that uses the excellent `nikic/php-parser` library to add extenders to `extend.php`.\n\nThe \"add extenders\" feature is effectively a function that receives current extend.php contents and an extender spec, and returns new extend.php contents.\nIn the frontend, there's an interface called `ExtenderDef` which encodes a format for generating extenders.\n\nMore documentation (and functionality!) will be added later.\n\n### Steps and the Step Manager\n\n#### What are steps?\n\nSteps represent meaningful, granular operations. Some are bigger and some are smaller, but they include:\n\n- Initializing an extension from boilerplate\n- Updating some part of an extension from boilerplate (like adding backend testing)\n- Adding an extender to extend.php\n- Creating a template code file from a stub\n- Running `composer install`\n\nIn general, tasks should be divided into steps on groups of reusability and optionality.\nIf some part of a task is optional, it should probably be a step.\nIf some subtask might be needed by multiple commands, that should be a step too.\n\nThe actual logic of a step is implemented in the async `run` method. This method takes:\n\n- An in-memory filesystem object (through `mem-fs`)\n- A paths object, which is a class that can be used to get paths relative to the extension root, the working directory, the requested directory (if the command was run with the `path` arg specified), and the monorepo directory (if in a subdirectory of a monorepo).\n- An `io` instance, which can be used to prompt the user for input or display outputs / errors.\n- An object of \"providers\", which represent additional functionality. For Flarum commands, these are:\n  - A \"php provider\", which can be used to interact with the PHP subsystem to add extenders to extend.php.\n\nSteps should modify the in-memory filesystem with their changes and return it.\n\nSteps also declare a human-readable type, a list of parameters that they expose to other steps, a method to get those exposed parameters, and whether they are composable.\n\n### What is the Step Manager?\n\nCommands have a `steps` method, where each comment implements a chain of events via a fluent API.\n\nThis fluent API is accomplished through the Step Manager class, and supports useful features like:\n\n- Optional steps, with options to configure a confirmation message and whether the default prompt value should be to run or not to run the step.\n- Sharing parameters between steps. For example, if one step creates an event listener and a second step adds that listener to extend.php, we can share information about the created listener class from the first step to the second step, and don't need to prompt the user for it again.\n- Step dependencies. If a step receives parameters from other steps, any of those are optional and don't run, the step in question won't even attempt to run.\n\nSharing parameters is done via the following process:\n\n- The source step is declared via the `namedStep` method\n- The consuming step includes a list of dependencies, which include\n  - Which step they are getting the param from\n  - What the param is exposed as in the source step\n  - What the param is consumed at\n  - If the param value is falsy, should the consumer step run at all? Useful for variable dependencies.\n\nFor example:\n\n```ts\n(new StepManager())\n  .step(stubStepFactory('Standalone'))\n  .step(stubStepFactory('Standalone'))\n  .namedStep('model', stubStepFactory('Generate Model', true, [], { modelClass: 'Something' }))\n  .step(stubStepFactory('Generate Controller'), { optional: false }, [{\n    sourceStep: 'model',\n    exposedName: 'modelClass',\n  }])\n  .step(stubStepFactory('Generate Serializer'), { optional: false }, [{\n    sourceStep: 'model',\n    exposedName: 'modelClass',\n    consumedName: 'targetModelClass',\n  }])\n  .atomicGroup((stepManager: StepManager) =\u003e {\n    stepManager\n      .namedStep('listener', stubStepFactory('Generate Listener', true, [], { listenerClass: 'Something Else' }))\n      .step(stubStepFactory('Listener Extender'), { optional: false }, [\n        {\n          sourceStep: 'listener',\n          exposedName: 'listenerClass',\n        },\n        {\n          sourceStep: 'model',\n          exposedName: 'modelClass',\n          consumedName: 'isnt_used_here_but_why_not',\n          dontRunIfFalsy: true,\n        },\n      ]);\n  })\n  .run(new Paths({...}), new PromptsIO(), {php: new PhpProvider(...)});\n```\n\n#### Step Composability and Atomic Groups\n\nAs noted above, steps should work by modifying and returning an in-memory filesystem.\nIf possible, steps should NEVER make changes directly to the filesystem.\n\nHaving steps work like this means that we can compose sequences of multiple steps, and only save their changes when all have ran.\nThis way, if something errors, we don't end up creating half-finished changes. This can be done by wrapping steps in a callback by calling the `atomicGroup` method of stepManager.\n\nUnfortunately, some steps `npm install` and `composer update` work by invoking external systems that make changes to the filesystem directly, so they are marked as non-composable.\n\n#### Step Types and Schemas\n\nTo avoid repetition, base classes have been introduced for some step types.\n\nSteps for generating extenders should use `src/steps/extenders/base`, where all they need to specify is an extender def and a list of params they need.\n\nSteps for generating templates from php stubs should use `src/steps/stubs/php-base`, where they just need to describe what stub file they use, which params they need, and a recommended namespace where the generated file should be placed.\n\n### Testing\n\nMost of this architecture was driven by TDD and BDD development, allowing for a highly decoupled system.\n\nAny additions of steps, modifications of providers, or changes of functionality of the step manager should be accompanied by unit tests.\n\nThe 3 base step types actually provide a simple interface to generate test cases. See the relevant test files for more info.\n\nSince commands directly affect the filesystem, unit tests for them aren't really possible. It would also be quite complex to unit test all commands in addition to all steps. For this reason, commands should be manually tested, but unit tests are not currently required.\n\n### Monorepo Support\n\nFlarum CLI was designed with monorepos in mind.\nWhen registering a step, you can provide it an array of subdirectories to run it in.\nFor each of these subdirectories, an instance of the step will run with the `paths` object returning the subdirectory as `package()`, and the original root directory as `monorepo()`.\n\nA monorepo should have a `flarum-monorepo.json` file in the root directory with the following (optional) keys:\n\n- `packages.core`: a path to Flarum Core, if present.\n- `packages.extensions`: an array of paths to Flarum extensions.\n- `packages.composer`: an array of paths to non-extension PHP Composer packages published on Packagist.\n- `packages.npm`: an array of paths to non-extension JS/TS packages published on NPM.\n\n### Scaffolding Generation\n\nOne powerful use-case of Flarum CLI is creating new extensions, and keeping infrastructure in sync for existing extensions.\nFor example, if managing 100 extensions, making sure the prettier config is consistent for all of them can be very tedious. Additionally, it can be annoying to have to add TypeScript config to every extension, and it's easy to make mistakes.\n\nFor this purpose, Flarum CLI's `Scaffolder` system formalizes the concepts of initializing and updating extension infrastructure.\n\nAt the core level, there's a boilerplate directory (`boilerplate/skeleton/extension`) that contains the scaffolding for a new extension.\n\nThe scaffolder receives a set of modules, which represent some aspect of infrastructure (e.g. JS, TypeScript, Backend Testing, etc).\nModules can be updatable (e.g. TypeScript) or not (e.g. the core setup of an `extend.php` file).\nThey can also be togglable (e.g. JS), or not, in which case they are always on.\n\nModules own files in the scaffolding directory, as well as keys in JSON config files. Every file and key must belong to at least one module. If any keys in a JSON config file belong to a module, all keys must belong to a module; if no keys are owned, the config file is treated like any other file.\n\nModules also have template params, which are used as variables in the scaffolding.\n\nThere's a lot more features and guaruntees than that, see the `Module` interface for more information.\n\nThe main benefit to this system is that given this scaffolding / module setup, it is possible to generate steps to initialize a new extension, update/add a module to an existing extension, or audit an extension for any outdated infrastructure. This is how the `init`, `infra`, and `audit infra` commands are implemented.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflarum%2Fcli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflarum%2Fcli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflarum%2Fcli/lists"}