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

Merged
merged 11 commits into from
Nov 24, 2024
93 changes: 93 additions & 0 deletions docs/commands/init.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: Init Command
description: Learn more about the `init` command in Melos.
---

# Init Command

The `init` command initializes a new Melos workspace.
It creates the necessary configuration files and
directory structure for your monorepo.

## Basic Usage

```bash
melos init [workspace_name]
```

If no workspace name is provided, you'll be prompted to enter one.
By default, it uses the current directory name.

## Options

### --directory (-d)
Specifies the directory where the workspace should be created.
If not provided, you'll be prompted to enter one.
Defaults to the workspace name, or current directory ('.')
if the workspace name matches the current directory name.

```bash
melos init my_workspace --directory custom_dir
```

### --packages (-p)
Defines additional glob patterns for package directories
to include in the workspace. Accepts comma-separated values and can be
specified multiple times.

```bash
melos init --packages "modules/*" --packages "libs/*"
```

## Interactive Setup

When running `melos init`, you'll be guided through
an interactive setup process that will:

1. Prompt for a workspace name
(if not provided, defaults to current directory name)
2. Ask for a directory location
(defaults to workspace name, or '.' if matching current directory)
3. Ask if you want to include an `apps` directory (defaults to true)

## Created Files

The command creates the following structure:

```
<directory>/
├── melos.yaml # Workspace configuration
├── pubspec.yaml # Root package configuration
├── packages/ # Packages directory (always created)
└── apps/ # Apps directory (created if confirmed during setup)
```

### melos.yaml
Contains the workspace configuration with:
- Workspace name
- Package locations (defaults to ['packages/*'] and optionally 'apps/*')
- Additional package glob patterns (if specified via --packages)

### pubspec.yaml
Contains the root package configuration with:
- Project name (same as workspace name)
- Dart SDK constraints (based on current Dart version)
- Melos as a dev dependency

## Example

```bash
# Basic initialization
melos init my_workspace

# Custom initialization with options
melos init my_workspace \
--directory custom_dir \
--packages "modules/*"
```

After initialization, you can bootstrap your workspace by running:
```bash
cd <directory>
melos bootstrap
```
6 changes: 5 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 Expand Up @@ -154,6 +157,7 @@ Future<MelosWorkspaceConfig> _resolveConfig(
}

bool _shouldUseEmptyConfig(List<String> arguments) {
if (arguments.firstOrNull == 'init') return true;
final willShowHelp = arguments.isEmpty ||
arguments.contains('--help') ||
arguments.contains('-h');
Expand Down
58 changes: 58 additions & 0 deletions packages/melos/lib/src/command_runner/init.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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 to create project in. Defaults to the workspace name.',
);

argParser.addMultiOption(
'packages',
abbr: 'p',
help: 'Comma separated glob paths to add to the melos workspace.',
);
}

@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 useAppsDir = promptBool(
message: 'Do you want to add the apps directory?',
defaultsTo: true,
);

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

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

mixin _InitMixin on _Melos {
Future<void> init(
String workspaceName, {
required String directory,
required List<String> packages,
required bool useAppDir,
}) 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 {
dir.createSync(recursive: true);
Directory(p.join(dir.absolute.path, 'packages')).createSync();
if (useAppDir) {
Directory(p.join(dir.absolute.path, 'apps')).createSync();
}
}

final dartVersion = utils.currentDartVersion('dart');
final melosYaml = <String, Object?>{
'name': qualifiedWorkspaceName,
'packages': [if (useAppDir) 'apps/*', 'packages/*'],
if (packages.isNotEmpty) 'packages': packages,
};
final pubspecYaml = <String, dynamic>{
exaby73 marked this conversation as resolved.
Show resolved Hide resolved
'name': qualifiedWorkspaceName,
'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 when you have created some packages and/or apps:\n'
'${isCurrentDir ? '' : ' cd ${dir.path}\n'}'
' melos bootstrap',
);
}
}
5 changes: 4 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_parse/pubspec_parse.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 @@ -51,6 +52,7 @@ part 'bootstrap.dart';
part 'clean.dart';
part 'exec.dart';
part 'format.dart';
part 'init.dart';
part 'list.dart';
part 'publish.dart';
part 'run.dart';
Expand All @@ -73,7 +75,8 @@ class Melos extends _Melos
_VersionMixin,
_PublishMixin,
_AnalyzeMixin,
_FormatMixin {
_FormatMixin,
_InitMixin {
Melos({
required this.config,
Logger? logger,
Expand Down
11 changes: 11 additions & 0 deletions packages/melos/lib/src/workspace_configs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,17 @@ class MelosWorkspaceConfig {
commands: CommandConfigs.empty,
);

@visibleForTesting
MelosWorkspaceConfig.emptyWith({
String? name,
String? path,
}) : this(
name: name ?? 'Melos',
packages: [],
path: path ?? Directory.current.path,
commands: CommandConfigs.empty,
);

/// Loads the [MelosWorkspaceConfig] for the workspace at [workspaceRoot].
static Future<MelosWorkspaceConfig> fromWorkspaceRoot(
Directory workspaceRoot,
Expand Down
Loading
Loading