Remove --template=skeleton and add a placeholder error message instead. (#160695)

Closes https://github.com/flutter/flutter/issues/160673.

Does the following:

- Renames `FlutterProjectType` to `FlutterTemplateType`; did some
enhanced enum cleanups while at it
- Creates a hierarchy of `RemovedFlutterTemplateType` from
`ParsedFlutterTemplateType`
- Removes the `skeleton` directory
- Merges `app_shared` back into `app` (no longer required now that
`skeleton` is removed)

Final cleanups are tracked in
https://github.com/flutter/flutter/issues/160692.

(Added @zanderso just to spot check this is what he meant by
https://github.com/flutter/flutter/issues/160673#issuecomment-2557742347)
This commit is contained in:
Matan Lurey 2024-12-23 16:02:29 -08:00 committed by GitHub
parent 62c6859e59
commit 0ffc4ce00e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
169 changed files with 665 additions and 1179 deletions

View File

@ -2443,7 +2443,7 @@ final String _kTemplateRelativePath = path.join(
'packages',
'flutter_tools',
'templates',
'app_shared',
'app',
'windows.tmpl',
'runner',
);

View File

@ -19,7 +19,7 @@ See also:
1. [Flutter tool's Windows logic](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/lib/src/windows) - Builds and runs Flutter Windows apps on
the command line.
1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app_shared/windows.tmpl) - The entrypoint for Flutter Windows app. This
1. [Windows app template](https://github.com/flutter/flutter/tree/master/packages/flutter_tools/templates/app/windows.tmpl) - The entrypoint for Flutter Windows app. This
launches the Windows embedder.
1. [`platform-windows` GitHub issues label](https://github.com/flutter/flutter/issues?q=is%3Aopen+label%3Aplatform-windows+sort%3Aupdated-desc)
1. [`#hackers-desktop` Discord channel](https://discord.com/channels/608014603317936148/608020180177780791)

6
packages/flutter_tools/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Normally (from the root) we ignore .idea folders, but the ones present
# in ide_templates/ and templates/ are real folders we intend to copy as part of
# "flutter create".
!ide_templates/intellij/.idea
!templates/**/.idea

View File

@ -104,13 +104,16 @@ class CreateCommand extends FlutterCommand with CreateBase {
hide: !verboseHelp,
);
addPlatformsOptions(customHelp: kPlatformHelp);
final List<ParsedFlutterTemplateType> enabledTemplates =
ParsedFlutterTemplateType.enabledValues(featureFlags);
argParser.addOption(
'template',
abbr: 't',
allowed: FlutterProjectType.enabledValues.map<String>((FlutterProjectType e) => e.cliName),
allowed: enabledTemplates.map((ParsedFlutterTemplateType t) => t.cliName),
help: 'Specify the type of project to create.',
valueHelp: 'type',
allowedHelp: CliEnum.allowedHelp(FlutterProjectType.enabledValues),
allowedHelp: CliEnum.allowedHelp(enabledTemplates),
);
argParser.addOption(
'sample',
@ -228,13 +231,28 @@ class CreateCommand extends FlutterCommand with CreateBase {
}
}
FlutterProjectType _getProjectType(Directory projectDir) {
FlutterProjectType? template;
FlutterProjectType? detectedProjectType;
FlutterTemplateType _getProjectType(Directory projectDir) {
FlutterTemplateType? template;
FlutterTemplateType? detectedProjectType;
final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync();
final String? templateArgument = stringArg('template');
if (templateArgument != null) {
template = FlutterProjectType.fromCliName(templateArgument);
final ParsedFlutterTemplateType? parsedTemplate = ParsedFlutterTemplateType.fromCliName(
templateArgument,
);
switch (parsedTemplate) {
case RemovedFlutterTemplateType():
throwToolExit(
'The template ${parsedTemplate.cliName} is no longer available. For '
'your convenience the former help text is repeated below with context '
'about the removal and other possible resources:\n\n'
'${parsedTemplate.helpText}',
);
case FlutterTemplateType():
template = parsedTemplate;
case null:
break;
}
}
// If the project directory exists and isn't empty, then try to determine the template
// type from the project directory.
@ -250,7 +268,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
);
}
}
template ??= detectedProjectType ?? FlutterProjectType.app;
template ??= detectedProjectType ?? FlutterTemplateType.app;
if (detectedProjectType != null && template != detectedProjectType && metadataExists) {
// We can only be definitive that this is the wrong type if the .metadata file
// exists and contains a type that doesn't match.
@ -279,27 +297,27 @@ class CreateCommand extends FlutterCommand with CreateBase {
String? sampleCode;
final String? sampleArgument = stringArg('sample');
final bool emptyArgument = boolArg('empty');
final FlutterProjectType template = _getProjectType(projectDir);
final FlutterTemplateType template = _getProjectType(projectDir);
if (sampleArgument != null) {
if (template != FlutterProjectType.app) {
if (template != FlutterTemplateType.app) {
throwToolExit(
'Cannot specify --sample with a project type other than '
'"${FlutterProjectType.app.cliName}"',
'"${FlutterTemplateType.app.cliName}"',
);
}
// Fetch the sample from the server.
sampleCode = await _fetchSampleFromServer(sampleArgument);
}
if (emptyArgument && template != FlutterProjectType.app) {
if (emptyArgument && template != FlutterTemplateType.app) {
throwToolExit('The --empty flag is only supported for the app template.');
}
final bool generateModule = template == FlutterProjectType.module;
final bool generateMethodChannelsPlugin = template == FlutterProjectType.plugin;
final bool generateFfiPackage = template == FlutterProjectType.packageFfi;
final bool generateFfiPlugin = template == FlutterProjectType.pluginFfi;
final bool generateModule = template == FlutterTemplateType.module;
final bool generateMethodChannelsPlugin = template == FlutterTemplateType.plugin;
final bool generateFfiPackage = template == FlutterTemplateType.packageFfi;
final bool generateFfiPlugin = template == FlutterTemplateType.pluginFfi;
final bool generateFfi = generateFfiPlugin || generateFfiPackage;
final bool generatePackage = template == FlutterProjectType.package;
final bool generatePackage = template == FlutterTemplateType.package;
final List<String> platforms = stringsArg('platforms');
// `--platforms` does not support module or package.
@ -359,7 +377,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
final bool includeLinux;
final bool includeMacos;
final bool includeWindows;
if (template == FlutterProjectType.module) {
if (template == FlutterTemplateType.module) {
// The module template only supports iOS and Android.
includeIos = true;
includeAndroid = true;
@ -367,7 +385,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
includeLinux = false;
includeMacos = false;
includeWindows = false;
} else if (template == FlutterProjectType.package) {
} else if (template == FlutterTemplateType.package) {
// The package template does not supports any platform.
includeIos = false;
includeAndroid = false;
@ -443,7 +461,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
int generatedFileCount = 0;
final PubContext pubContext;
switch (template) {
case FlutterProjectType.app:
case FlutterTemplateType.app:
final bool skipWidgetTestsGeneration = sampleCode != null || emptyArgument;
generatedFileCount += await generateApp(
@ -455,17 +473,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template,
);
pubContext = PubContext.create;
case FlutterProjectType.skeleton:
generatedFileCount += await generateApp(
<String>['skeleton'],
relativeDir,
templateContext,
overwrite: overwrite,
printStatusWhenWriting: !creatingNewProject,
generateMetadata: false,
);
pubContext = PubContext.create;
case FlutterProjectType.module:
case FlutterTemplateType.module:
generatedFileCount += await _generateModule(
relativeDir,
templateContext,
@ -473,7 +481,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
printStatusWhenWriting: !creatingNewProject,
);
pubContext = PubContext.create;
case FlutterProjectType.package:
case FlutterTemplateType.package:
generatedFileCount += await _generatePackage(
relativeDir,
templateContext,
@ -481,7 +489,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
printStatusWhenWriting: !creatingNewProject,
);
pubContext = PubContext.createPackage;
case FlutterProjectType.plugin:
case FlutterTemplateType.plugin:
generatedFileCount += await _generateMethodChannelPlugin(
relativeDir,
templateContext,
@ -490,7 +498,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template,
);
pubContext = PubContext.createPlugin;
case FlutterProjectType.pluginFfi:
case FlutterTemplateType.pluginFfi:
generatedFileCount += await _generateFfiPlugin(
relativeDir,
templateContext,
@ -499,7 +507,7 @@ class CreateCommand extends FlutterCommand with CreateBase {
projectType: template,
);
pubContext = PubContext.createPlugin;
case FlutterProjectType.packageFfi:
case FlutterTemplateType.packageFfi:
generatedFileCount += await _generateFfiPackage(
relativeDir,
templateContext,
@ -667,7 +675,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
required FlutterProjectType projectType,
required FlutterTemplateType projectType,
}) async {
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults!.wasParsed('platforms')) {
@ -763,7 +771,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
required FlutterProjectType projectType,
required FlutterTemplateType projectType,
}) async {
// Plugins only add a platform if it was requested explicitly by the user.
if (!argResults!.wasParsed('platforms')) {
@ -844,7 +852,7 @@ Your $application code is in $relativeAppMain.
Map<String, Object?> templateContext, {
bool overwrite = false,
bool printStatusWhenWriting = true,
required FlutterProjectType projectType,
required FlutterTemplateType projectType,
}) async {
int generatedCount = 0;
final String? description =
@ -1025,7 +1033,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
required String templateGradleVersion,
required String templateAgpVersion,
required String templateAgpVersionForModule,
required FlutterProjectType projectType,
required FlutterTemplateType projectType,
required String projectDirPath,
}) {
// Determine if the Java version specified conflicts with the template Gradle or AGP version.
@ -1041,7 +1049,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
);
String relevantTemplateAgpVersion = templateAgpVersion;
if (projectType == FlutterProjectType.module &&
if (projectType == FlutterTemplateType.module &&
Version.parse(templateAgpVersion)! < Version.parse(templateAgpVersionForModule)!) {
// If a module is being created, make sure to check for Java/AGP compatibility between the highest used version of AGP in the module template.
javaAgpVersionsCompatible = gradle.validateJavaAndAgp(
@ -1066,7 +1074,7 @@ void _printIncompatibleJavaAgpGradleVersionsWarning({
);
if (!javaGradleVersionsCompatible) {
if (projectType == FlutterProjectType.plugin || projectType == FlutterProjectType.pluginFfi) {
if (projectType == FlutterTemplateType.plugin || projectType == FlutterTemplateType.pluginFfi) {
// Only impacted files could be in sample code.
return;
}
@ -1157,24 +1165,26 @@ globally for Flutter by running:
// Returns path of the gradle-wrapper.properties file for the specified
// generated project type.
String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, String projectDirPath) {
String? _getGradleWrapperPropertiesFilePath(
FlutterTemplateType projectType,
String projectDirPath,
) {
String gradleWrapperPropertiesFilePath = '';
switch (projectType) {
case FlutterProjectType.app:
case FlutterProjectType.skeleton:
case FlutterTemplateType.app:
gradleWrapperPropertiesFilePath = globals.fs.path.join(
projectDirPath,
'android/gradle/wrapper/gradle-wrapper.properties',
);
case FlutterProjectType.module:
case FlutterTemplateType.module:
gradleWrapperPropertiesFilePath = globals.fs.path.join(
projectDirPath,
'.android/gradle/wrapper/gradle-wrapper.properties',
);
case FlutterProjectType.plugin:
case FlutterProjectType.pluginFfi:
case FlutterProjectType.package:
case FlutterProjectType.packageFfi:
case FlutterTemplateType.plugin:
case FlutterTemplateType.pluginFfi:
case FlutterTemplateType.package:
case FlutterTemplateType.packageFfi:
// TODO(camsim99): Add relevant file path for packageFfi when Android is supported.
// No gradle-wrapper.properties files not part of sample code that
// can be determined.
@ -1186,18 +1196,17 @@ String? _getGradleWrapperPropertiesFilePath(FlutterProjectType projectType, Stri
// Returns the path(s) of the build.gradle file(s) for the specified generated
// project type.
List<String>? _getBuildGradleConfigurationFilePaths(
FlutterProjectType projectType,
FlutterTemplateType projectType,
String projectDirPath,
) {
final List<String> buildGradleConfigurationFilePaths = <String>[];
switch (projectType) {
case FlutterProjectType.app:
case FlutterProjectType.skeleton:
case FlutterProjectType.pluginFfi:
case FlutterTemplateType.app:
case FlutterTemplateType.pluginFfi:
buildGradleConfigurationFilePaths.add(
globals.fs.path.join(projectDirPath, 'android/build.gradle'),
);
case FlutterProjectType.module:
case FlutterTemplateType.module:
const String moduleBuildGradleFilePath = '.android/build.gradle';
const String moduleAppBuildGradleFlePath = '.android/app/build.gradle';
const String moduleFlutterBuildGradleFilePath = '.android/Flutter/build.gradle';
@ -1206,12 +1215,12 @@ List<String>? _getBuildGradleConfigurationFilePaths(
globals.fs.path.join(projectDirPath, moduleAppBuildGradleFlePath),
globals.fs.path.join(projectDirPath, moduleFlutterBuildGradleFilePath),
]);
case FlutterProjectType.plugin:
case FlutterTemplateType.plugin:
buildGradleConfigurationFilePaths.add(
globals.fs.path.join(projectDirPath, 'android/app/build.gradle'),
);
case FlutterProjectType.package:
case FlutterProjectType.packageFfi:
case FlutterTemplateType.package:
case FlutterTemplateType.packageFfi:
// TODO(camsim99): Add any relevant file paths for packageFfi when Android is supported.
// No build.gradle file because there is no platform-specific implementation.
return null;

View File

@ -158,7 +158,7 @@ mixin CreateBase on FlutterCommand {
/// Throws assertion if [projectDir] does not exist or empty.
/// Returns null if no project type can be determined.
@protected
FlutterProjectType? determineTemplateType() {
FlutterTemplateType? determineTemplateType() {
assert(projectDir.existsSync() && projectDir.listSync().isNotEmpty);
final File metadataFile = globals.fs.file(
globals.fs.path.join(projectDir.absolute.path, '.metadata'),
@ -167,7 +167,7 @@ mixin CreateBase on FlutterCommand {
metadataFile,
globals.logger,
);
final FlutterProjectType? projectType = projectMetadata.projectType;
final FlutterTemplateType? projectType = projectMetadata.projectType;
if (projectType != null) {
return projectType;
}
@ -184,7 +184,7 @@ mixin CreateBase on FlutterCommand {
if (exists(<String>['android', 'app']) ||
exists(<String>['ios', 'Runner']) ||
exists(<String>['ios', 'Flutter'])) {
return FlutterProjectType.app;
return FlutterTemplateType.app;
}
// Since we can't really be definitive on nearly-empty directories, err on
// the side of prudence and just say we don't know.
@ -472,11 +472,11 @@ mixin CreateBase on FlutterCommand {
bool pluginExampleApp = false,
bool printStatusWhenWriting = true,
bool generateMetadata = true,
FlutterProjectType? projectType,
FlutterTemplateType? projectType,
}) async {
int generatedCount = 0;
generatedCount += await renderMerged(
<String>[...templateNames, 'app_shared'],
<String>[...templateNames],
directory,
templateContext,
overwrite: overwrite,

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
import 'base/file_system.dart';
@ -12,73 +13,122 @@ import 'project.dart';
import 'template.dart';
import 'version.dart';
enum FlutterProjectType implements CliEnum {
/// This is the default project with the user-managed host code.
/// It is different than the "module" template in that it exposes and doesn't
/// manage the platform code.
app,
/// The result of parsing `--template=` for `flutter create` and related commands.
@immutable
sealed class ParsedFlutterTemplateType implements CliEnum {
static const List<ParsedFlutterTemplateType> _values = <ParsedFlutterTemplateType>[
...FlutterTemplateType.values,
...RemovedFlutterTemplateType.values,
];
/// A List/Detail app template that follows community best practices.
skeleton,
/// The is a project that has managed platform host code. It is an application with
/// ephemeral .ios and .android directories that can be updated automatically.
module,
/// This is a Flutter Dart package project. It doesn't have any native
/// components, only Dart.
package,
/// This is a Dart package project with external builds for native components.
packageFfi,
/// This is a native plugin project.
plugin,
/// This is an FFI native plugin project.
pluginFfi;
@override
String get cliName => snakeCase(name);
@override
String get helpText => switch (this) {
FlutterProjectType.app => '(default) Generate a Flutter application.',
FlutterProjectType.skeleton =>
'Generate a List View / Detail View Flutter application that follows community best practices.',
FlutterProjectType.package =>
'Generate a shareable Flutter project containing modular Dart code.',
FlutterProjectType.plugin =>
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
'Linux, macOS, Windows, web, or any combination of these.',
FlutterProjectType.pluginFfi =>
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, Windows, or any combination of these.',
FlutterProjectType.packageFfi =>
'Generate a shareable Dart/Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, and Windows.',
FlutterProjectType.module =>
'Generate a project to add a Flutter module to an existing Android or iOS application.',
};
static FlutterProjectType? fromCliName(String value) {
for (final FlutterProjectType type in FlutterProjectType.values) {
if (value == type.cliName) {
/// Parses and returns a [ParsedFlutterTemplateType], if any, for [cliName].
///
/// If no match was found `null` is returned.
static ParsedFlutterTemplateType? fromCliName(String cliName) {
for (final ParsedFlutterTemplateType type in _values) {
if (cliName == type.cliName) {
return type;
}
}
return null;
}
static List<FlutterProjectType> get enabledValues {
return <FlutterProjectType>[
for (final FlutterProjectType value in values)
if (value != FlutterProjectType.packageFfi || featureFlags.isNativeAssetsEnabled) value,
];
/// Returns template types that are enabled based on the current [featureFlags].
static List<ParsedFlutterTemplateType> enabledValues(FeatureFlags featureFlags) {
return _values.toList()..retainWhere((ParsedFlutterTemplateType templateType) {
return templateType.isEnabled(featureFlags);
});
}
/// Whether the flag is enabled based on a flag being set.
bool isEnabled(FeatureFlags featureFlags) => true;
}
/// A [ParsedFlutterTemplateType] that is no longer operable.
///
/// The CLI can give a hint to a developer that the [cliName] _did_ use to exist,
/// but does no longer, and provides [helpText] for other resources to use instead.
enum RemovedFlutterTemplateType implements ParsedFlutterTemplateType {
skeleton(
helpText:
'Formerly generated a list view / detail view Flutter application that '
'followed some community best practices. For up to date resources, see '
'https://flutter.github.io/samples, https://docs.flutter.dev/codelabs, '
'and external resources such as https://flutter-builder.app/.',
);
const RemovedFlutterTemplateType({required this.helpText});
@override
bool isEnabled(FeatureFlags featureFlags) => true;
@override
final String helpText;
@override
String get cliName => snakeCase(name);
}
/// The result of parsing a recognized `--template` for `flutter create` and related commands.
enum FlutterTemplateType implements ParsedFlutterTemplateType {
/// The default project with the user-managed host code.
///
/// It is different than the "module" template in that it exposes and doesn't
/// manage the platform code.
app(helpText: '(default) Generate a Flutter application.'),
/// A project that has managed platform host code.
///
/// It is an application with ephemeral .ios and .android directories that can be updated automatically.
module(
helpText:
'Generate a project to add a Flutter module to an existing Android or iOS application.',
),
/// A Flutter Dart package project.
///
/// It doesn't have any native components, only Dart.
package(helpText: 'Generate a shareable Flutter project containing modular Dart code.'),
/// A Dart package project with external builds for native components.
packageFfi(
helpText:
'Generate a shareable Dart/Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, and Windows.',
),
/// A native plugin project.
plugin(
helpText:
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through method channels for Android, iOS, '
'Linux, macOS, Windows, web, or any combination of these.',
),
/// This is an FFI native plugin project.
pluginFfi(
helpText:
'Generate a shareable Flutter project containing an API '
'in Dart code with a platform-specific implementation through dart:ffi for Android, iOS, '
'Linux, macOS, Windows, or any combination of these.',
);
const FlutterTemplateType({required this.helpText});
@override
bool isEnabled(FeatureFlags featureFlags) {
return switch (this) {
FlutterTemplateType.packageFfi => featureFlags.isNativeAssetsEnabled,
_ => true,
};
}
@override
final String helpText;
@override
String get cliName => snakeCase(name);
}
/// Verifies the expected yaml keys are present in the file.
@ -135,7 +185,13 @@ class FlutterProjectMetadata {
}
}
if (_validateMetadataMap(yamlRoot, <String, Type>{'project_type': String}, _logger)) {
_projectType = FlutterProjectType.fromCliName(yamlRoot['project_type'] as String);
final ParsedFlutterTemplateType? templateType = ParsedFlutterTemplateType.fromCliName(
yamlRoot['project_type'] as String,
);
_projectType = switch (templateType) {
RemovedFlutterTemplateType() || null => null,
FlutterTemplateType() => templateType,
};
}
final Object? migrationYaml = yamlRoot['migration'];
if (migrationYaml is YamlMap) {
@ -148,7 +204,7 @@ class FlutterProjectMetadata {
required this.file,
required String? versionRevision,
required String? versionChannel,
required FlutterProjectType? projectType,
required FlutterTemplateType? projectType,
required this.migrateConfig,
required Logger logger,
}) : _logger = logger,
@ -165,8 +221,8 @@ class FlutterProjectMetadata {
String? _versionChannel;
String? get versionChannel => _versionChannel;
FlutterProjectType? _projectType;
FlutterProjectType? get projectType => _projectType;
FlutterTemplateType? _projectType;
FlutterTemplateType? get projectType => _projectType;
/// Metadata and configuration for the migrate command.
MigrateConfig migrateConfig;

View File

@ -201,7 +201,7 @@ class BuildableIOSApp extends IOSApp {
}
String _templateImageAssetDirNameSuffix(String asset) =>
globals.fs.path.join('app_shared', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset);
globals.fs.path.join('app', 'ios.tmpl', 'Runner', 'Assets.xcassets', asset);
String get _appIconAsset => 'AppIcon.appiconset';
String get _launchImageAsset => 'LaunchImage.imageset';

View File

@ -129,7 +129,7 @@ ${_projectMetadataInformation()}
return 'No pubspec in working directory.';
}
final FlutterProjectMetadata metadata = FlutterProjectMetadata(project.metadataFile, _logger);
final FlutterProjectType? projectType = metadata.projectType;
final FlutterTemplateType? projectType = metadata.projectType;
final StringBuffer description =
StringBuffer()
..writeln('**Type**: ${projectType == null ? 'malformed' : projectType.cliName}')

View File

@ -173,13 +173,22 @@ class Template {
required Logger logger,
required TemplateRenderer templateRenderer,
}) async {
// TODO(matanl): Remove this once https://github.com/flutter/packages/pull/8336 is merged and published.
// See https://github.com/flutter/flutter/issues/160692.
final List<String> imageNames;
if (names.contains('app')) {
// Emulates what used to happen when app_shared existed. It still exist in package:flutter_template_images.
imageNames = <String>[...names, 'app_shared'];
} else {
imageNames = names;
}
// All named templates are placed in the 'templates' directory
return Template._(
<Directory>[
for (final String name in names) templatePathProvider.directoryInPackage(name, fileSystem),
],
<Directory>[
for (final String name in names)
for (final String name in imageNames)
if ((await templatePathProvider.imageDirectory(name, fileSystem, logger)).existsSync())
await templatePathProvider.imageDirectory(name, fileSystem, logger),
],

View File

@ -1,25 +0,0 @@
This directory contains templates for `flutter create`.
The `*_shared` subdirectories provide files for multiple templates.
* `app_shared` for `app` and `skeleton`.
* `plugin_shared` for (method channel) `plugin` and `plugin_ffi`.
For example, there are two app templates: `app` (the counter app)
and `skeleton` (the more advanced list view/detail view app).
```plain
┌────────────┐
│ app_shared │
└──┬──────┬──┘
│ │
│ │
▼ ▼
┌─────┐ ┌──────────┐
│ app │ │ skeleton │
└─────┘ └──────────┘
```
Thanks to `app_shared`, the templates for `app` and `skeleton` can contain
only the files that are specific to them alone, and the rest is automatically
kept in sync.

Some files were not shown because too many files have changed in this diff Show More