Skip to content

Commit 0ee8514

Browse files
author
Dillon Nys
committed
feat: GitHub Action customizations
1 parent 6aec157 commit 0ee8514

File tree

7 files changed

+407
-19
lines changed

7 files changed

+407
-19
lines changed

mono_repo/lib/src/commands/github/github_yaml.dart

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'dart:collection';
66

7+
import 'package:path/path.dart';
8+
79
import '../../ci_shared.dart';
810
import '../../github_config.dart';
911
import '../../mono_config.dart';
@@ -308,15 +310,27 @@ extension on CIJobEntry {
308310
),
309311
);
310312
for (var i = 0; i < commands.length; i++) {
313+
final task = job.tasks[i];
314+
var workingDirectory = task.action?.workingDirectory;
315+
if (workingDirectory != null) {
316+
workingDirectory = posix.normalize(
317+
posix.join(package, workingDirectory),
318+
);
319+
}
311320
commandEntries.add(
312321
_CommandEntry(
313-
'$package; ${job.tasks[i].command}',
314-
_commandForOs(job.tasks[i].command),
315-
type: job.tasks[i].type,
316-
// Run this regardless of the success of other steps other than the
317-
// pub step.
318-
ifCondition: "always() && steps.$pubStepId.conclusion == 'success'",
319-
workingDirectory: package,
322+
'$package; ${task.command}',
323+
_commandForOs(task.command),
324+
type: task.type,
325+
id: task.action?.id,
326+
ifCondition: task.action?.condition ??
327+
// Run this regardless of the success of other steps other than
328+
// the pub step.
329+
"always() && steps.$pubStepId.conclusion == 'success'",
330+
workingDirectory: workingDirectory ?? package,
331+
uses: task.action?.uses,
332+
inputs: task.action?.inputs,
333+
shell: task.action?.shell,
320334
),
321335
);
322336
}
@@ -425,6 +439,10 @@ class _CommandEntry extends _CommandEntryBase {
425439
final String? id;
426440
final String? ifCondition;
427441
final String workingDirectory;
442+
final String? uses;
443+
final Map<String, String>? env;
444+
final Map<String, Object>? inputs;
445+
final String? shell;
428446

429447
_CommandEntry(
430448
super.name,
@@ -433,17 +451,32 @@ class _CommandEntry extends _CommandEntryBase {
433451
this.type,
434452
this.id,
435453
this.ifCondition,
454+
this.uses,
455+
this.env,
456+
this.inputs,
457+
this.shell,
436458
});
437459

438460
@override
439461
Iterable<Step> get runContent => [
440-
Step.run(
441-
id: id,
442-
name: name,
443-
ifContent: ifCondition,
444-
workingDirectory: workingDirectory,
445-
run: run,
446-
),
462+
if (uses != null)
463+
Step.uses(
464+
id: id,
465+
name: name,
466+
uses: uses,
467+
withContent: inputs,
468+
ifContent: ifCondition,
469+
)
470+
else
471+
Step.run(
472+
id: id,
473+
name: name,
474+
ifContent: ifCondition,
475+
workingDirectory: workingDirectory,
476+
run: run,
477+
env: env,
478+
shell: shell,
479+
),
447480
...?type?.afterEachSteps(workingDirectory),
448481
];
449482
}

