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: Add enforce lockfile bootstrap command config #600

Merged
merged 22 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5faf4bc
Enforce lockfile for CI environments
Ayman-Barghout Nov 13, 2023
7bfb60c
Add enforce lockfile to bootstrap command config
Ayman-Barghout Nov 13, 2023
e691b80
Enforce lockfile based on the bootstrap command workspace config
Ayman-Barghout Nov 13, 2023
ae270ad
Update overview.mdx with enforce lockfile option
Ayman-Barghout Nov 13, 2023
09e0aa4
Update the `docstring` for `enforceLockfile` to include it is default…
Ayman-Barghout Nov 13, 2023
39eb0a0
Update workspace config and bootstrap tests to include the new enforc…
Ayman-Barghout Nov 13, 2023
11e8eb2
Stop enforcing lock file on CI by default
Ayman-Barghout Nov 13, 2023
768c340
Remove unnecessary parenthesis as linter is complaining
Ayman-Barghout Nov 21, 2023
e4b1c38
Merge branch 'main' into enforce-lockfile-for-ci
Ayman-Barghout Nov 22, 2023
a4bdc53
Update enforcing lock files to be inclusive of Dart as well as Flutter
Ayman-Barghout Dec 4, 2023
42e0a4e
Merge branch 'main' into enforce-lockfile-for-ci
Ayman-Barghout Dec 4, 2023
d49db30
Add `--enforce-lockfile` as a bootstrap flag
Ayman-Barghout Dec 18, 2023
c0daaa2
Merge branch 'main' into enforce-lockfile-for-ci
Ayman-Barghout Dec 18, 2023
11b1cc3
Fix duplicate calling of enforce lockfile flag
Ayman-Barghout Dec 18, 2023
de22390
Consolidate bootstrap flags documentation
Ayman-Barghout Dec 18, 2023
8b081a5
Update docs/commands/bootstrap.mdx for clarity
Ayman-Barghout Dec 19, 2023
3bb9d04
Update docs/commands/bootstrap.mdx for clarity
Ayman-Barghout Dec 19, 2023
ba66172
Update docs/commands/bootstrap.mdx for clarity
Ayman-Barghout Dec 19, 2023
1a6cd2c
Update docs/commands/bootstrap.mdx for clarity
Ayman-Barghout Dec 19, 2023
4c2734e
Update docs/commands/bootstrap.mdx formatting
Ayman-Barghout Dec 19, 2023
9b62565
Update docs/configuration/overview.mdx for better clarity
Ayman-Barghout Dec 19, 2023
47d714f
Update docs/configuration/overview.mdx for better clarity
Ayman-Barghout Dec 19, 2023
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
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
Loading