Skip to content

Commit

Permalink
feat: Add enforce lockfile bootstrap command config (#600)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* 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 <[email protected]>

* Update docs/commands/bootstrap.mdx for clarity

Co-authored-by: Lukas Klingsbo <[email protected]>

* Update docs/commands/bootstrap.mdx for clarity

Co-authored-by: Lukas Klingsbo <[email protected]>

* Update docs/commands/bootstrap.mdx for clarity

Co-authored-by: Lukas Klingsbo <[email protected]>

* Update docs/commands/bootstrap.mdx formatting

Co-authored-by: Lukas Klingsbo <[email protected]>

* Update docs/configuration/overview.mdx for better clarity

Co-authored-by: Lukas Klingsbo <[email protected]>

* Update docs/configuration/overview.mdx for better clarity

Co-authored-by: Lukas Klingsbo <[email protected]>

---------

Co-authored-by: Lukas Klingsbo <[email protected]>
  • Loading branch information
Ayman-Barghout and spydon authored Dec 22, 2023
1 parent 8b69f51 commit b9c6d0c
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 10 deletions.
17 changes: 8 additions & 9 deletions docs/commands/bootstrap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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_.

Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 7 additions & 0 deletions packages/melos/lib/src/command_runner/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
);
}
Expand Down
13 changes: 12 additions & 1 deletion packages/melos/lib/src/commands/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -54,6 +58,7 @@ mixin _BootstrapMixin on _CleanMixin {

await _linkPackagesWithPubspecOverrides(
workspace,
enforceLockfile: enforceLockfile,
noExample: noExample,
);
} on BootstrapException catch (exception) {
Expand Down Expand Up @@ -83,6 +88,7 @@ mixin _BootstrapMixin on _CleanMixin {

Future<void> _linkPackagesWithPubspecOverrides(
MelosWorkspace workspace, {
required bool enforceLockfile,
required bool noExample,
}) async {
final filteredPackages = workspace.filteredPackages.values;
Expand Down Expand Up @@ -114,6 +120,7 @@ mixin _BootstrapMixin on _CleanMixin {
await _runPubGetForPackage(
workspace,
package,
enforceLockfile: enforceLockfile,
noExample: noExample,
);

Expand Down Expand Up @@ -181,8 +188,11 @@ mixin _BootstrapMixin on _CleanMixin {
Future<void> _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,
Expand All @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions packages/melos/lib/src/workspace_configs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ class BootstrapCommandConfigs {
const BootstrapCommandConfigs({
this.runPubGetInParallel = true,
this.runPubGetOffline = false,
this.enforceLockfile = false,
this.environment,
this.dependencies,
this.devDependencies,
Expand All @@ -383,6 +384,13 @@ class BootstrapCommandConfigs {
) ??
false;

final enforceLockfile = assertKeyIsA<bool?>(
key: 'enforceLockfile',
map: yaml,
path: 'command/bootstrap',
) ??
false;

final environment = assertKeyIsA<Map<Object?, Object?>?>(
key: 'environment',
map: yaml,
Expand Down Expand Up @@ -431,6 +439,7 @@ class BootstrapCommandConfigs {
return BootstrapCommandConfigs(
runPubGetInParallel: runPubGetInParallel,
runPubGetOffline: runPubGetOffline,
enforceLockfile: enforceLockfile,
environment: environment,
dependencies: dependencies,
devDependencies: devDependencies,
Expand All @@ -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;

Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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 ^
Expand All @@ -535,6 +554,7 @@ class BootstrapCommandConfigs {
BootstrapCommandConfigs(
runPubGetInParallel: $runPubGetInParallel,
runPubGetOffline: $runPubGetOffline,
enforceLockfile: $enforceLockfile,
environment: $environment,
dependencies: $dependencies,
devDependencies: $devDependencies,
Expand Down
50 changes: 50 additions & 0 deletions packages/melos/test/commands/bootstrap_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions packages/melos/test/workspace_config_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ void main() {
const {
'runPubGetInParallel': false,
'runPubGetOffline': true,
'enforceLockfile': true,
'dependencyOverridePaths': ['a'],
},
workspacePath: '.',
),
BootstrapCommandConfigs(
runPubGetInParallel: false,
runPubGetOffline: true,
enforceLockfile: true,
dependencyOverridePaths: [
createGlob('a', currentDirectoryPath: '.'),
],
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit b9c6d0c

Please sign in to comment.