Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Init command #780

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/melos/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'command_runner/bootstrap.dart';
import 'command_runner/clean.dart';
import 'command_runner/exec.dart';
import 'command_runner/format.dart';
import 'command_runner/init.dart';
import 'command_runner/list.dart';
import 'command_runner/publish.dart';
import 'command_runner/run.dart';
Expand All @@ -38,7 +39,8 @@ class MelosCommandRunner extends CommandRunner<void> {
: super(
'melos',
'A CLI tool for managing Dart & Flutter projects with multiple '
'packages.',
'packages.\n\n'
'To get started with Melos, run "melos init".',
usageLineLength: terminalWidth,
) {
argParser.addFlag(
Expand All @@ -55,6 +57,7 @@ class MelosCommandRunner extends CommandRunner<void> {
'the special value "auto".',
);

addCommand(InitCommand(config));
addCommand(ExecCommand(config));
addCommand(BootstrapCommand(config));
addCommand(CleanCommand(config));
Expand Down
71 changes: 71 additions & 0 deletions packages/melos/lib/src/command_runner/init.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import 'dart:io';

import 'package:path/path.dart' as p;

import '../../melos.dart';
import '../common/utils.dart';
import 'base.dart';

class InitCommand extends MelosCommand {
InitCommand(super.config) {
argParser.addOption(
'directory',
abbr: 'd',
help: 'Directory name to create project in. Defaults to workspace name',
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
);

argParser.addMultiOption(
'packages',
abbr: 'p',
help: 'Comma separated packages to add in top level `packages` array',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
help: 'Comma separated packages to add in top level `packages` array',
help: 'Comma separated packages to add in top level `packages` array.',

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one should probably be a bit more descriptive, "add in top level packages array" doesn't really say much to the user.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a look at what I have now "Comma separated glob paths to add to the melos workspace."

);

argParser.addOption(
'project',
abbr: 'P',
help:
'Project name to be used in pubspec.yaml. Defaults to workspace name',
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
);
}

@override
final String name = 'init';

@override
final String description = 'Initialize a new Melos workspace.';

@override
Future<void> run() {
final workspaceDefault = p.basename(Directory.current.absolute.path);
final workspaceName = argResults!.rest.firstOrNull ??
promptInput(
'Enter your workspace name',
defaultsTo: workspaceDefault,
);
final directory = argResults!['directory'] as String? ??
promptInput(
'Enter the directory',
defaultsTo: workspaceDefault != workspaceName ? workspaceName : '.',
);
final packages = argResults!['packages'] as List<String>?;
final project = argResults!['project'] as String? ??
promptInput(
'Enter the project name',
defaultsTo: workspaceName,
);
Comment on lines +51 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we need to differentiate between workspace name and project name

final useRecommendedDirectoryStructure = promptBool(
message: 'Use recommended directory structure?',
defaultsTo: true,
);
exaby73 marked this conversation as resolved.
Show resolved Hide resolved

final melos = Melos(logger: logger, config: config);

return melos.init(
workspaceName,
directory: directory,
packages: packages ?? const [],
project: project,
useRecommendedStructure: useRecommendedDirectoryStructure,
);
}
}
59 changes: 59 additions & 0 deletions packages/melos/lib/src/commands/init.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
part of 'runner.dart';

mixin _InitMixin on _Melos {
Future<void> init(
String workspaceName, {
required String directory,
required List<String> packages,
required String project,
bool useRecommendedStructure = false,
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
}) async {
late final String qualifiedWorkspaceName;
if (workspaceName == '.') {
qualifiedWorkspaceName = p.basename(Directory.current.absolute.path);
} else {
qualifiedWorkspaceName = workspaceName;
}

final isCurrentDir = directory == '.';
final dir = Directory(directory);
if (!isCurrentDir && dir.existsSync()) {
throw StateError('Directory $directory already exists');
} else if (!isCurrentDir) {
dir.createSync(recursive: true);
}

final dartVersion = utils.currentDartVersion('dart');
final melosYaml = <String, dynamic>{
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
'name': qualifiedWorkspaceName,
if (useRecommendedStructure) 'packages': ['apps/**', 'packages/**'],
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
if (packages.isNotEmpty) 'packages': packages,
};
final pubspecYaml = <String, dynamic>{
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
'name': project,
'environment': {
'sdk': '>=$dartVersion <${dartVersion.major + 1}.0.0',
},
'dev_dependencies': {
'melos': '^$melosVersion',
},
};

final melosFile = File(p.join(dir.absolute.path, 'melos.yaml'));
final pubspecFile = File(p.join(dir.absolute.path, 'pubspec.yaml'));

melosFile.writeAsStringSync(
(YamlEditor('')..update([], melosYaml)).toString(),
);
pubspecFile.writeAsStringSync(
(YamlEditor('')..update([], pubspecYaml)).toString(),
);

logger.log(
'Initialized Melos workspace in ${dir.path}\n'
'Run the following commands to bootstrap the workspace:\n'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Run the following commands to bootstrap the workspace:\n'
'Run the following commands to bootstrap the workspace when you have created some packages and/or apps:\n'

Since this won't do anything when run directly after init has been run we should explain that they should run this once the apps and/or packages are created.

' cd ${dir.path}\n'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the path is . we should omit this

' melos bootstrap',
);
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
}
}
15 changes: 14 additions & 1 deletion packages/melos/lib/src/commands/runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:pubspec/pubspec.dart';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';

import '../../version.g.dart';
spydon marked this conversation as resolved.
Show resolved Hide resolved
import '../command_configs/command_configs.dart';
import '../command_runner/version.dart';
import '../common/aggregate_changelog.dart';
Expand Down Expand Up @@ -44,15 +45,25 @@ import '../workspace.dart';
import '../workspace_configs.dart';

part 'bootstrap.dart';

part 'clean.dart';

part 'exec.dart';

part 'list.dart';

part 'publish.dart';

part 'run.dart';

part 'version.dart';

part 'analyze.dart';

part 'format.dart';

part 'init.dart';

exaby73 marked this conversation as resolved.
Show resolved Hide resolved
enum CommandWithLifecycle {
bootstrap,
clean,
Expand All @@ -70,7 +81,8 @@ class Melos extends _Melos
_VersionMixin,
_PublishMixin,
_AnalyzeMixin,
_FormatMixin {
_FormatMixin,
_InitMixin {
Melos({
required this.config,
Logger? logger,
Expand All @@ -84,6 +96,7 @@ class Melos extends _Melos

abstract class _Melos {
MelosLogger get logger;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

MelosWorkspaceConfig get config;

Future<MelosWorkspace> createWorkspace({
Expand Down
Loading