Skip to content
Back to archipelago.banuacoder.com

Scaffolding

feature_monorepo_skeleton Free ​

Tier
Freeversion 2.1.0
Command
archipelago generate feature_monorepo_skeleton
Generates
one empty feature package, or an _api and _impl pair, named after featureName

Scaffolds a new, empty feature module into an existing Archipelago monorepo — a single package, or a shared _api/_impl split — with optional business logic (DI, repositories, use cases), state management, per-feature localization, and native Android and iOS code (a Flutter plugin). This is what you run to start a brand-new feature from scratch; the finished feature SDK bricks (auth_sdk, rating_review_sdk, etc.) are hand-written, richer examples of the same shape this brick scaffolds bare.

Variables ​

VariableTypeDefaultDescription
featureNamestringFeatureNameThe name of the feature module
isSharedbooleanfalseWhether this is a shared feature (API/Impl split)
hasBusinessLogicbooleantrueWhether this feature has business logic (repos, usecases, DI)
hasLocalebooleanfalseWhether this feature manages its own localization
isPluginbooleanfalseWhether this feature carries native Android and iOS code (a Flutter plugin). Always both platforms
includeGeneratedbooleanfalseWhether to include generated files in version control
stateManagementenum (bloc, cubit, riverpod, provider, none)cubitState management solution for the feature
prefixstringAppUI kit class prefix; the shell page uses locale_core's <prefix>Locale. pre_gen reads it from archipelago.yaml (project.prefix), so you rarely set it

pre_gen.dart derives isBloc/isCubit/isRiverpod/isProvider/isNone/usesBloc/usesProvider/usesRiverpod/hasStateManagement from stateManagement, the plugin's pluginOrg (<domain>.<organization> from archipelago.yaml, com.example without one) and pluginOrgPath, plus a fixed slang config (l10nBaseLocale: en, l10nInputDirectory: l10n, l10nOutputDirectory: lib/src/l10n, ...) used to keep slang.yaml and build.yaml's slang_build_runner: options in sync when hasLocale is on.

Usage ​

Interactive ​

bash
archipelago generate feature_monorepo_skeleton

Non-interactive (CI) ​

bash
archipelago generate feature_monorepo_skeleton --config my_config.json

Generated Structure ​

Single package (isShared: false) ​

text
features/
└── {feature_name}/
    ├── lib/
    │   ├── {feature_name}.dart                         # Barrel export
    │   └── src/
    │       ├── {feature_name}_manifest.dart            # Routes, read at boot
    │       ├── {feature_name}_sdk.dart                 # Port + lazy implementation
    │       ├── data/{datasources,models,repositories}/  # if hasBusinessLogic
    │       ├── di/{feature_name}_global_module.dart     # Manifest + lazy SDK into the root
    │       ├── di/{feature_name}_container.dart, {feature_name}_imports.dart, injector.dart  # if hasBusinessLogic
    │       ├── domain/{entities,repositories,usecases}/  # if hasBusinessLogic
    │       ├── presentation/{feature_name}_shell_page.dart, ui/{pages,widgets}/
    │       └── router/{feature_name}_router.dart
    │       └── platform/{feature_name}_platform_channel.dart  # if isPlugin
    ├── test/di/{feature_name}_di_test.dart             # Lazy SDK, container isolation and teardown
    ├── test/platform/{feature_name}_platform_channel_test.dart  # if isPlugin
    ├── android/                                        # if isPlugin: build.gradle, kotlin/<org>/{feature_name}/{FeatureName}Plugin.kt
    ├── ios/                                            # if isPlugin: {feature_name}.podspec, Classes/{FeatureName}Plugin.swift
    ├── l10n/                                           # if hasLocale — en.i18n.json, id.i18n.json
    ├── slang.yaml                                      # if hasLocale
    ├── analysis_options.yaml
    ├── build.yaml
    └── pubspec.yaml

API/Impl split (isShared: true) ​

text
features/
├── {feature_name}_api/
│   └── lib/src/{feature_name}_sdk.dart    # port only, no FeatureSDK
└── {feature_name}_impl/
    ├── lib/src/{feature_name}_manifest.dart, {feature_name}_sdk_impl.dart, data/, di/, domain/, presentation/, router/
    ├── lib/src/platform/                  # if isPlugin
    ├── test/di/{feature_name}_di_test.dart
    ├── test/platform/                     # if isPlugin
    ├── android/, ios/                     # if isPlugin: native code lives only here, _api stays pure Dart
    ├── l10n/, slang.yaml                  # if hasLocale
    └── pubspec.yaml

Whichever variant is generated always lands flat at features/<name>/... (or features/<name>_api/ + features/<name>_impl/) — never double-nested under an extra <name>/<name>/ directory (fixed in 1.2.0).

