From 7a7501063e68d28b8f99a2f89e8c00bab7921057 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 23 Oct 2024 13:19:50 +0530 Subject: [PATCH 01/10] feat: Add init command and options --- packages/melos/lib/src/command_runner.dart | 5 +- .../melos/lib/src/command_runner/init.dart | 71 +++++++++++++++++++ packages/melos/lib/src/commands/init.dart | 59 +++++++++++++++ packages/melos/lib/src/commands/runner.dart | 15 +++- 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 packages/melos/lib/src/command_runner/init.dart create mode 100644 packages/melos/lib/src/commands/init.dart diff --git a/packages/melos/lib/src/command_runner.dart b/packages/melos/lib/src/command_runner.dart index 8f4fe912..eddf39bd 100644 --- a/packages/melos/lib/src/command_runner.dart +++ b/packages/melos/lib/src/command_runner.dart @@ -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'; @@ -38,7 +39,8 @@ class MelosCommandRunner extends CommandRunner { : 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( @@ -55,6 +57,7 @@ class MelosCommandRunner extends CommandRunner { 'the special value "auto".', ); + addCommand(InitCommand(config)); addCommand(ExecCommand(config)); addCommand(BootstrapCommand(config)); addCommand(CleanCommand(config)); diff --git a/packages/melos/lib/src/command_runner/init.dart b/packages/melos/lib/src/command_runner/init.dart new file mode 100644 index 00000000..c7621036 --- /dev/null +++ b/packages/melos/lib/src/command_runner/init.dart @@ -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', + ); + + argParser.addMultiOption( + 'packages', + abbr: 'p', + help: 'Comma separated packages to add in top level `packages` array', + ); + + argParser.addOption( + 'project', + abbr: 'P', + help: + 'Project name to be used in pubspec.yaml. Defaults to workspace name', + ); + } + + @override + final String name = 'init'; + + @override + final String description = 'Initialize a new Melos workspace.'; + + @override + Future 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?; + final project = argResults!['project'] as String? ?? + promptInput( + 'Enter the project name', + defaultsTo: workspaceName, + ); + final useRecommendedDirectoryStructure = promptBool( + message: 'Use recommended directory structure?', + defaultsTo: true, + ); + + final melos = Melos(logger: logger, config: config); + + return melos.init( + workspaceName, + directory: directory, + packages: packages ?? const [], + project: project, + useRecommendedStructure: useRecommendedDirectoryStructure, + ); + } +} diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart new file mode 100644 index 00000000..6f5fdef4 --- /dev/null +++ b/packages/melos/lib/src/commands/init.dart @@ -0,0 +1,59 @@ +part of 'runner.dart'; + +mixin _InitMixin on _Melos { + Future init( + String workspaceName, { + required String directory, + required List packages, + required String project, + bool useRecommendedStructure = false, + }) 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 = { + 'name': qualifiedWorkspaceName, + if (useRecommendedStructure) 'packages': ['apps/**', 'packages/**'], + if (packages.isNotEmpty) 'packages': packages, + }; + final pubspecYaml = { + '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' + ' cd ${dir.path}\n' + ' melos bootstrap', + ); + } +} diff --git a/packages/melos/lib/src/commands/runner.dart b/packages/melos/lib/src/commands/runner.dart index 465233bf..9883cdd1 100644 --- a/packages/melos/lib/src/commands/runner.dart +++ b/packages/melos/lib/src/commands/runner.dart @@ -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'; import '../command_configs/command_configs.dart'; import '../command_runner/version.dart'; import '../common/aggregate_changelog.dart'; @@ -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'; + enum CommandWithLifecycle { bootstrap, clean, @@ -70,7 +81,8 @@ class Melos extends _Melos _VersionMixin, _PublishMixin, _AnalyzeMixin, - _FormatMixin { + _FormatMixin, + _InitMixin { Melos({ required this.config, Logger? logger, @@ -84,6 +96,7 @@ class Melos extends _Melos abstract class _Melos { MelosLogger get logger; + MelosWorkspaceConfig get config; Future createWorkspace({ From 9e673a4adc969d18cdb81a1195bcf33230986a1b Mon Sep 17 00:00:00 2001 From: exaby73 Date: Thu, 24 Oct 2024 15:32:52 +0530 Subject: [PATCH 02/10] feat: Prompt for apps dir --- packages/melos/lib/src/command_runner/init.dart | 6 +++--- packages/melos/lib/src/commands/init.dart | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/melos/lib/src/command_runner/init.dart b/packages/melos/lib/src/command_runner/init.dart index c7621036..9fb89119 100644 --- a/packages/melos/lib/src/command_runner/init.dart +++ b/packages/melos/lib/src/command_runner/init.dart @@ -53,8 +53,8 @@ class InitCommand extends MelosCommand { 'Enter the project name', defaultsTo: workspaceName, ); - final useRecommendedDirectoryStructure = promptBool( - message: 'Use recommended directory structure?', + final useAppsDir = promptBool( + message: 'Do you want to add the apps directory to the list of packages?', defaultsTo: true, ); @@ -65,7 +65,7 @@ class InitCommand extends MelosCommand { directory: directory, packages: packages ?? const [], project: project, - useRecommendedStructure: useRecommendedDirectoryStructure, + useAppDir: useAppsDir, ); } } diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart index 6f5fdef4..bd6a28f8 100644 --- a/packages/melos/lib/src/commands/init.dart +++ b/packages/melos/lib/src/commands/init.dart @@ -6,7 +6,7 @@ mixin _InitMixin on _Melos { required String directory, required List packages, required String project, - bool useRecommendedStructure = false, + bool useAppDir = false, }) async { late final String qualifiedWorkspaceName; if (workspaceName == '.') { @@ -21,12 +21,16 @@ mixin _InitMixin on _Melos { throw StateError('Directory $directory already exists'); } else if (!isCurrentDir) { 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 = { 'name': qualifiedWorkspaceName, - if (useRecommendedStructure) 'packages': ['apps/**', 'packages/**'], + 'packages': [if (useAppDir) 'apps/*', 'packages/*'], if (packages.isNotEmpty) 'packages': packages, }; final pubspecYaml = { From c4a20aee6215433fb5d727fe95edf7aeab17cd28 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Thu, 24 Oct 2024 16:53:52 +0530 Subject: [PATCH 03/10] feat: Disable requirement for melos workspace when running init --- packages/melos/lib/src/command_runner.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/melos/lib/src/command_runner.dart b/packages/melos/lib/src/command_runner.dart index eddf39bd..d06809b7 100644 --- a/packages/melos/lib/src/command_runner.dart +++ b/packages/melos/lib/src/command_runner.dart @@ -157,6 +157,7 @@ Future _resolveConfig( } bool _shouldUseEmptyConfig(List arguments) { + if (arguments.firstOrNull == 'init') return true; final willShowHelp = arguments.isEmpty || arguments.contains('--help') || arguments.contains('-h'); From 854a086d1c47c419631fe69bf9379f22a209d795 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 30 Oct 2024 14:40:29 +0530 Subject: [PATCH 04/10] fix: nits --- packages/melos/lib/src/command_runner/init.dart | 8 ++++---- packages/melos/lib/src/commands/init.dart | 4 ++-- packages/melos/lib/src/commands/runner.dart | 9 --------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/packages/melos/lib/src/command_runner/init.dart b/packages/melos/lib/src/command_runner/init.dart index 9fb89119..95719bdd 100644 --- a/packages/melos/lib/src/command_runner/init.dart +++ b/packages/melos/lib/src/command_runner/init.dart @@ -11,20 +11,20 @@ class InitCommand extends MelosCommand { argParser.addOption( 'directory', abbr: 'd', - help: 'Directory name to create project in. Defaults to workspace name', + help: 'Directory to create project in. Defaults to the workspace name.', ); argParser.addMultiOption( 'packages', abbr: 'p', - help: 'Comma separated packages to add in top level `packages` array', + help: 'Comma separated packages to add in top level `packages` array.', ); argParser.addOption( 'project', abbr: 'P', - help: - 'Project name to be used in pubspec.yaml. Defaults to workspace name', + help: 'Project name to be used in pubspec.yaml. ' + 'Defaults to the workspace name.', ); } diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart index bd6a28f8..ada1d3e9 100644 --- a/packages/melos/lib/src/commands/init.dart +++ b/packages/melos/lib/src/commands/init.dart @@ -28,7 +28,7 @@ mixin _InitMixin on _Melos { } final dartVersion = utils.currentDartVersion('dart'); - final melosYaml = { + final melosYaml = { 'name': qualifiedWorkspaceName, 'packages': [if (useAppDir) 'apps/*', 'packages/*'], if (packages.isNotEmpty) 'packages': packages, @@ -54,7 +54,7 @@ mixin _InitMixin on _Melos { ); logger.log( - 'Initialized Melos workspace in ${dir.path}\n' + 'Initialized Melos workspace in ${dir.path}.\n' 'Run the following commands to bootstrap the workspace:\n' ' cd ${dir.path}\n' ' melos bootstrap', diff --git a/packages/melos/lib/src/commands/runner.dart b/packages/melos/lib/src/commands/runner.dart index 9883cdd1..2c514cb4 100644 --- a/packages/melos/lib/src/commands/runner.dart +++ b/packages/melos/lib/src/commands/runner.dart @@ -45,23 +45,14 @@ 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'; enum CommandWithLifecycle { From 9a0e6f6bf043ff88fbfc7b6aac32cd3055be8ce1 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Wed, 30 Oct 2024 14:42:40 +0530 Subject: [PATCH 05/10] fix: Improve wording for packages option --- packages/melos/lib/src/command_runner/init.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/melos/lib/src/command_runner/init.dart b/packages/melos/lib/src/command_runner/init.dart index 95719bdd..2e76a118 100644 --- a/packages/melos/lib/src/command_runner/init.dart +++ b/packages/melos/lib/src/command_runner/init.dart @@ -17,7 +17,7 @@ class InitCommand extends MelosCommand { argParser.addMultiOption( 'packages', abbr: 'p', - help: 'Comma separated packages to add in top level `packages` array.', + help: 'Comma separated glob paths to add to the melos workspace.', ); argParser.addOption( From b9c069c97775de92a92b64f9da17cbba2cccec4b Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sun, 24 Nov 2024 17:41:45 +0530 Subject: [PATCH 06/10] docs: Add documentation for the `init` command in Melos --- docs/commands/init.mdx | 91 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 docs/commands/init.mdx diff --git a/docs/commands/init.mdx b/docs/commands/init.mdx new file mode 100644 index 00000000..47361382 --- /dev/null +++ b/docs/commands/init.mdx @@ -0,0 +1,91 @@ +--- +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, defaults to the workspace name. + +```bash +melos init my_workspace --directory custom_dir +``` + +### --packages (-p) +Defines additional glob patterns for package directories to include in the workspace. Can be specified multiple times. + +```bash +melos init --packages "modules/*" --packages "libs/*" +``` + +### --project (-P) +Sets the project name to be used in the pubspec.yaml file. If not provided, defaults to the workspace name. + +```bash +melos init --project my_project_name +``` + +## 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) +2. Ask for a directory location +3. Request a project name +4. Ask if you want to include an `apps` directory + +## Created Files + +The command creates the following structure: + +``` +/ +├── melos.yaml # Workspace configuration +├── pubspec.yaml # Root package configuration +└── packages/ # Packages directory +└── apps/ # Apps directory (optional) +``` + +### melos.yaml +Contains the workspace configuration with: +- Workspace name +- Package locations (glob patterns) +- Default package includes + +### pubspec.yaml +Contains the root package configuration with: +- Project name +- Dart SDK constraints +- 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/*" \ + --project custom_project_name +``` + +After initialization, you can bootstrap your workspace by running: +```bash +cd +melos bootstrap +``` From d4dece6c2ad60088e8ffef24a3e1939846f31ced Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sun, 24 Nov 2024 17:53:37 +0530 Subject: [PATCH 07/10] refactor(init): Improve directory and project options default logic --- docs/commands/init.mdx | 33 +++++++------------ .../melos/lib/src/command_runner/init.dart | 15 +-------- packages/melos/lib/src/commands/init.dart | 7 ++-- packages/melos/lib/src/commands/runner.dart | 1 - 4 files changed, 16 insertions(+), 40 deletions(-) diff --git a/docs/commands/init.mdx b/docs/commands/init.mdx index 47361382..3b6409f0 100644 --- a/docs/commands/init.mdx +++ b/docs/commands/init.mdx @@ -18,34 +18,26 @@ If no workspace name is provided, you'll be prompted to enter one. By default, i ## Options ### --directory (-d) -Specifies the directory where the workspace should be created. If not provided, defaults to the workspace name. +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. Can be specified multiple times. +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/*" ``` -### --project (-P) -Sets the project name to be used in the pubspec.yaml file. If not provided, defaults to the workspace name. - -```bash -melos init --project my_project_name -``` - ## 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) -2. Ask for a directory location -3. Request a project name -4. Ask if you want to include an `apps` directory +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 @@ -55,20 +47,20 @@ The command creates the following structure: / ├── melos.yaml # Workspace configuration ├── pubspec.yaml # Root package configuration -└── packages/ # Packages directory -└── apps/ # Apps directory (optional) +├── packages/ # Packages directory (always created) +└── apps/ # Apps directory (created if confirmed during setup) ``` ### melos.yaml Contains the workspace configuration with: - Workspace name -- Package locations (glob patterns) -- Default package includes +- 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 -- Dart SDK constraints +- Project name (same as workspace name) +- Dart SDK constraints (based on current Dart version) - Melos as a dev dependency ## Example @@ -80,8 +72,7 @@ melos init my_workspace # Custom initialization with options melos init my_workspace \ --directory custom_dir \ - --packages "modules/*" \ - --project custom_project_name + --packages "modules/*" ``` After initialization, you can bootstrap your workspace by running: diff --git a/packages/melos/lib/src/command_runner/init.dart b/packages/melos/lib/src/command_runner/init.dart index 2e76a118..3af82789 100644 --- a/packages/melos/lib/src/command_runner/init.dart +++ b/packages/melos/lib/src/command_runner/init.dart @@ -19,13 +19,6 @@ class InitCommand extends MelosCommand { abbr: 'p', help: '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 the workspace name.', - ); } @override @@ -48,13 +41,8 @@ class InitCommand extends MelosCommand { defaultsTo: workspaceDefault != workspaceName ? workspaceName : '.', ); final packages = argResults!['packages'] as List?; - final project = argResults!['project'] as String? ?? - promptInput( - 'Enter the project name', - defaultsTo: workspaceName, - ); final useAppsDir = promptBool( - message: 'Do you want to add the apps directory to the list of packages?', + message: 'Do you want to add the apps directory?', defaultsTo: true, ); @@ -64,7 +52,6 @@ class InitCommand extends MelosCommand { workspaceName, directory: directory, packages: packages ?? const [], - project: project, useAppDir: useAppsDir, ); } diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart index ada1d3e9..776ceb96 100644 --- a/packages/melos/lib/src/commands/init.dart +++ b/packages/melos/lib/src/commands/init.dart @@ -5,7 +5,6 @@ mixin _InitMixin on _Melos { String workspaceName, { required String directory, required List packages, - required String project, bool useAppDir = false, }) async { late final String qualifiedWorkspaceName; @@ -34,7 +33,7 @@ mixin _InitMixin on _Melos { if (packages.isNotEmpty) 'packages': packages, }; final pubspecYaml = { - 'name': project, + 'name': qualifiedWorkspaceName, 'environment': { 'sdk': '>=$dartVersion <${dartVersion.major + 1}.0.0', }, @@ -55,8 +54,8 @@ mixin _InitMixin on _Melos { logger.log( 'Initialized Melos workspace in ${dir.path}.\n' - 'Run the following commands to bootstrap the workspace:\n' - ' cd ${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', ); } diff --git a/packages/melos/lib/src/commands/runner.dart b/packages/melos/lib/src/commands/runner.dart index 2c514cb4..1dc0275f 100644 --- a/packages/melos/lib/src/commands/runner.dart +++ b/packages/melos/lib/src/commands/runner.dart @@ -87,7 +87,6 @@ class Melos extends _Melos abstract class _Melos { MelosLogger get logger; - MelosWorkspaceConfig get config; Future createWorkspace({ From 6d59ee4d1efd96183b5998b1b4ab1ab14ef2d74f Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sun, 24 Nov 2024 21:33:19 +0530 Subject: [PATCH 08/10] docs: Update lines as to not exceed 80 characters --- docs/commands/init.mdx | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/docs/commands/init.mdx b/docs/commands/init.mdx index 3b6409f0..4fb4340d 100644 --- a/docs/commands/init.mdx +++ b/docs/commands/init.mdx @@ -5,7 +5,9 @@ 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. +The `init` command initializes a new Melos workspace. +It creates the necessary configuration files and +directory structure for your monorepo. ## Basic Usage @@ -13,19 +15,25 @@ The `init` command initializes a new Melos workspace. It creates the necessary c 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. +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. +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. +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/*" @@ -33,10 +41,13 @@ melos init --packages "modules/*" --packages "libs/*" ## Interactive Setup -When running `melos init`, you'll be guided through an interactive setup process that will: +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) +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 @@ -48,7 +59,7 @@ The command creates the following structure: ├── melos.yaml # Workspace configuration ├── pubspec.yaml # Root package configuration ├── packages/ # Packages directory (always created) -└── apps/ # Apps directory (created if confirmed during setup) +└── apps/ # Apps directory (created if confirmed during setup) ``` ### melos.yaml From 81ca1dedba149f5878ab65cf93fa6ffc3f3afc14 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sun, 24 Nov 2024 22:32:23 +0530 Subject: [PATCH 09/10] fix: Create packages and apps dir in current dir --- packages/melos/lib/src/commands/init.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart index 776ceb96..9e9a9fbd 100644 --- a/packages/melos/lib/src/commands/init.dart +++ b/packages/melos/lib/src/commands/init.dart @@ -18,7 +18,7 @@ mixin _InitMixin on _Melos { final dir = Directory(directory); if (!isCurrentDir && dir.existsSync()) { throw StateError('Directory $directory already exists'); - } else if (!isCurrentDir) { + } else { dir.createSync(recursive: true); Directory(p.join(dir.absolute.path, 'packages')).createSync(); if (useAppDir) { From a6ebd40d9e44ec9c5ecf81471cb75890935771a2 Mon Sep 17 00:00:00 2001 From: exaby73 Date: Sun, 24 Nov 2024 22:42:15 +0530 Subject: [PATCH 10/10] test: Add tests for the init command --- packages/melos/lib/src/commands/init.dart | 2 +- packages/melos/lib/src/workspace_configs.dart | 11 ++ packages/melos/test/commands/init_test.dart | 184 ++++++++++++++++++ 3 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 packages/melos/test/commands/init_test.dart diff --git a/packages/melos/lib/src/commands/init.dart b/packages/melos/lib/src/commands/init.dart index 9e9a9fbd..690a6fb6 100644 --- a/packages/melos/lib/src/commands/init.dart +++ b/packages/melos/lib/src/commands/init.dart @@ -5,7 +5,7 @@ mixin _InitMixin on _Melos { String workspaceName, { required String directory, required List packages, - bool useAppDir = false, + required bool useAppDir, }) async { late final String qualifiedWorkspaceName; if (workspaceName == '.') { diff --git a/packages/melos/lib/src/workspace_configs.dart b/packages/melos/lib/src/workspace_configs.dart index 6e376ef0..18653acc 100644 --- a/packages/melos/lib/src/workspace_configs.dart +++ b/packages/melos/lib/src/workspace_configs.dart @@ -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 fromWorkspaceRoot( Directory workspaceRoot, diff --git a/packages/melos/test/commands/init_test.dart b/packages/melos/test/commands/init_test.dart new file mode 100644 index 00000000..35d81f41 --- /dev/null +++ b/packages/melos/test/commands/init_test.dart @@ -0,0 +1,184 @@ +import 'dart:io'; + +import 'package:melos/melos.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +import '../utils.dart'; + +void main() { + group('init', () { + late TestLogger logger; + late Directory tempDir; + + setUp(() async { + logger = TestLogger(); + tempDir = await Directory.systemTemp.createTemp('melos_init_test_'); + }); + + tearDown(() async { + await tempDir.delete(recursive: true); + }); + + test('creates a new workspace with default settings', () async { + final workspaceDir = Directory(p.join(tempDir.path, 'my_workspace')); + final config = MelosWorkspaceConfig.emptyWith( + name: 'my_workspace', + path: tempDir.path, + ); + final melos = Melos(logger: logger, config: config); + + await melos.init( + 'my_workspace', + directory: workspaceDir.path, + packages: [], + useAppDir: true, + ); + + // Verify directory structure + expect(workspaceDir.existsSync(), isTrue); + expect( + Directory(p.join(workspaceDir.path, 'packages')).existsSync(), + isTrue, + ); + expect(Directory(p.join(workspaceDir.path, 'apps')).existsSync(), isTrue); + + // Verify melos.yaml content + final melosYaml = loadYaml( + File(p.join(workspaceDir.path, 'melos.yaml')).readAsStringSync(), + ) as YamlMap; + expect(melosYaml['name'], equals('my_workspace')); + expect(melosYaml['packages'], equals(['apps/*', 'packages/*'])); + + // Verify pubspec.yaml content + final pubspecYaml = loadYaml( + File(p.join(workspaceDir.path, 'pubspec.yaml')).readAsStringSync(), + ) as YamlMap; + expect(pubspecYaml['name'], equals('my_workspace')); + expect( + (pubspecYaml['environment'] as YamlMap)['sdk'], + contains('>='), + ); + expect( + (pubspecYaml['dev_dependencies'] as YamlMap)['melos'], + contains('^'), + ); + + // Verify logger output + expect( + logger.output, + contains( + 'Initialized Melos workspace in ${workspaceDir.path}', + ), + ); + }); + + test('creates a workspace with custom packages', () async { + final workspaceDir = Directory(p.join(tempDir.path, 'custom_workspace')); + final config = MelosWorkspaceConfig.emptyWith( + name: 'custom_workspace', + path: tempDir.path, + ); + final melos = Melos(logger: logger, config: config); + + await melos.init( + 'custom_workspace', + directory: workspaceDir.path, + packages: ['custom/*', 'plugins/**'], + useAppDir: false, + ); + + final melosYaml = loadYaml( + File(p.join(workspaceDir.path, 'melos.yaml')).readAsStringSync(), + ) as YamlMap; + expect(melosYaml['packages'], equals(['custom/*', 'plugins/**'])); + }); + + test('creates workspace in current directory when directory is "."', + () async { + final config = MelosWorkspaceConfig.emptyWith( + name: 'melos_init_test_', + path: tempDir.path, + ); + final melos = Melos(logger: logger, config: config); + + final originalDir = Directory.current; + try { + Directory.current = tempDir; + + await melos.init( + '.', + directory: '.', + packages: [], + useAppDir: true, + ); + + // Verify files were created in current directory + expect(File('melos.yaml').existsSync(), isTrue); + expect(File('pubspec.yaml').existsSync(), isTrue); + expect(Directory('packages').existsSync(), isTrue); + expect(Directory('apps').existsSync(), isTrue); + + final melosYaml = + loadYaml(File('melos.yaml').readAsStringSync()) as YamlMap; + expect(melosYaml['name'], equals(p.basename(tempDir.path))); + } finally { + Directory.current = originalDir; + } + }); + + test('throws error if target directory already exists', () async { + final workspaceDir = Directory(p.join(tempDir.path, 'existing_workspace')) + ..createSync(); + final config = MelosWorkspaceConfig.emptyWith( + name: 'existing_workspace', + path: tempDir.path, + ); + final melos = Melos(logger: logger, config: config); + + expect( + () => melos.init( + 'existing_workspace', + directory: workspaceDir.path, + packages: [], + useAppDir: false, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Directory ${workspaceDir.path} already exists', + ), + ), + ); + }); + + test('creates workspace without apps directory when useAppDir is false', + () async { + final workspaceDir = Directory(p.join(tempDir.path, 'no_apps_workspace')); + final config = MelosWorkspaceConfig.emptyWith( + name: 'no_apps_workspace', + path: tempDir.path, + ); + final melos = Melos(logger: logger, config: config); + + await melos.init( + 'no_apps_workspace', + directory: workspaceDir.path, + packages: [], + useAppDir: false, + ); + + expect( + Directory(p.join(workspaceDir.path, 'apps')).existsSync(), + isFalse, + ); + + final melosYaml = loadYaml( + File(p.join(workspaceDir.path, 'melos.yaml')).readAsStringSync(), + ) as YamlMap; + expect(melosYaml['packages'], equals(['packages/*'])); + }); + }); +}