diff --git a/mono_repo/lib/src/commands/github/github_yaml.dart b/mono_repo/lib/src/commands/github/github_yaml.dart index 46743d70..a38df958 100644 --- a/mono_repo/lib/src/commands/github/github_yaml.dart +++ b/mono_repo/lib/src/commands/github/github_yaml.dart @@ -4,6 +4,8 @@ import 'dart:collection'; +import 'package:path/path.dart'; + import '../../basic_config.dart'; import '../../ci_shared.dart'; import '../../github_config.dart'; @@ -16,6 +18,7 @@ import '../../user_exception.dart'; import '../../yaml.dart'; import 'action_info.dart'; import 'job.dart'; +import 'overrides.dart'; import 'step.dart'; const _onCompletionStage = '_on_completion'; @@ -301,20 +304,40 @@ extension on CIJobEntry { id: pubStepId, // Run this regardless of the success of other steps other than the // pub step. - ifCondition: "always() && steps.checkout.conclusion == 'success'", + ifContent: "always() && steps.checkout.conclusion == 'success'", workingDirectory: package, ), ); for (var i = 0; i < commands.length; i++) { + final task = job.tasks[i]; + final overrides = task.type.overrides; + var workingDirectory = overrides?.workingDirectory; + if (workingDirectory != null) { + workingDirectory = posix.normalize( + posix.join(package, workingDirectory), + ); + } + var ifCondition = overrides?.ifContent; + if (ifCondition != null) { + ifCondition += " && steps.$pubStepId.conclusion == 'success'"; + } commandEntries.add( _CommandEntry( - '$package; ${job.tasks[i].command}', - _commandForOs(job.tasks[i].command), - type: job.tasks[i].type, - // Run this regardless of the success of other steps other than the - // pub step. - ifCondition: "always() && steps.$pubStepId.conclusion == 'success'", - workingDirectory: package, + overrides?.name ?? '$package; ${task.command}', + _commandForOs(task.command), + type: task.type, + id: overrides?.id, + ifContent: ifCondition ?? + // Run this regardless of the success of other steps other than + // the pub step. + "always() && steps.$pubStepId.conclusion == 'success'", + workingDirectory: workingDirectory ?? package, + uses: overrides?.uses, + withContent: overrides?.withContent, + shell: overrides?.shell, + env: overrides?.env, + timeoutMinutes: overrides?.timeoutMinutes, + continueOnError: overrides?.continueOnError, ), ); } @@ -420,30 +443,54 @@ class _CommandEntryBase { [Step.run(name: name, run: run)]; } -class _CommandEntry extends _CommandEntryBase { +class _CommandEntry extends _CommandEntryBase implements GitHubActionOverrides { final TaskType? type; + + @override final String? id; - final String? ifCondition; + + @override + final String? ifContent; + + @override final String workingDirectory; + @override + final String? uses; + + @override + final Map? env; + + @override + final Map? withContent; + + @override + final String? shell; + + @override + final bool? continueOnError; + + @override + final int? timeoutMinutes; + _CommandEntry( super.name, super.run, { required this.workingDirectory, this.type, this.id, - this.ifCondition, + this.ifContent, + this.uses, + this.env, + this.withContent, + this.shell, + this.continueOnError, + this.timeoutMinutes, }); @override Iterable runContent(BasicConfiguration config) => [ - Step.run( - id: id, - name: name, - ifContent: ifCondition, - workingDirectory: workingDirectory, - run: run, - ), + Step.fromOverrides(this), ...?type?.afterEachSteps(workingDirectory, config), ]; } diff --git a/mono_repo/lib/src/commands/github/overrides.dart b/mono_repo/lib/src/commands/github/overrides.dart new file mode 100644 index 00000000..c8e48866 --- /dev/null +++ b/mono_repo/lib/src/commands/github/overrides.dart @@ -0,0 +1,42 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +abstract class GitHubActionOverrides { + /// The step's identifier, which can be used to refer to the step and its + /// outputs in the [ifContent] property of this and other steps. + String? get id; + + /// The name of the step. + String? get name; + + /// The shell command to run for this step. + String? get run; + + /// The GitHub action identifier, e.g. `actions/checkout@v3`. + String? get uses; + + /// The inputs to the action. + /// + /// A map of key-value pairs which are passed to the action's `with` + /// parameter. + Map? get withContent; + + /// The condition on which to run this action. + String? get ifContent; + + /// The directory in which to run this action. + String? get workingDirectory; + + /// The shell override for this action. + String? get shell; + + /// The environment variables for the step. + Map? get env; + + /// Prevents a job from failing when a step fails. + bool? get continueOnError; + + /// The number of minutes to allow the step to run. + int? get timeoutMinutes; +} diff --git a/mono_repo/lib/src/commands/github/step.dart b/mono_repo/lib/src/commands/github/step.dart index e5a1533c..264a3eb6 100644 --- a/mono_repo/lib/src/commands/github/step.dart +++ b/mono_repo/lib/src/commands/github/step.dart @@ -5,6 +5,7 @@ import 'package:json_annotation/json_annotation.dart'; import '../../yaml.dart'; +import 'overrides.dart'; part 'step.g.dart'; @@ -13,21 +14,41 @@ part 'step.g.dart'; includeIfNull: false, constructor: '_', ) -class Step implements YamlLike { +class Step implements GitHubActionOverrides, YamlLike { + @override final String? id; + @override final String? name; + @override final String? run; + @override @JsonKey(name: 'if') final String? ifContent; + + @override @JsonKey(name: 'working-directory') final String? workingDirectory; - final Map? env; + @override + final Map? env; + @override final String? uses; + @override @JsonKey(name: 'with') - final Map? withContent; + final Map? withContent; + + @override + final String? shell; + + @override + @JsonKey(name: 'continue-on-error') + final bool? continueOnError; + + @override + @JsonKey(name: 'timeout-minutes') + final int? timeoutMinutes; Step._({ this.id, @@ -38,6 +59,9 @@ class Step implements YamlLike { this.ifContent, this.workingDirectory, this.env, + this.shell, + this.continueOnError, + this.timeoutMinutes, }) { if (run == null) { if (uses == null) { @@ -64,25 +88,56 @@ class Step implements YamlLike { } } + factory Step.fromOverrides(GitHubActionOverrides overrides) { + if (overrides.uses != null) { + return Step._( + id: overrides.id, + name: overrides.name, + uses: overrides.uses, + withContent: overrides.withContent, + ifContent: overrides.ifContent, + continueOnError: overrides.continueOnError, + timeoutMinutes: overrides.timeoutMinutes, + ); + } + return Step._( + id: overrides.id, + name: overrides.name, + run: overrides.run, + workingDirectory: overrides.workingDirectory, + env: overrides.env, + shell: overrides.shell, + ifContent: overrides.ifContent, + continueOnError: overrides.continueOnError, + timeoutMinutes: overrides.timeoutMinutes, + ); + } + Step.run({ this.id, required String this.name, - required this.run, + required String this.run, this.ifContent, this.workingDirectory, this.env, + this.shell, + this.continueOnError, + this.timeoutMinutes, }) : uses = null, withContent = null; Step.uses({ this.id, required String this.name, - required this.uses, + required String this.uses, this.withContent, this.ifContent, + this.continueOnError, + this.timeoutMinutes, }) : run = null, env = null, - workingDirectory = null; + workingDirectory = null, + shell = null; factory Step.fromJson(Map json) => _$StepFromJson(json); diff --git a/mono_repo/lib/src/commands/github/step.g.dart b/mono_repo/lib/src/commands/github/step.g.dart index bdae7e96..074527d9 100644 --- a/mono_repo/lib/src/commands/github/step.g.dart +++ b/mono_repo/lib/src/commands/github/step.g.dart @@ -14,21 +14,35 @@ Step _$StepFromJson(Map json) => $checkedCreate( ($checkedConvert) { final val = Step._( id: $checkedConvert('id', (v) => v as String?), - withContent: $checkedConvert('with', (v) => v as Map?), + withContent: $checkedConvert( + 'with', + (v) => (v as Map?)?.map( + (k, e) => MapEntry(k as String, e), + )), name: $checkedConvert('name', (v) => v as String?), uses: $checkedConvert('uses', (v) => v as String?), run: $checkedConvert('run', (v) => v as String?), ifContent: $checkedConvert('if', (v) => v as String?), workingDirectory: $checkedConvert('working-directory', (v) => v as String?), - env: $checkedConvert('env', (v) => v as Map?), + env: $checkedConvert( + 'env', + (v) => (v as Map?)?.map( + (k, e) => MapEntry(k as String, e as String), + )), + shell: $checkedConvert('shell', (v) => v as String?), + continueOnError: + $checkedConvert('continue-on-error', (v) => v as bool?), + timeoutMinutes: $checkedConvert('timeout-minutes', (v) => v as int?), ); return val; }, fieldKeyMap: const { 'withContent': 'with', 'ifContent': 'if', - 'workingDirectory': 'working-directory' + 'workingDirectory': 'working-directory', + 'continueOnError': 'continue-on-error', + 'timeoutMinutes': 'timeout-minutes' }, ); @@ -49,5 +63,8 @@ Map _$StepToJson(Step instance) { writeNotNull('env', instance.env); writeNotNull('uses', instance.uses); writeNotNull('with', instance.withContent); + writeNotNull('shell', instance.shell); + writeNotNull('continue-on-error', instance.continueOnError); + writeNotNull('timeout-minutes', instance.timeoutMinutes); return val; } diff --git a/mono_repo/lib/src/github_config.dart b/mono_repo/lib/src/github_config.dart index 6e49c5c5..443a4786 100644 --- a/mono_repo/lib/src/github_config.dart +++ b/mono_repo/lib/src/github_config.dart @@ -2,9 +2,12 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:convert'; + import 'package:json_annotation/json_annotation.dart'; import 'commands/github/job.dart'; +import 'commands/github/overrides.dart'; part 'github_config.g.dart'; @@ -141,6 +144,217 @@ class GitHubWorkflow { factory GitHubWorkflow.fromJson(Map json) => _$GitHubWorkflowFromJson(json); } +/// Configuration for a single step in a GitHub Actions task. +class GitHubActionConfig implements GitHubActionOverrides { + GitHubActionConfig({ + this.id, + this.name, + this.run, + this.uses, + this.ifContent, + this.withContent, + this.workingDirectory, + this.shell, + this.env, + this.continueOnError, + this.timeoutMinutes, + }) : assert( + run != null || uses != null, + 'Either `run` or `uses` must be specified', + ); + + @override + final String? id; + + @override + final String? name; + + @override + final String? run; + + @override + final String? uses; + + @override + final Map? withContent; + + @override + final String? ifContent; + + @override + final String? workingDirectory; + + @override + final String? shell; + + @override + final Map? env; + + @override + final bool? continueOnError; + + @override + final int? timeoutMinutes; + + factory GitHubActionConfig.fromJson(Map json) { + // Create a copy of unmodifiable `json`. + json = Map.of(json); + + // Transform -> instead of throwing so + // that, for example, numerical values are properly converted. + Map? toEnvMap(Map? map) => map?.map( + (key, value) { + if (value is! String) { + value = jsonEncode(value); + } + return MapEntry(key as String, value); + }, + ); + + final Object? id = json.remove('id'); + if (id is! String?) { + throw CheckedFromJsonException( + json, + 'id', + 'GitHubActionConfig', + 'Invalid `id` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid', + ); + } + final Object? name = json.remove('name'); + if (name is! String?) { + throw CheckedFromJsonException( + json, + 'name', + 'GitHubActionConfig', + 'Invalid `name` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsname', + ); + } + final Object? run = json.remove('run'); + if (run is! String?) { + throw CheckedFromJsonException( + json, + 'run', + 'GitHubActionConfig', + 'Invalid `run` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun', + ); + } + final Object? uses = json.remove('uses'); + if (uses is! String?) { + throw CheckedFromJsonException( + json, + 'uses', + 'GitHubActionConfig', + 'Invalid `uses` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses', + ); + } + final Object? withContent = json.remove('with'); + if (withContent is! Map?) { + throw CheckedFromJsonException( + json, + 'with', + 'GitHubActionConfig', + 'Invalid `with` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepswith', + ); + } + final Object? ifContent = json.remove('if'); + if (ifContent is! String?) { + throw CheckedFromJsonException( + json, + 'if', + 'GitHubActionConfig', + 'Invalid `if` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif', + ); + } + final Object? workingDirectory = json.remove('working-directory'); + if (workingDirectory is! String?) { + throw CheckedFromJsonException( + json, + 'working-directory', + 'GitHubActionConfig', + 'Invalid `working-directory` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun', + ); + } + final Object? shell = json.remove('shell'); + if (shell is! String?) { + throw CheckedFromJsonException( + json, + 'shell', + 'GitHubActionConfig', + 'Invalid `shell` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell', + ); + } + final Object? env = json.remove('env'); + if (env is! Map?) { + throw CheckedFromJsonException( + json, + 'env', + 'GitHubActionConfig', + 'Invalid `env` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsenv', + ); + } + final Object? continueOnError = json.remove('continue-on-error'); + if (continueOnError is! bool?) { + throw CheckedFromJsonException( + json, + 'continue-on-error', + 'GitHubActionConfig', + 'Invalid `continue-on-error` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error', + ); + } + final Object? timeoutMinutes = json.remove('timeout-minutes'); + if (timeoutMinutes is! num?) { + throw CheckedFromJsonException( + json, + 'timeout-minutes', + 'GitHubActionConfig', + 'Invalid `timeout-minutes` parameter. See GitHub docs for more info: ' + 'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes', + ); + } + + if (uses == null && run == null) { + throw CheckedFromJsonException( + json, + 'run,uses', + 'GitHubActionConfig', + 'Either `run` or `uses` must be specified', + ); + } + if (json.isNotEmpty) { + throw CheckedFromJsonException( + json, + json.keys.join(','), + 'GitHubActionConfig', + 'Invalid keys', + ); + } + + return GitHubActionConfig( + id: id, + name: name, + uses: uses, + run: run, + withContent: toEnvMap(withContent), + ifContent: ifContent, + workingDirectory: workingDirectory, + shell: shell, + env: toEnvMap(env), + continueOnError: continueOnError, + timeoutMinutes: timeoutMinutes?.toInt(), + ); + } +} + Map _parseOn(Map? on, String? cron) { if (on == null) { if (cron == null) { diff --git a/mono_repo/lib/src/package_config.dart b/mono_repo/lib/src/package_config.dart index 76a949fc..df2bd99f 100644 --- a/mono_repo/lib/src/package_config.dart +++ b/mono_repo/lib/src/package_config.dart @@ -9,6 +9,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:yaml/yaml.dart'; +import 'github_config.dart'; import 'package_flavor.dart'; import 'raw_config.dart'; import 'task_type.dart'; @@ -370,6 +371,21 @@ class Task { } final taskName = taskNames.single; + + if (taskName == 'github_action') { + final configYaml = yamlValue['github_action'] as Object?; + if (configYaml is! Map) { + throw CheckedFromJsonException( + yamlValue, + 'github_action', + 'GitHubActionConfig', + 'Must be a map', + ); + } + final config = GitHubActionConfig.fromJson(configYaml); + return Task(flavor, TaskType.githubAction(config)); + } + final taskType = _taskTypeForName(taskName); String? args; diff --git a/mono_repo/lib/src/task_type.dart b/mono_repo/lib/src/task_type.dart index b3026836..77f05542 100644 --- a/mono_repo/lib/src/task_type.dart +++ b/mono_repo/lib/src/task_type.dart @@ -4,13 +4,18 @@ import 'basic_config.dart'; import 'commands/github/action_info.dart'; +import 'commands/github/overrides.dart'; import 'commands/github/step.dart'; import 'coverage_processor.dart'; +import 'github_config.dart'; import 'package_flavor.dart'; abstract class TaskType implements Comparable { static const command = _CommandTask(); + const factory TaskType.githubAction(GitHubActionConfig config) = + _GitHubActionTaskType; + static const _values = [ _FormatTask(), _AnalyzeTask(), @@ -43,15 +48,20 @@ abstract class TaskType implements Comparable { ) => const Iterable.empty(); + GitHubActionOverrides? get overrides => null; + static Iterable get allowedTaskNames sync* { for (var val in TaskType._values) { yield val.name; yield* val.alternates; } + yield _GitHubActionTaskType._name; } - static final prettyTaskList = - TaskType._values.map((t) => '`${t.name}`').join(', '); + static final prettyTaskList = [ + ...TaskType._values.map((t) => '`${t.name}`'), + '`${_GitHubActionTaskType._name}`', + ].join(', '); static TaskType taskFromName(String input) => TaskType._values.singleWhere( (element) => @@ -173,3 +183,18 @@ class _TestWithCoverageTask extends TaskType { ]; } } + +class _GitHubActionTaskType extends TaskType { + const _GitHubActionTaskType(this.overrides) : super._(_name); + + static const _name = 'github_action'; + + @override + final GitHubActionConfig overrides; + + @override + List commandValue(PackageFlavor flavor, String? args) => [ + if (overrides.uses != null) overrides.uses!, + if (overrides.run != null) overrides.run!, + ]; +} diff --git a/mono_repo/test/generate_test.dart b/mono_repo/test/generate_test.dart index 208118da..405df751 100644 --- a/mono_repo/test/generate_test.dart +++ b/mono_repo/test/generate_test.dart @@ -1230,6 +1230,163 @@ env: ); }); }); + + group('github actions config', () { + test('custom uses', () async { + await d.dir('pkg_a', [ + d.file('pubspec.yaml', ''' +name: pkg_a +'''), + d.file(monoPkgFileName, ''' +sdk: +- dev + +stages: + - custom_step: + - github_action: + id: custom-scripts + name: 'My Custom Scripts' + uses: ./.github/actions/my-action + with: + my-key: my-var + my-num: 123 + my-map: {'abc':123} + if: \${{ github.event_name == 'pull_request' }} + continue-on-error: false + timeout-minutes: 30 +''') + ]).create(); + + testGenerateBothConfig(printMatcher: anything); + + await d + .file( + defaultGitHubWorkflowFilePath, + contains(''' + name: My Custom Scripts + if: "\${{ github.event_name == 'pull_request' }} && steps.pkg_a_pub_upgrade.conclusion == 'success'" + uses: "./.github/actions/my-action" + with: + my-key: my-var + my-num: "123" + my-map: "{\\"abc\\":123}" + continue-on-error: false + timeout-minutes: 30 +'''), + ) + .validate(); + }); + + test('custom run', () async { + await d.dir('pkg_a', [ + d.file('pubspec.yaml', ''' +name: pkg_a +'''), + d.file(monoPkgFileName, ''' +sdk: +- dev + +stages: + - custom_step: + - github_action: + id: custom-scripts + run: | + ./script_a + ./script_b + ./script_c + if: \${{ github.event_name == 'pull_request' }} + working-directory: ./tool + shell: fish + env: + my-key: my-var + my-num: 123 + my-map: {'abc':123} + continue-on-error: false + timeout-minutes: 30 +''') + ]).create(); + + testGenerateBothConfig(printMatcher: anything); + + await d + .file( + defaultGitHubWorkflowFilePath, + contains(''' + run: | + ./script_a + ./script_b + ./script_c + if: "\${{ github.event_name == 'pull_request' }} && steps.pkg_a_pub_upgrade.conclusion == 'success'" + working-directory: pkg_a/tool + env: + my-key: my-var + my-num: "123" + my-map: "{\\"abc\\":123}" + shell: fish + continue-on-error: false + timeout-minutes: 30 +'''), + ) + .validate(); + }); + + test('merges `id`, `if`, `working-directory`', () async { + await d.dir('pkg_a', [ + d.file('pubspec.yaml', ''' +name: pkg_a +'''), + d.file(monoPkgFileName, ''' +sdk: +- dev + +stages: + - custom_step: + - github_action: + id: custom-script + if: always() + run: ./script + working-directory: ./tool +''') + ]).create(); + + testGenerateBothConfig(printMatcher: anything); + + await d + .file( + defaultGitHubWorkflowFilePath, + stringContainsInOrder([ + 'id: custom-script', + 'if: "always() && ' + r"steps.pkg_a_pub_upgrade.conclusion == 'success'", + 'working-directory: pkg_a/tool', + ]), + ) + .validate(); + }); + + test('disallows unknown keys', () async { + await d.dir('pkg_a', [ + d.file('pubspec.yaml', ''' +name: pkg_a +'''), + d.file(monoPkgFileName, ''' +sdk: +- dev + +stages: + - custom_step: + - github_action: + run: ./script + some-key: some-value +''') + ]).create(); + + expect( + testGenerateBothConfig, + throwsACheckedFromJsonException('Invalid keys'), + ); + }); + }); } String get _subPkgStandardOutput => ''' diff --git a/mono_repo/test/mono_config_test.dart b/mono_repo/test/mono_config_test.dart index 99cae8a6..a99c7f19 100644 --- a/mono_repo/test/mono_config_test.dart +++ b/mono_repo/test/mono_config_test.dart @@ -211,7 +211,7 @@ line 4, column 12: Unsupported value for "group". expected a list of tasks _expectParseThrows( monoYaml, r''' -line 9, column 6: Must have one key of `format`, `analyze`, `test`, `command`, `test_with_coverage`. +line 9, column 6: Must have one key of `format`, `analyze`, `test`, `command`, `test_with_coverage`, `github_action`. ╷ 9 │ "weird": "thing" │ ^^^^^^^ @@ -234,7 +234,7 @@ line 9, column 6: Must have one key of `format`, `analyze`, `test`, `command`, ` _expectParseThrows( monoYaml, r''' -line 10, column 6: Must have one and only one key of `format`, `analyze`, `test`, `command`, `test_with_coverage`. +line 10, column 6: Must have one and only one key of `format`, `analyze`, `test`, `command`, `test_with_coverage`, `github_action`. ╷ 10 │ "command": "other thing" │ ^^^^^^^^^ @@ -415,6 +415,13 @@ stages: - test: --preset travis --total-shards 5 --shard-index 0 - test: --preset travis --total-shards 5 --shard-index 1 - test #no args + - group: + - github_action: + uses: actions/setup-node@v3 + with: + node-version: 16 + - command: npm run build + - test: --platform node '''; List get _testConfig1expectedOutput => [ @@ -607,5 +614,53 @@ List get _testConfig1expectedOutput => [ {'flavor': 'dart', 'type': 'test'} ], 'flavor': 'dart' - } + }, + { + 'os': 'linux', + 'package': 'a', + 'sdk': '1.23.0', + 'stageName': 'unit_test', + 'tasks': [ + {'flavor': 'dart', 'type': 'github_action'}, + {'flavor': 'dart', 'type': 'command', 'args': 'npm run build'}, + { + 'flavor': 'dart', + 'type': 'test', + 'args': '--platform node', + } + ], + 'flavor': 'dart' + }, + { + 'os': 'linux', + 'package': 'a', + 'sdk': 'dev', + 'stageName': 'unit_test', + 'tasks': [ + {'flavor': 'dart', 'type': 'github_action'}, + {'flavor': 'dart', 'type': 'command', 'args': 'npm run build'}, + { + 'flavor': 'dart', + 'type': 'test', + 'args': '--platform node', + } + ], + 'flavor': 'dart' + }, + { + 'os': 'linux', + 'package': 'a', + 'sdk': 'stable', + 'stageName': 'unit_test', + 'tasks': [ + {'flavor': 'dart', 'type': 'github_action'}, + {'flavor': 'dart', 'type': 'command', 'args': 'npm run build'}, + { + 'flavor': 'dart', + 'type': 'test', + 'args': '--platform node', + } + ], + 'flavor': 'dart' + }, ]; diff --git a/mono_repo/test/shared.dart b/mono_repo/test/shared.dart index 1ba6390a..b36984fb 100644 --- a/mono_repo/test/shared.dart +++ b/mono_repo/test/shared.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:io'; import 'package:checked_yaml/checked_yaml.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:mono_repo/src/ci_shared.dart'; import 'package:mono_repo/src/commands/ci_script/generate.dart'; import 'package:mono_repo/src/commands/generate.dart'; @@ -103,6 +104,11 @@ Matcher throwsAParsedYamlException(Object matcher) => throwsA( ), ); +Matcher throwsACheckedFromJsonException(Object message) => throwsA( + isA() + .having((e) => e.message, 'message', message), + ); + const testConfig2 = r''' sdk: - dev