What gets scaffolded ​

  • Every feature gets a const <Name>Manifest (its routes) and a lazy SDK. The global module registers the manifest in FeatureSDKRegistry and gh.lazySingleton<<Name>SDK>(<Name>SDKImpl.new), so the feature is not built at boot. The _api port is a plain interface with launch(); a port method that returns data uses Future<T?>.
  • hasBusinessLogic: true adds the feature's own container:
    • <name>_container.dart: <name>Container = FeatureContainer('<name>', root: GetIt.instance, build: build<Name>Container). The build runs the imports, then the generated init<Name>LocalDI(child).
    • <name>_imports.dart: import<Name>Globals(child, root), the only file that reads the app root. It starts empty; add one child.registerFactory<T>(() => root<T>()) line per root type the feature uses. The container never falls back to the root, so anything else throws.
    • The shell page takes a lease in initState, builds its content after lease.ready, and releases the lease in dispose (errors go to FlutterError.reportError). It creates the Cubit/Bloc/Notifier and passes it what it needs from _lease.get<T>(). No GetIt.instance appears outside lib/src/di/.
    • A port the feature owns and other features use is registered once, in the feature's global module, with a // di-global: <reason> comment; the feature reads it back through its imports file.
  • hasBusinessLogic: false produces a UI-only feature: no data/, domain/, container or imports — the global module (manifest + lazy SDK), presentation and router stay.
  • test/di/<name>_di_test.dart checks that the global module registers the manifest and that the SDK is not built until resolved, and (with business logic) that the container cannot resolve a root type it did not import, and that it builds from a fake root holding only the imported types, resolves every registration, and is empty with every closable singleton closed after the last release. Add one fake to that root per type you forward in <name>_imports.dart. See DI Guard Tests.
  • stateManagement cleans up every state-management variant's files except the chosen one via _cleanupStateManagementFiles in post_gen.dart; none removes state-management files entirely.
  • hasLocale: true scaffolds l10n/en.i18n.json + l10n/id.i18n.json, a slang.yaml, and a hasLocale-gated slang_build_runner:legacy stanza in build.yaml (the default slang_build_runner is turned off, see flutter_l10n) plus the slang/slang_flutter/slang_build_runner dependencies (both single_feature and shared_feature reached parity here as of 1.2.0/1.1.2) — run dart run slang (or melos run build) afterward to generate the translations class.
  • post_gen wires the feature into the app: it adds the package (<name>_impl for the shared variant) to the app's pubspec.yaml and registers <Name>GlobalModule/<Name>ImplGlobalModule in lib/di/injector.dart (fixed in 1.3.2).
  • The UI kit dependency uses the project's real UI kit name (<app>_ui_kit, from archipelago.yaml), not app_ui_kit. State classes use freezed ^4.0.1, which resolves with analyzer 13 (fixed in 1.3.2).
  • isPlugin: true makes the feature package a Flutter plugin for Android and iOS. With the API/Impl split, that is <name>_impl; <name>_api stays pure Dart.
    • pubspec.yaml declares flutter: plugin: platforms: with pluginClass: <Name>Plugin for both platforms and package: <org>.<package> for Android.
    • Android: android/build.gradle reads AGP, Kotlin and SDK versions from gradle/libs.versions.toml at the workspace root (post_gen writes it when a project has none), and <Name>Plugin.kt is a FlutterPlugin and MethodCallHandler.
    • iOS: ios/<package>.podspec and ios/Classes/<Name>Plugin.swift.
    • Both sides listen on one MethodChannel named <org>/<package>, for example com.banua_coder/cart, where <org> is the project's <domain>.<organization>. A sample getPlatformVersion method returns Android <release> or iOS <version>.
    • Dart: lib/src/platform/<name>_platform_channel.dart wraps the channel and returns null when the plugin is not registered. With hasBusinessLogic it is a @lazySingleton in the feature container, so a repository takes it by constructor like any datasource. test/platform/<name>_platform_channel_test.dart fakes the channel with TestDefaultBinaryMessenger.
    • Add a native call by adding a case in the Kotlin and Swift when/switch and a method on the Dart wrapper.
  • includeGenerated: false (default) keeps generated files out of version control (adds them to .gitignore); set true to commit them.

Dependencies ​

  • feature_sdk, router_registry, identifiers, dependencies — shared workspace packages (only referenced when hasBusinessLogic/routing code is generated).
  • No self-generation and no hard dependency on another brick — this is a leaf scaffolding brick.

Known Limitations ​

  • Native code (isPlugin) covers Android and iOS only, always both. The template targets mobile.
  • The plugin is a single package with a MethodChannel. Federated plugins (one package per platform) and FFI are not scaffolded; write them by hand if you need them.
  • The plugin's Android namespace is <org>.<package>, so a feature named like the app collides with the app's own namespace. Pick another feature name.
  • The plugin has no Swift Package Manager manifest (Package.swift), so flutter build ios lists it among plugins without Swift Package Manager support. CocoaPods builds it.
  • Renaming a feature after generation is a manual file/identifier rename; there's no archipelago rename command.
  • Only one state-management choice per feature; mixing (e.g. one screen on Riverpod, another on Bloc) isn't scaffolded.

Changelog ​

Run archipelago brick info feature_monorepo_skeleton to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.