# plugin:install Register a third-party artisan plugin into the consumer project. After adding a package to `pubspec.yaml` and running `flutter pub get`, `plugin:install` wires the plugin's `ArtisanServiceProvider` without requiring manual edits to `bin/artisan.dart` or `lib/app/_plugins.g.dart`. --- ## Basic Usage ```bash dart run fluttersdk_artisan plugin:install ``` `` is the plugin's pubspec package name (e.g. `magic_logger`). The package must be listed in `pubspec.yaml` and resolved via `flutter pub get` before this command runs. Pre-flight checks (`lib/src/commands/plugin_install_command.dart:197-241`): `bin/artisan.dart` must exist, the package must appear in `pubspec.yaml`, and `.dart_tool/package_config.json` must contain the package name. Any failure exits with code `1` before any write is attempted. --- ## Synopsis Full signature as declared in `lib/src/commands/plugin_install_command.dart:67`: ``` plugin:install {name : Plugin pubspec package name (e.g. magic_logger)} {--provider= : Override the auto-derived provider class name} {--bootstrap-command= : Plugin install sub-command to chain after registration} {--use-yaml-only : Fail if install.yaml not found instead of falling back to legacy injection} {--force : Bypass conflict detection (inherited from ArtisanInstallCommand)} {--dry-run : Print staged ops without writing (inherited)} {--non-interactive : Skip prompts, use defaults (inherited)} {--no-bootstrap : Skip post-install bootstrap hint message (inherited)} ``` The four inherited flags come from `ArtisanInstallCommand.baseFlags` and are available on every install command, not only `plugin:install`. | Flag | Effect | |---|---| | `--provider=ClassName` | Overrides `ArtisanProvider` convention (Mode 3 only). | | `--bootstrap-command=cmd` | Overrides the post-registration bootstrap hint (e.g. `logger:install`). | | `--use-yaml-only` | Error out when no `install.yaml` found; no Mode 2 or Mode 3 fallback. | | `--force` | Bypass conflict detection; overwrite user-modified files. | | `--dry-run` | Print every staged op without touching disk. | | `--non-interactive` | Skip interactive prompts; use defaults. Required in non-TTY environments. | | `--no-bootstrap` | Suppress the post-install bootstrap hint message. | --- ## Three Routing Modes The core of `handle` dispatches through three distinct code paths (`lib/src/commands/plugin_install_command.dart:130-181`). The selection logic runs in strict priority order: Mode 1 wins when an `install.yaml` manifest is present; Mode 2 wins when the canonical scaffold barrel exists; Mode 3 is the final fallback. ### Mode 1: install.yaml Manifest Flow **Condition**: `resolveInstallYaml(name)` returns a non-null path. The resolver checks `/install.yaml` first, then `/assets/install.yaml` (`lib/src/commands/plugin_install_command.dart:110-127`). `ManifestParser.parseFile` parses the YAML into an `InstallManifest`. `ManifestInstaller` stages `InstallOperation` objects on an `InstallTransaction`. `InstallTransaction.commit` writes every file to a `.tmp` path first; on full success, all `.tmp` files are renamed atomically. Any `.tmp` write failure deletes all staged temps and returns `Error(rolledBack: true)`. On `Success`, the plugin entry is written to `.artisan/plugins.json` and `PluginsRefreshCommand` regenerates `lib/app/_plugins.g.dart` in-process. Preferred authoring path: declarative, conflict-checked, dry-run-capable, and partially reversible via `plugin:uninstall` (V1 limitations; see Reversibility). ### Mode 2: Canonical Scaffold Fast Path **Condition**: No `install.yaml` found AND `lib/app/_plugins.g.dart` exists at the project root (`lib/src/commands/plugin_install_command.dart:172-178`). `_registerArtisanProvider` runs directly: writes a `PluginEntry` to `.artisan/plugins.json` and regenerates `_plugins.g.dart`. No `bin/artisan.dart` edit occurs. Covers plugins without a manifest whose consumers used `install`. Naming convention: `my_plugin` maps to `package:my_plugin/cli.dart` and class `MyPluginArtisanProvider`. ### Mode 3: Legacy bin/artisan.dart Injection **Condition**: No `install.yaml` found AND `lib/app/_plugins.g.dart` does not exist (`lib/src/commands/plugin_install_command.dart:180`). 1. `import 'package:/cli.dart';` is appended to `bin/artisan.dart` via `ConfigEditor.addImportToFile` (idempotent; skips when already present). 2. `registry.registerProvider(());` is inserted after the `registry.registerAll(auto.commands, ...)` anchor, or before the `ArtisanApplication` construction line when the anchor is absent. 3. Provider class defaults to `ArtisanProvider`; override with `--provider=ClassName`. Retained for backward compatibility with plugins predating the `install.yaml` schema. Pass `--use-yaml-only` to error out instead of reaching this fallback. --- ## Examples ### 1. Manifest-based plugin install (Mode 1) ```bash flutter pub add magic_logger dart run fluttersdk_artisan plugin:install magic_logger ``` Output: ``` Success: applied 4 ops; record at .artisan/installed/magic_logger.json Bootstrap with: artisan logger:install ``` ### 2. Canonical scaffold, no install.yaml (Mode 2) ```bash flutter pub add awesome_plugin dart run fluttersdk_artisan plugin:install awesome_plugin ``` Output: ``` Registered "awesome_plugin" via canonical scaffold (no install.yaml needed). ``` ### 3. Dry-run preview ```bash dart run fluttersdk_artisan plugin:install magic_logger --dry-run ``` Output: ``` DryRun: previewed 4 ops; no changes written WriteFile lib/config/logger.dart InjectImport lib/main.dart InjectAfterPattern lib/app/providers/app_service_provider.dart AddPubspecAsset assets/logging/ ``` --- ## Idempotency Re-running `plugin:install` for the same plugin is safe in all three modes. `PluginsRegistryFile.addPlugin` replaces by name so `.artisan/plugins.json` always contains exactly one entry per plugin. `ConfigEditor.addImportToFile` and `insertCodeAfterPattern` skip injection when the target content is already present. Mode 3's `registerProvider` line is also checked before insertion (unless `--force` is passed). All `InstallTransaction` file writes use `.tmp` + atomic rename: concurrent readers never observe partial state. A mid-commit `.tmp` write failure deletes all staged temps and returns `Error(rolledBack: true)` before any rename occurs. --- ## Reversibility `plugin:uninstall ` is the complement to `plugin:install`. A Mode 1 manifest commit writes `.artisan/installed/.json` listing every op and content hash. V1 reversibility is partial: `WriteFile` ops are fully reversed (tamper-check then delete); `InjectImport`, `InjectBeforePattern`, and `InjectAfterPattern` ops are logged as `[skipped]` (no anchor-bracketed markers in V1; operator must reverse by hand). Mode 2 and Mode 3 installs write no record, so `plugin:uninstall` cannot auto-reverse them. Full bidirectional reversibility is planned for V1.1. --- ## Related - [plugin:uninstall](./plugin-uninstall.md): removes a plugin registered via `plugin:install`, using the `.artisan/installed/.json` record. - [plugins:refresh](./plugins-refresh.md): regenerates `lib/app/_plugins.g.dart` from `.artisan/plugins.json` when the codegen barrel drifts out of sync. - [install.yaml schema](../plugins/install-yaml.md): full reference for the declarative manifest format consumed by Mode 1.