mono_repo/lib/src/commands/github/step.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class Step implements YamlLike {
2929
@JsonKey(name: 'with')
3030
final Map? withContent;
3131

32+
final String? shell;
33+
3234
Step._({
3335
this.id,
3436
this.withContent,
@@ -38,6 +40,7 @@ class Step implements YamlLike {
3840
this.ifContent,
3941
this.workingDirectory,
4042
this.env,
43+
this.shell,
4144
}) {
4245
if (run == null) {
4346
if (uses == null) {
@@ -71,6 +74,7 @@ class Step implements YamlLike {
7174
this.ifContent,
7275
this.workingDirectory,
7376
this.env,
77+
this.shell,
7478
}) : uses = null,
7579
withContent = null;
7680

@@ -82,7 +86,8 @@ class Step implements YamlLike {
8286
this.ifContent,
8387
}) : run = null,
8488
env = null,
85-
workingDirectory = null;
89+
workingDirectory = null,
90+
shell = null;
8691

8792
factory Step.fromJson(Map json) => _$StepFromJson(json);
8893

mono_repo/lib/src/github_config.dart

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:convert';
6+
57
import 'package:json_annotation/json_annotation.dart';
68

79
import 'commands/github/job.dart';
@@ -141,6 +143,142 @@ class GitHubWorkflow {
141143
factory GitHubWorkflow.fromJson(Map json) => _$GitHubWorkflowFromJson(json);
142144
}
143145

146+
/// Extra configuration for customizing the GitHub action context in which a
147+
/// task runs.
148+
class ActionConfig {
149+
ActionConfig({
150+
this.id,
151+
this.uses,
152+
this.condition,
153+
this.inputs,
154+
this.workingDirectory,
155+
this.shell,
156+
});
157+
158+
/// The step's identifier, which can be used to refer to the step and its
159+
/// outputs in the [condition] property of this and other steps.
160+
final String? id;
161+
162+
/// The GitHub action identifier, e.g. `actions/checkout@v3`.
163+
final String? uses;
164+
165+
/// The inputs to the action.
166+
///
167+
/// A map of key-value pairs which are passed to the action's `with`
168+
/// parameter.
169+
final Map<String, String>? inputs;
170+
171+
/// The condition on which to run this action.
172+
final String? condition;
173+
174+
/// The directory in which to run this action.
175+
final String? workingDirectory;
176+
177+
/// The shell override for this action.
178+
final String? shell;
179+
180+
factory ActionConfig.fromJson(Map json) {
181+
// Create a copy of unmodifiable `json`.
182+
json = Map.of(json);
183+
184+
final Object? id = json.remove('id');
185+
if (id is! String?) {
186+
throw CheckedFromJsonException(
187+
json,
188+
'id',
189+
'ActionConfig',
190+
'Invalid `id` parameter. See GitHub docs for more info: '
191+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsid',
192+
);
193+
}
194+
final Object? uses = json.remove('uses');
195+
if (uses is! String?) {
196+
throw CheckedFromJsonException(
197+
json,
198+
'uses',
199+
'ActionConfig',
200+
'Invalid `uses` parameter. See GitHub docs for more info: '
201+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsuses',
202+
);
203+
}
204+
final Object? inputs = json.remove('with');
205+
if (inputs is! Map?) {
206+
throw CheckedFromJsonException(
207+
json,
208+
'with',
209+
'ActionConfig',
210+
'Invalid `with` parameter. See GitHub docs for more info: '
211+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepswith',
212+
);
213+
}
214+
// Transform <String, Object> -> <String, String> instead of throwing so
215+
// that, for example, numerical values are properly converted.
216+
final mappedInputs = inputs?.map(
217+
(key, value) {
218+
if (value is! String) {
219+
value = jsonEncode(value);
220+
}
221+
return MapEntry(key as String, value);
222+
},
223+
);
224+
final Object? condition = json.remove('if');
225+
if (condition is! String?) {
226+
throw CheckedFromJsonException(
227+
json,
228+
'if',
229+
'ActionConfig',
230+
'Invalid `if` parameter. See GitHub docs for more info: '
231+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif',
232+
);
233+
}
234+
final Object? workingDirectory = json.remove('working-directory');
235+
if (workingDirectory is! String?) {
236+
throw CheckedFromJsonException(
237+
json,
238+
'working-directory',
239+
'ActionConfig',
240+
'Invalid `working-directory` parameter. See GitHub docs for more info: '
241+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun',
242+
);
243+
}
244+
final Object? shell = json.remove('shell');
245+
if (shell is! String?) {
246+
throw CheckedFromJsonException(
247+
json,
248+
'shell',
249+
'ActionConfig',
250+
'Invalid `shell` parameter. See GitHub docs for more info: '
251+
'https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell',
252+
);
253+
}
254+
if (json.isNotEmpty) {
255+
throw CheckedFromJsonException(
256+
json,
257+
json.keys.join(','),
258+
'ActionConfig',
259+
'Invalid keys',
260+
);
261+
}
262+
return ActionConfig(
263+
id: id,
264+
uses: uses,
265+
inputs: mappedInputs,
266+
condition: condition,
267+
workingDirectory: workingDirectory,
268+
shell: shell,
269+
);
270+
}
271+
272+
Map<String, Object> toJson() => {
273+
if (id != null) 'id': id!,
274+
if (uses != null) 'uses': uses!,
275+
if (inputs != null) 'with': inputs!,
276+
if (condition != null) 'if': condition!,
277+
if (workingDirectory != null) 'working-directory': workingDirectory!,
278+
if (shell != null) 'shell': shell!,
279+
};
280+
}
281+
144282
Map<String, dynamic> _parseOn(Map<String, dynamic>? on, String? cron) {
145283
if (on == null) {
146284
if (cron == null) {

mono_repo/lib/src/package_config.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:pub_semver/pub_semver.dart';
99
import 'package:pubspec_parse/pubspec_parse.dart';
1010
import 'package:yaml/yaml.dart';
1111

12+
import 'github_config.dart';
1213
import 'package_flavor.dart';
1314
import 'raw_config.dart';
1415
import 'task_type.dart';
@@ -307,7 +308,10 @@ class Task {
307308

308309
final String command;
309310

310-
Task(this.flavor, this.type, {this.args})
311+
/// The GitHub action context.
312+
final ActionConfig? action;
313+
314+
Task(this.flavor, this.type, {this.args, this.action})
311315
: command = type.commandValue(flavor, args).join(' ');
312316

313317
/// Parses an individual item under `stages`, which might be a `group` or an
@@ -395,8 +399,22 @@ class Task {
395399
args = yamlValue[taskName] as String?;
396400
}
397401

402+
final actionYaml = yamlValue['action'] as Object?;
403+
if (actionYaml is! Map?) {
404+
throw CheckedFromJsonException(
405+
yamlValue,
406+
'action',
407+
'Task',
408+
'Must be a map',
409+
);
410+
}
411+
ActionConfig? action;
412+
if (actionYaml != null) {
413+
action = ActionConfig.fromJson(actionYaml);
414+
}
415+
398416
final extraConfig = Set<String>.from(yamlValue.keys)
399-
..removeAll([taskName, 'os', 'sdk']);
417+
..removeAll([taskName, 'os', 'sdk', 'action']);
400418

401419
// TODO(kevmoo): at some point, support custom configuration here
402420
if (extraConfig.isNotEmpty) {
@@ -409,7 +427,7 @@ class Task {
409427
);
410428
}
411429
try {
412-
return Task(flavor, taskType, args: args);
430+
return Task(flavor, taskType, args: args, action: action);
413431
} on InvalidTaskConfigException catch (e) {
414432
throw CheckedFromJsonException(
415433
yamlValue,

0 commit comments

Comments
 (0)