Repository navigation
0.0.16
Fixed
-
A pattern injection that matches nothing now fails the install instead of reporting Success over a file it never touched.
ConfigEditor.insertCodeAfterPatternandinsertCodeBeforePatternended in a bareif (match != null) { write }with no else, so a miss wrote nothing and returned normally.InstallTransactionhad no way to tell an applied injection from a skipped one, added the target to_helperWrittenTargetseither way, andplugin:installprinted Success. Both helpers returnboolnow and the transaction returnsErrornaming the target file.The distinction that had to survive: an idempotent skip, where the code is already present, still answers true. Collapsing the two would make a re-run of
plugin:installfail on work it had already done.Measured in a consumer app.
injectProvider's regex required the providers-list entry to read exactly(app) => XServiceProvider(app),, and that app'slib/config/app.dartwrites(MagicApp app) =>. The type annotation is optional in Dart and both spellings are in use across real apps, so the install matched nothing, registered no provider, booted no plugin, and reported Success.plugin_installer.dart's own docblock described this as "helper behaviour" and told plugin authors to document the requirement upstream; it is a defect rather than a contract. -
A pattern injection is checked BEFORE anything is staged, so a miss costs nothing. Returning the
Errorfrom inside the stage loop was not enough: helper-backed ops write throughdart:ioduring staging and sit outside the.tmprollback, and the install recordplugin:uninstallreads is only written in phase 6. So a miss found mid-loop stranded an orphan import, and possibly a pubspec entry, with nothing recorded to reverse them._assertPatternsResolvableruns over the same files first and names every offending op rather than the first. An idempotent re-run still commits, because the preflight treats "the code is already there" as resolvable. -
InjectBeforePatternandInjectAfterPatterntake an optionalfallbackPattern, tried only when the primary matches nothing. Without it, converting the silent no-op into a hard failure widened the blast radius rather than narrowing it: a host whose'providers': []orconfigFactories: []is empty matched nothing before and installed quietly, and would now fail outright. The fallback anchors on the opening bracket, which is the same two-stepmake:commandalready does by hand (make_command_command.dart:215-228). One regex with an alternation cannot express it, becausefirstMatchscans by position and the opening bracket always precedes the last entry, so every injection would land at the top of a populated list. -
injectConfigFactory's regex no longer requires the entry to end inConfig,. A host is free to name its own factory, and() => appSettings,is a legal entry the old pattern refused. WideninginjectProvideralone would have left this one silently broken and then loudly broken. -
Both composite injections now match from their list's own key forward, so the insertion point cannot land in a different list. Dropping the
Config,requirement removed the only thing tyinginjectConfigFactory's primary toconfigFactories:() => \w+,before a]describes any zero-argument closure that is last in any list, andfirstMatchscans by position, so an earlier list anywhere inlib/main.dartwon and the factory was appended to THAT list. An emptyconfigFactories: []was the same defect wearing a second face, and the worse one: the primary matched a later list's last entry, so the fallback that exists for the empty case never ran.'providers': [andconfigFactories: [now open both patterns and]is excluded from the gap, which keeps the scan inside the list the anchor opened and turns an empty list back into the non-match the fallback is waiting for.injectProvidertakes the same anchor, where\w+ServiceProvider(app),had been an accidental guard rather than a deliberate one. -
The append lookahead skips a trailing or own-line
//comment between the last entry and the closing bracket. A comment trailing the entry on the same line ((app) => AppServiceProvider(app), // core) defeated it, and so did a comment on its own line below, which is the shape a scaffold placeholder takes (// add plugin providers here). Either one sent the injection to the fallback, which prepends: correct Dart, wrong position. A/* block */comment in that position is still a miss and still prepends, unchanged from before. It cannot skip a real entry, because every line it consumes has to be whitespace or a//comment through to the newline, so the append still pins to the LAST entry. -
The providers key may be written with either quote. Anchoring the primary on
'providers'made its quoting load-bearing for the first time, so a host writing"providers": [had BOTH patterns miss and the preflight fail an install that worked before the anchor.prefer_single_quotesmakes that shape uncommon rather than impossible, and a character class costs one character in each pattern. -
injectProvidermatches both spellings of a providers-list entry. The parameter's type is optional in the pattern now, so(app) => XServiceProvider(app),and(MagicApp app) => XServiceProvider(app),both match. The lookahead that anchors the append to the LAST entry before]is asserted separately, because widening a pattern is the easy way to lose it. That assertion is now on where the match ENDS rather than on what it spans, since an anchored pattern necessarily carries every earlier entry in its match text whilematch.endstays the byte the code is written at. (lib/src/helpers/config_editor.dart,lib/src/installer/install_operation.dart,lib/src/installer/install_transaction.dart,lib/src/installer/plugin_installer.dart,test/helpers/config_editor_match_report_test.dart,test/installer/inject_provider_shapes_test.dart,doc/plugins/installer-dsl.md,skills/fluttersdk-artisan/references/cli-commands.md)
Added
server.json, the manifest that lists this package on the official MCP registry. The ecosystem was absent from every MCP directory, so an agent looking for a Dart CLI and MCP substrate had no way to find it. The entry carriesrepositoryandwebsiteUrland deliberately nopackagesblock:registryTypedocuments npm, pypi, oci, nuget and mcpb with no pub equivalent, and bothpackagesandremotesare optional onServerDetail, which requires only name, description and version. This is a sixth hand-maintained copy of the package version and the only one nothing guarded, sotest/server_json_version_test.dartasserts it againstpubspec.yamlin the same spirit astest/mcp/mcp_server_version_test.dart, and additionally pins the schema's 100 character description cap. Excluded from the pub archive for the same reasoncodecov.ymlis. (server.json,.pubignore,test/server_json_version_test.dart)