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: allow bundle to take multiple inputs to take advantage of bash's wildcard #483

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
140 changes: 80 additions & 60 deletions packages/mason_cli/lib/src/commands/bundle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,81 +63,101 @@ class BundleCommand extends MasonCommand {
@override
Future<int> run() async {
final source = results['source'] as String;
final outputDir = results['output-dir'] as String;
final bundleType = (results['type'] as String).toBundleType();

final bricks = _parseBricks(source);
final tempBricksJson = <BricksJson>[];
final bundleProgress = logger.progress('Bundling ${bricks.length} bricks');

final bundlePaths = <String>[];
try {
for (final brick in bricks) {
final Directory brickDirectory;
if (brick.location.path != null) {
brickDirectory = Directory(brick.location.path!);
} else {
final tempBrickJson = BricksJson.temp();
final cachedBrick = await tempBrickJson.add(brick);
tempBricksJson.add(tempBrickJson);
brickDirectory = Directory(cachedBrick.path);
}

if (!brickDirectory.existsSync()) {
throw BrickNotFoundException(brickDirectory.path);
}

final bundle = createBundle(brickDirectory);
bundleProgress.update('Bundling ${bundle.name}');

final String bundlePath;
switch (bundleType) {
case BundleType.dart:
bundlePath = await _generateDartBundle(bundle, outputDir);
break;
case BundleType.universal:
bundlePath = await _generateUniversalBundle(bundle, outputDir);
break;
}
bundleProgress.update('Bundled ${bundle.name}');
bundlePaths.add(bundlePath);
}

final Brick brick;
bundleProgress
.update('${lightGreen.wrap('✓')} Generated ${bricks.length} files:');
for (final bundlePath in bundlePaths) {
final logLine = darkGray.wrap(' $bundlePath');
if (logLine != null) {
bundleProgress.update(logLine);
}
}
bundleProgress.complete();
} catch (_) {
bundleProgress.fail();
rethrow;
} finally {
for (final tmp in tempBricksJson) {
tmp.clear();
}
}

return ExitCode.success.code;
}

List<Brick> _parseBricks(String source) {
if (source == 'git') {
if (results.rest.isEmpty) {
usageException('A repository url must be provided');
}
brick = Brick(
location: BrickLocation(
git: GitPath(
results.rest.first,
path: results['git-path'] as String?,
ref: results['git-ref'] as String?,
return results.rest.map((url) {
return Brick(
location: BrickLocation(
git: GitPath(
url,
path: results['git-path'] as String?,
ref: results['git-ref'] as String?,
),
),
),
);
);
}).toList();
} else if (source == 'hosted') {
if (results.rest.isEmpty) {
usageException('A brick name must be provided');
}
brick = Brick(
name: results.rest.first,
location: const BrickLocation(version: 'any'),
);
return results.rest.map((name) {
return Brick(
name: name,
location: const BrickLocation(version: 'any'),
);
}).toList();
} else {
if (results.rest.isEmpty) {
usageException('A path to the brick template must be provided');
}
brick = Brick(location: BrickLocation(path: results.rest.first));
return results.rest.map((location) {
return Brick(location: BrickLocation(path: location));
}).toList();
}

BricksJson? tempBricksJson;

final Directory brickDirectory;
if (brick.location.path != null) {
brickDirectory = Directory(brick.location.path!);
} else {
tempBricksJson = BricksJson.temp();
final cachedBrick = await tempBricksJson.add(brick);
brickDirectory = Directory(cachedBrick.path);
}

if (!brickDirectory.existsSync()) {
throw BrickNotFoundException(brickDirectory.path);
}

final bundle = createBundle(brickDirectory);
final outputDir = results['output-dir'] as String;
final bundleType = (results['type'] as String).toBundleType();
final bundleProgress = logger.progress('Bundling ${bundle.name}');

try {
late final String bundlePath;
switch (bundleType) {
case BundleType.dart:
bundlePath = await _generateDartBundle(bundle, outputDir);
break;
case BundleType.universal:
bundlePath = await _generateUniversalBundle(bundle, outputDir);
break;
}
bundleProgress.complete('Bundled ${bundle.name}');
logger
..info(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
)
..info(darkGray.wrap(' $bundlePath'));
} catch (_) {
bundleProgress.fail();
rethrow;
} finally {
tempBricksJson?.clear();
}

return ExitCode.success.code;
}
}

Expand Down
114 changes: 85 additions & 29 deletions packages/mason_cli/test/commands/bundle_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ void main() {

group('mason bundle', () {
late Logger logger;
late Progress progress;
late PubUpdater pubUpdater;
late MasonCommandRunner commandRunner;

setUp(() {
logger = MockLogger();
progress = MockProgress();
pubUpdater = MockPubUpdater();

when(() => logger.progress(any())).thenReturn(MockProgress());
when(() => logger.progress(any())).thenReturn(progress);
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => packageVersion);
Expand Down Expand Up @@ -82,15 +84,16 @@ void main() {
'''"vars":{"name":{"type":"string","description":"Your name","default":"Dash","prompt":"What is your name?"}}}''',
),
);
verify(() => logger.progress('Bundling greeting')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling greeting')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
luanpotter marked this conversation as resolved.
Show resolved Hide resolved
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

Expand Down Expand Up @@ -144,15 +147,16 @@ void main() {
'''"name":"hooks","description":"A Hooks Example Template","version":"0.1.0+1","environment":{"mason":"any"},"vars":{"name":{"type":"string","description":"Your name","default":"Dash","prompt":"What is your name?"}}''',
),
);
verify(() => logger.progress('Bundling hooks')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling hooks')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

Expand Down Expand Up @@ -197,15 +201,16 @@ void main() {
'''"vars":{"name":{"type":"string","description":"Your name","default":"Dash","prompt":"What is your name?"}}});''',
),
);
verify(() => logger.progress('Bundling greeting')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling greeting')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

Expand Down Expand Up @@ -271,15 +276,64 @@ void main() {
'''"name":"hooks","description":"A Hooks Example Template","version":"0.1.0+1","environment":{"mason":"any"},"vars":{"name":{"type":"string","description":"Your name","default":"Dash","prompt":"What is your name?"}}''',
),
);
verify(() => logger.progress('Bundling hooks')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling hooks')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

test('creates a new dart bundle with multiple files', () async {
final testDir = Directory(
path.join(Directory.current.path, 'dart'),
)..createSync(recursive: true);
final brick1Path =
path.join('..', '..', '..', '..', '..', '..', 'bricks', 'greeting');
final brick2Path =
path.join('..', '..', '..', '..', '..', '..', 'bricks', 'hello');
Directory.current = testDir.path;
final result = await commandRunner.run(
['bundle', brick1Path, brick2Path, '-t', 'dart'],
);
expect(result, equals(ExitCode.success.code));
final file1 = File(
path.join(
testFixturesPath(cwd, suffix: '.bundle'),
'dart',
'greeting_bundle.dart',
),
);
final file2 = File(
path.join(
testFixturesPath(cwd, suffix: '.bundle'),
'dart',
'hello_bundle.dart',
),
);
verify(() => logger.progress('Bundling 2 bricks')).called(1);
verify(() => progress.update('Bundling greeting')).called(1);
verify(() => progress.update('Bundled greeting')).called(1);
verify(() => progress.update('Bundling hello')).called(1);
verify(() => progress.update('Bundled hello')).called(1);
verify(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 2 files:',
),
).called(1);
verify(
() =>
progress.update(darkGray.wrap(' ${canonicalize(file1.path)}')!),
).called(1);
verify(
() =>
progress.update(darkGray.wrap(' ${canonicalize(file2.path)}')!),
).called(1);
});

Expand All @@ -299,12 +353,12 @@ void main() {
verify(
() => logger.err('Could not find brick at $brickPath'),
).called(1);
verifyNever(() => logger.progress(any()));
verifyNever(() => progress.update(any()));
});

test('exists with code 64 when exception occurs on bundling', () async {
final progress = MockProgress();
when(() => progress.complete(any())).thenAnswer((invocation) {
when(() => progress.update(any())).thenAnswer((invocation) {
final update = invocation.positionalArguments[0] as String?;

if (update == 'Bundled greeting') {
Expand Down Expand Up @@ -367,15 +421,16 @@ void main() {
'''"vars":{"name":{"type":"string","description":"Your name","default":"Dash","prompt":"What is your name?"}}}''',
),
);
verify(() => logger.progress('Bundling greeting')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling greeting')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

Expand Down Expand Up @@ -405,10 +460,10 @@ void main() {

expect(result, equals(ExitCode.usage.code));

verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(
() => logger.err('Could not find brick at $url'),
).called(1);
verifyNever(() => logger.progress(any()));
});
});

Expand Down Expand Up @@ -444,15 +499,16 @@ void main() {
expect(actual, contains('"changelog":{"path":"CHANGELOG.md","data":"'));
expect(actual, contains('"license":{"path":"LICENSE","data":"'));

verify(() => logger.progress('Bundling greeting')).called(1);
verify(() => logger.progress('Bundling 1 bricks')).called(1);
verify(() => progress.update('Bundling greeting')).called(1);
verify(
() => logger.info(
() => progress.update(
'${lightGreen.wrap('✓')} '
'Generated 1 file:',
'Generated 1 files:',
),
).called(1);
verify(
() => logger.info(darkGray.wrap(' ${canonicalize(file.path)}')),
() => progress.update(darkGray.wrap(' ${canonicalize(file.path)}')!),
).called(1);
});

Expand Down Expand Up @@ -482,7 +538,7 @@ void main() {
verify(
() => logger.err('Brick "nonexistent-brick" does not exist.'),
).called(1);
verifyNever(() => logger.progress(any()));
verifyNever(() => progress.update(any()));
});
});
});
Expand Down