From b9c6d0ccd55698d244dd856c26767e5e3a9852ac Mon Sep 17 00:00:00 2001 From: "Ayman E. Barghout" Date: Fri, 22 Dec 2023 10:50:10 +0100 Subject: [PATCH] feat: Add enforce lockfile bootstrap command config (#600) * Enforce lockfile for CI environments * Add enforce lockfile to bootstrap command config * Enforce lockfile based on the bootstrap command workspace config * Update overview.mdx with enforce lockfile option * Update the `docstring` for `enforceLockfile` to include it is default for CI * Update workspace config and bootstrap tests to include the new enforceLockfile property * Stop enforcing lock file on CI by default * Remove unnecessary parenthesis as linter is complaining * Update enforcing lock files to be inclusive of Dart as well as Flutter Co-authored-by: Lukas Klingsbo * Add `--enforce-lockfile` as a bootstrap flag * Fix duplicate calling of enforce lockfile flag * Consolidate bootstrap flags documentation * Update docs/commands/bootstrap.mdx for clarity Co-authored-by: Lukas Klingsbo * Update docs/commands/bootstrap.mdx for clarity Co-authored-by: Lukas Klingsbo * Update docs/commands/bootstrap.mdx for clarity Co-authored-by: Lukas Klingsbo * Update docs/commands/bootstrap.mdx for clarity Co-authored-by: Lukas Klingsbo * Update docs/commands/bootstrap.mdx formatting Co-authored-by: Lukas Klingsbo * Update docs/configuration/overview.mdx for better clarity Co-authored-by: Lukas Klingsbo * Update docs/configuration/overview.mdx for better clarity Co-authored-by: Lukas Klingsbo --------- Co-authored-by: Lukas Klingsbo --- docs/commands/bootstrap.mdx | 17 +++---- docs/configuration/overview.mdx | 8 +++ .../lib/src/command_runner/bootstrap.dart | 7 +++ .../melos/lib/src/commands/bootstrap.dart | 13 ++++- packages/melos/lib/src/workspace_configs.dart | 20 ++++++++ .../melos/test/commands/bootstrap_test.dart | 50 +++++++++++++++++++ .../melos/test/workspace_config_test.dart | 19 +++++++ 7 files changed, 124 insertions(+), 10 deletions(-) diff --git a/docs/commands/bootstrap.mdx b/docs/commands/bootstrap.mdx index c1a9e742..55d98ae0 100644 --- a/docs/commands/bootstrap.mdx +++ b/docs/commands/bootstrap.mdx @@ -19,13 +19,6 @@ melos bs Bootstrapping has two primary functions: 1. Installing all package dependencies (internally using `pub get`). - Optionally, you can use the `--no-example`` flag to exclude flutter package's example's dependencies (https://github.com/dart-lang/pub/pull/3856): - ```bash - melos bootstrap --no-example - # or - melos bs --no-example - ``` - this will run `pub get --no-example` instead of `pub get` 2. Locally linking any packages together via path dependency overrides _without having to edit your pubspec.yaml_. @@ -93,8 +86,14 @@ melos bootstrap --diff="main" ## Bootstrap flags -Melos bootstrap command supports a few different flags that can be defined in -your `melos.yaml`. +- The `--no-example` flag is used to exclude flutter package's example's dependencies (https://github.com/dart-lang/pub/pull/3856) + - This will run `pub get` with the `--no-example` flag. +- The `--enforce-lockfile` flag is used to enforce versions from `.lock` files. + - Ensure .lock files exist, as failure may occur if they're not checked in. + + +In addition to the above flags, the `melos bootstrap` command supports a few different flags that can be defined in + your `melos.yaml` file. ### Shared dependencies diff --git a/docs/configuration/overview.mdx b/docs/configuration/overview.mdx index 9a7a01d8..b5a2b115 100644 --- a/docs/configuration/overview.mdx +++ b/docs/configuration/overview.mdx @@ -178,6 +178,14 @@ Useful in closed network environments with pre-populated pubcaches. The default is `false`. +### enforceLockfile + +Whether to run `pub get` with the `--enforce-lockfile` option or not, to force getting the versions specified in the `pubspec.lock` file. + +This is useful in CI environments or when you want to ensure that all environments/machines are using the same package versions. + +The default is `false`. + ## command/version Configuration for the `version` command. diff --git a/packages/melos/lib/src/command_runner/bootstrap.dart b/packages/melos/lib/src/command_runner/bootstrap.dart index 84f4b343..c6821077 100644 --- a/packages/melos/lib/src/command_runner/bootstrap.dart +++ b/packages/melos/lib/src/command_runner/bootstrap.dart @@ -28,6 +28,12 @@ class BootstrapCommand extends MelosCommand { negatable: false, help: 'Run pub get with/without example pub get', ); + argParser.addFlag( + 'enforce-lockfile', + negatable: false, + help: 'Rub pub get with --enforce-lockfile to enforce versions from .lock' + ' files, ensure .lockfile exist for all packages.', + ); } @override @@ -47,6 +53,7 @@ class BootstrapCommand extends MelosCommand { return melos.bootstrap( global: global, packageFilters: parsePackageFilters(config.path), + enforceLockfile: argResults?['enforce-lockfile'] as bool? ?? false, noExample: argResults?['no-example'] as bool, ); } diff --git a/packages/melos/lib/src/commands/bootstrap.dart b/packages/melos/lib/src/commands/bootstrap.dart index 6160418e..b765fc18 100644 --- a/packages/melos/lib/src/commands/bootstrap.dart +++ b/packages/melos/lib/src/commands/bootstrap.dart @@ -5,6 +5,7 @@ mixin _BootstrapMixin on _CleanMixin { GlobalOptions? global, PackageFilters? packageFilters, bool noExample = false, + bool enforceLockfile = false, }) async { final workspace = await createWorkspace(global: global, packageFilters: packageFilters); @@ -14,14 +15,17 @@ mixin _BootstrapMixin on _CleanMixin { _CommandWithLifecycle.bootstrap, () async { final bootstrapCommandConfig = workspace.config.commands.bootstrap; + final shouldEnforceLockfile = + bootstrapCommandConfig.enforceLockfile || enforceLockfile; final pubCommandForLogging = [ ...pubCommandExecArgs( useFlutter: workspace.isFlutterWorkspace, workspace: workspace, ), 'get', - if (noExample == true) '--no-example', + if (noExample) '--no-example', if (bootstrapCommandConfig.runPubGetOffline) '--offline', + if (shouldEnforceLockfile) '--enforce-lockfile', ].join(' '); logger @@ -54,6 +58,7 @@ mixin _BootstrapMixin on _CleanMixin { await _linkPackagesWithPubspecOverrides( workspace, + enforceLockfile: enforceLockfile, noExample: noExample, ); } on BootstrapException catch (exception) { @@ -83,6 +88,7 @@ mixin _BootstrapMixin on _CleanMixin { Future _linkPackagesWithPubspecOverrides( MelosWorkspace workspace, { + required bool enforceLockfile, required bool noExample, }) async { final filteredPackages = workspace.filteredPackages.values; @@ -114,6 +120,7 @@ mixin _BootstrapMixin on _CleanMixin { await _runPubGetForPackage( workspace, package, + enforceLockfile: enforceLockfile, noExample: noExample, ); @@ -181,8 +188,11 @@ mixin _BootstrapMixin on _CleanMixin { Future _runPubGetForPackage( MelosWorkspace workspace, Package package, { + required bool enforceLockfile, required bool noExample, }) async { + final shouldEnforceLockfile = + workspace.config.commands.bootstrap.enforceLockfile || enforceLockfile; final command = [ ...pubCommandExecArgs( useFlutter: package.isFlutterPackage, @@ -191,6 +201,7 @@ mixin _BootstrapMixin on _CleanMixin { 'get', if (noExample) '--no-example', if (workspace.config.commands.bootstrap.runPubGetOffline) '--offline', + if (shouldEnforceLockfile) '--enforce-lockfile', ]; final process = await startCommandRaw( diff --git a/packages/melos/lib/src/workspace_configs.dart b/packages/melos/lib/src/workspace_configs.dart index fd39c572..da506ab6 100644 --- a/packages/melos/lib/src/workspace_configs.dart +++ b/packages/melos/lib/src/workspace_configs.dart @@ -358,6 +358,7 @@ class BootstrapCommandConfigs { const BootstrapCommandConfigs({ this.runPubGetInParallel = true, this.runPubGetOffline = false, + this.enforceLockfile = false, this.environment, this.dependencies, this.devDependencies, @@ -383,6 +384,13 @@ class BootstrapCommandConfigs { ) ?? false; + final enforceLockfile = assertKeyIsA( + key: 'enforceLockfile', + map: yaml, + path: 'command/bootstrap', + ) ?? + false; + final environment = assertKeyIsA?>( key: 'environment', map: yaml, @@ -431,6 +439,7 @@ class BootstrapCommandConfigs { return BootstrapCommandConfigs( runPubGetInParallel: runPubGetInParallel, runPubGetOffline: runPubGetOffline, + enforceLockfile: enforceLockfile, environment: environment, dependencies: dependencies, devDependencies: devDependencies, @@ -457,6 +466,13 @@ class BootstrapCommandConfigs { /// The default is `false`. final bool runPubGetOffline; + /// Whether `pubspec.lock` is enforced when running `pub get` or not. + /// Useful when you want to ensure the same versions of dependencies are used + /// across different environments/machines. + /// + /// The default is `false`. + final bool enforceLockfile; + /// Environment configuration to be synced between all packages. final Environment? environment; @@ -477,6 +493,7 @@ class BootstrapCommandConfigs { return { 'runPubGetInParallel': runPubGetInParallel, 'runPubGetOffline': runPubGetOffline, + 'enforceLockfile': enforceLockfile, if (environment != null) 'environment': environment!.toJson(), if (dependencies != null) 'dependencies': dependencies!.map( @@ -499,6 +516,7 @@ class BootstrapCommandConfigs { runtimeType == other.runtimeType && other.runPubGetInParallel == runPubGetInParallel && other.runPubGetOffline == runPubGetOffline && + other.enforceLockfile == enforceLockfile && // Extracting equality from environment here as it does not implement == other.environment?.sdkConstraint == environment?.sdkConstraint && const DeepCollectionEquality().equals( @@ -517,6 +535,7 @@ class BootstrapCommandConfigs { runtimeType.hashCode ^ runPubGetInParallel.hashCode ^ runPubGetOffline.hashCode ^ + enforceLockfile.hashCode ^ // Extracting hashCode from environment here as it does not implement // hashCode (environment?.sdkConstraint).hashCode ^ @@ -535,6 +554,7 @@ class BootstrapCommandConfigs { BootstrapCommandConfigs( runPubGetInParallel: $runPubGetInParallel, runPubGetOffline: $runPubGetOffline, + enforceLockfile: $enforceLockfile, environment: $environment, dependencies: $dependencies, devDependencies: $devDependencies, diff --git a/packages/melos/test/commands/bootstrap_test.dart b/packages/melos/test/commands/bootstrap_test.dart index 5f380815..c9012107 100644 --- a/packages/melos/test/commands/bootstrap_test.dart +++ b/packages/melos/test/commands/bootstrap_test.dart @@ -580,6 +580,56 @@ melos bootstrap Running "${pubExecArgs.join(' ')} get --offline" in workspace packages... > SUCCESS +Generating IntelliJ IDE files... + > SUCCESS + + -> 0 packages bootstrapped +''', + ), + ); + }); + + test('can run pub get --enforce-lockfile', () async { + final workspaceDir = await createTemporaryWorkspace( + configBuilder: (path) => MelosWorkspaceConfig.fromYaml( + createYamlMap( + { + 'command': { + 'bootstrap': { + 'enforceLockfile': true, + }, + }, + }, + defaults: configMapDefaults, + ), + path: path, + ), + ); + + final logger = TestLogger(); + final config = await MelosWorkspaceConfig.fromWorkspaceRoot(workspaceDir); + final workspace = await MelosWorkspace.fromConfig( + config, + logger: logger.toMelosLogger(), + ); + final melos = Melos(logger: logger, config: config); + final pubExecArgs = pubCommandExecArgs( + useFlutter: workspace.isFlutterWorkspace, + workspace: workspace, + ); + + await runMelosBootstrap(melos, logger); + + expect( + logger.output, + ignoringAnsii( + ''' +melos bootstrap + └> ${workspaceDir.path} + +Running "${pubExecArgs.join(' ')} get --enforce-lockfile" in workspace packages... + > SUCCESS + Generating IntelliJ IDE files... > SUCCESS diff --git a/packages/melos/test/workspace_config_test.dart b/packages/melos/test/workspace_config_test.dart index 9a5d8540..24606e92 100644 --- a/packages/melos/test/workspace_config_test.dart +++ b/packages/melos/test/workspace_config_test.dart @@ -59,6 +59,7 @@ void main() { const { 'runPubGetInParallel': false, 'runPubGetOffline': true, + 'enforceLockfile': true, 'dependencyOverridePaths': ['a'], }, workspacePath: '.', @@ -66,6 +67,7 @@ void main() { BootstrapCommandConfigs( runPubGetInParallel: false, runPubGetOffline: true, + enforceLockfile: true, dependencyOverridePaths: [ createGlob('a', currentDirectoryPath: '.'), ], @@ -248,6 +250,23 @@ void main() { ); }); + test('can decode `bootstrap` with pub get --enforce-lockfile', () { + expect( + CommandConfigs.fromYaml( + const { + 'bootstrap': { + 'enforceLockfile': true, + }, + }, + workspacePath: '.', + ), + const CommandConfigs( + bootstrap: BootstrapCommandConfigs( + enforceLockfile: true, + ), + ), + ); + }); test('can decode `version`', () { expect( CommandConfigs.fromYaml(