refactor: improve repo structure
This commit is contained in:
parent
2bdf75d2ad
commit
19af0bbd1b
|
|
@ -3,3 +3,5 @@
|
|||
- read `README.md` for general information
|
||||
run `pnpx skills experimental_install`, `lefthook install` before development at first time.
|
||||
- For any new or modified Dart test under `test/`, use the repo-local skill `dart-gherkin-tests`.
|
||||
- For file more than 500 lines, consider to split it into multiple files for better maintainability.
|
||||
- Keep refactoring e.g. `SOLID` principles
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export 'src/comment_templates.dart';
|
||||
export 'src/config.dart';
|
||||
export 'src/database.dart';
|
||||
export 'src/app_logger.dart';
|
||||
export 'src/app_environment.dart';
|
||||
export 'src/issue_event_source/base.dart';
|
||||
export 'src/issue_tracker_client.dart';
|
||||
export 'src/issue_assistant_app.dart';
|
||||
export 'src/notifier.dart';
|
||||
export 'src/scm_client.dart';
|
||||
export 'src/workspace_manager.dart';
|
||||
export 'src/config/comment_templates.dart';
|
||||
export 'src/config/config.dart';
|
||||
export 'src/core/database.dart';
|
||||
export 'src/config/app_logger.dart';
|
||||
export 'src/config/app_environment.dart';
|
||||
export 'src/issue_tracker/issue_event_source/base.dart';
|
||||
export 'src/issue_tracker/issue_tracker_client.dart';
|
||||
export 'src/core/issue_assistant_app.dart';
|
||||
export 'src/notifier/notifier.dart';
|
||||
export 'src/workspace/scm_client.dart';
|
||||
export 'src/workspace/workspace_manager.dart';
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'config.dart';
|
||||
import 'issue_tracker_client.dart';
|
||||
import '../config/config.dart';
|
||||
import '../issue_tracker/issue_tracker_client.dart';
|
||||
import 'process_launcher.dart';
|
||||
|
||||
class CliResponderRunner {
|
||||
|
|
@ -3,17 +3,17 @@ import 'dart:convert';
|
|||
|
||||
import 'package:crypto/crypto.dart';
|
||||
|
||||
import 'app_logger.dart';
|
||||
import '../config/app_logger.dart';
|
||||
import 'cli_responder.dart';
|
||||
import 'config.dart';
|
||||
import '../config/config.dart';
|
||||
import 'database.dart';
|
||||
import 'issue_event_source/base.dart';
|
||||
import 'issue_event_source/gitea_gosmee.dart';
|
||||
import 'issue_event_source/github_gosmee.dart';
|
||||
import 'issue_event_source/github_webhook_forward.dart';
|
||||
import 'issue_tracker_client.dart';
|
||||
import 'notifier.dart';
|
||||
import 'workspace_manager.dart';
|
||||
import '../issue_tracker/issue_event_source/base.dart';
|
||||
import '../issue_tracker/issue_event_source/gitea_gosmee.dart';
|
||||
import '../issue_tracker/issue_event_source/github_gosmee.dart';
|
||||
import '../issue_tracker/issue_event_source/github_webhook_forward.dart';
|
||||
import '../issue_tracker/issue_tracker_client.dart';
|
||||
import '../notifier/notifier.dart';
|
||||
import '../workspace/workspace_manager.dart';
|
||||
|
||||
class IssueAssistantApp {
|
||||
static final AppLogger _logger = AppLogger('code_work_spawner.app');
|
||||
|
|
@ -333,7 +333,7 @@ class IssueAssistantApp {
|
|||
stdout: rawStdout,
|
||||
stderr: rawStderr,
|
||||
);
|
||||
_log(
|
||||
_logError(
|
||||
'responder=${responder.id} issue=${project.key}#${issue.number} '
|
||||
'failed error=$error',
|
||||
);
|
||||
|
|
@ -4,9 +4,9 @@ import 'dart:io';
|
|||
import 'package:github/github.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../app_environment.dart';
|
||||
import '../config_schema.dart';
|
||||
import '../process_launcher.dart';
|
||||
import '../config/app_environment.dart';
|
||||
import '../config/config_schema.dart';
|
||||
import '../core/process_launcher.dart';
|
||||
import 'models.dart';
|
||||
|
||||
class IssueTrackerClient {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../database.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../../config/app_logger.dart';
|
||||
import '../../config/config.dart';
|
||||
import '../../core/database.dart';
|
||||
import '../client.dart';
|
||||
import '../models.dart';
|
||||
|
||||
typedef IssueEventBatchHandler = Future<void> Function(IssueEventBatch batch);
|
||||
|
||||
|
|
@ -4,10 +4,11 @@ import 'dart:io';
|
|||
|
||||
import 'package:github/github.dart';
|
||||
|
||||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import '../../config/app_logger.dart';
|
||||
import '../../config/config.dart';
|
||||
import '../client.dart';
|
||||
import '../models.dart';
|
||||
import '../../core/process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GiteaGosmeeIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -4,10 +4,11 @@ import 'dart:io';
|
|||
|
||||
import 'package:github/github.dart';
|
||||
|
||||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import '../../config/app_logger.dart';
|
||||
import '../../config/config.dart';
|
||||
import '../client.dart';
|
||||
import '../models.dart';
|
||||
import '../../core/process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GitHubGosmeeIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -2,10 +2,11 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import '../app_logger.dart';
|
||||
import '../config.dart';
|
||||
import '../issue_tracker_client.dart';
|
||||
import '../process_launcher.dart';
|
||||
import '../../config/app_logger.dart';
|
||||
import '../../config/config.dart';
|
||||
import '../client.dart';
|
||||
import '../models.dart';
|
||||
import '../../core/process_launcher.dart';
|
||||
import 'base.dart';
|
||||
|
||||
class GitHubWebhookForwardIssueEventSource implements IssueEventSource {
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export 'client.dart';
|
||||
export 'models.dart';
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export 'issue_tracker/client.dart';
|
||||
export 'issue_tracker/models.dart';
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'config.dart';
|
||||
import '../config/config.dart';
|
||||
|
||||
typedef NotificationLogger = void Function(String message);
|
||||
typedef DesktopNotificationCommandRunner =
|
||||
|
|
@ -3,8 +3,8 @@ import 'dart:io';
|
|||
import 'package:git/git.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'app_environment.dart';
|
||||
import 'config.dart';
|
||||
import '../config/app_environment.dart';
|
||||
import '../config/config.dart';
|
||||
|
||||
abstract interface class ScmClient {
|
||||
Future<String> createEphemeralWorkspace({
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import 'config.dart';
|
||||
import '../config/config.dart';
|
||||
import 'scm_client.dart';
|
||||
|
||||
class WorkspaceManager {
|
||||
|
|
@ -349,7 +349,7 @@
|
|||
"eval-driven-dev": {
|
||||
"source": "github/awesome-copilot",
|
||||
"sourceType": "github",
|
||||
"computedHash": "0b2cf0b8a57cf332eecb36ca00c6bb0636a3b0ecfc1f79f28b1fd4bab7f8d4fb"
|
||||
"computedHash": "1b5d8a5edb5c3540cd260e9ac71394077f814151b71fc343afb6ffd7e15c21b9"
|
||||
},
|
||||
"excalidraw-diagram-generator": {
|
||||
"source": "github/awesome-copilot",
|
||||
|
|
@ -944,7 +944,7 @@
|
|||
"worktrunk": {
|
||||
"source": "max-sixty/worktrunk",
|
||||
"sourceType": "github",
|
||||
"computedHash": "d480468e16c7de39a121e6e760788357bb178495f8912b05f05f55977468d72e"
|
||||
"computedHash": "e95e53e4ef26baddf9efb6b6c4e08276384edba9d7e9269d61a11f2c6871c6d5"
|
||||
},
|
||||
"write-coding-standards-from-file": {
|
||||
"source": "github/awesome-copilot",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import 'package:code_work_spawner/code_work_spawner.dart';
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../test_support.dart';
|
||||
|
||||
void main() {
|
||||
/// ```gherkin
|
||||
/// Feature: Init-config CLI
|
||||
|
|
@ -21,21 +23,16 @@ void main() {
|
|||
/// ```
|
||||
test('Show init-config in top-level help', () async {
|
||||
// Given the CLI is executed with the top-level help flag.
|
||||
final result = await Process.run(Platform.resolvedExecutable, [
|
||||
'run',
|
||||
'bin/code_work_spawner.dart',
|
||||
'--help',
|
||||
], workingDirectory: Directory.current.path);
|
||||
final result = await runCli(['--help']);
|
||||
|
||||
// When the process completes successfully.
|
||||
expect(result.exitCode, 0, reason: '${result.stderr}');
|
||||
expect(result.exitCode, 0, reason: result.stderr);
|
||||
|
||||
// Then stdout lists the init-config command in the available commands section.
|
||||
final stdoutText = result.stdout as String;
|
||||
expect(stdoutText, contains('Available commands:'));
|
||||
expect(stdoutText, contains('init-config'));
|
||||
expect(stdoutText, contains('--glab-command'));
|
||||
expect(stdoutText, contains('--gosmee-command'));
|
||||
expect(result.stdout, contains('Available commands:'));
|
||||
expect(result.stdout, contains('init-config'));
|
||||
expect(result.stdout, contains('--glab-command'));
|
||||
expect(result.stdout, contains('--gosmee-command'));
|
||||
});
|
||||
|
||||
/// ```gherkin
|
||||
|
|
@ -49,29 +46,27 @@ void main() {
|
|||
// Given the CLI is executed with the init-config command.
|
||||
final sandbox = await Directory.systemTemp.createTemp('cws-cli-stdout-');
|
||||
final configFile = File(p.join(sandbox.path, 'agent-orchestrator.yaml'));
|
||||
final result = await Process.run(Platform.resolvedExecutable, [
|
||||
'run',
|
||||
'bin/code_work_spawner.dart',
|
||||
'init-config',
|
||||
], workingDirectory: Directory.current.path);
|
||||
final result = await runCli(['init-config']);
|
||||
|
||||
// When the process completes successfully.
|
||||
expect(result.exitCode, 0, reason: '${result.stderr}');
|
||||
expect(result.exitCode, 0, reason: result.stderr);
|
||||
|
||||
// Then stdout contains the starter config content.
|
||||
final stdoutText = result.stdout as String;
|
||||
expect(stdoutText, contains('dataDir:'));
|
||||
expect(stdoutText, contains('# responders:'));
|
||||
expect(stdoutText, contains('# notifications:'));
|
||||
expect(stdoutText, contains(r'# webhookUrl: ${CWS_DISCORD_WEBHOOK}'));
|
||||
expect(stdoutText, contains('# - type: desktop'));
|
||||
expect(stdoutText, contains('issueTracker:'));
|
||||
expect(stdoutText, contains('provider: github'));
|
||||
expect(stdoutText, contains('scm:'));
|
||||
expect(stdoutText, contains('projects:'));
|
||||
expect(result.stdout, contains('dataDir:'));
|
||||
expect(result.stdout, contains('# responders:'));
|
||||
expect(result.stdout, contains('# notifications:'));
|
||||
expect(
|
||||
result.stdout,
|
||||
contains(r'# webhookUrl: ${CWS_DISCORD_WEBHOOK}'),
|
||||
);
|
||||
expect(result.stdout, contains('# - type: desktop'));
|
||||
expect(result.stdout, contains('issueTracker:'));
|
||||
expect(result.stdout, contains('provider: github'));
|
||||
expect(result.stdout, contains('scm:'));
|
||||
expect(result.stdout, contains('projects:'));
|
||||
|
||||
// And the printed config is valid when written to disk and loaded.
|
||||
await configFile.writeAsString(stdoutText);
|
||||
await configFile.writeAsString(result.stdout);
|
||||
final config = await AppConfig.load(configFile.path);
|
||||
expect(config.projects['sample'], isNotNull);
|
||||
});
|
||||
|
|
@ -90,31 +85,27 @@ void main() {
|
|||
await outputFile.writeAsString('original');
|
||||
|
||||
// When the CLI writes without the force flag and then with the force flag.
|
||||
final withoutForce = await Process.run(Platform.resolvedExecutable, [
|
||||
'run',
|
||||
'bin/code_work_spawner.dart',
|
||||
final withoutForce = await runCli([
|
||||
'init-config',
|
||||
'--output',
|
||||
outputFile.path,
|
||||
], workingDirectory: Directory.current.path);
|
||||
final withForce = await Process.run(Platform.resolvedExecutable, [
|
||||
'run',
|
||||
'bin/code_work_spawner.dart',
|
||||
]);
|
||||
final withForce = await runCli([
|
||||
'init-config',
|
||||
'--output',
|
||||
outputFile.path,
|
||||
'--force',
|
||||
], workingDirectory: Directory.current.path);
|
||||
]);
|
||||
|
||||
// Then the first run fails with an overwrite error.
|
||||
expect(withoutForce.exitCode, 2);
|
||||
expect(
|
||||
withoutForce.stderr as String,
|
||||
withoutForce.stderr,
|
||||
contains('Refusing to overwrite existing file'),
|
||||
);
|
||||
|
||||
// And the forced run replaces the file with a valid starter config.
|
||||
expect(withForce.exitCode, 0, reason: '${withForce.stderr}');
|
||||
expect(withForce.exitCode, 0, reason: withForce.stderr);
|
||||
final contents = await outputFile.readAsString();
|
||||
expect(contents, contains('defaults:'));
|
||||
expect(contents, contains('issueTracker:'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import '../../bin/code_work_spawner.dart' as _cliEntry;
|
||||
|
||||
/// Result of a [runCli] invocation.
|
||||
typedef CliResult = ({int exitCode, String stdout, String stderr});
|
||||
|
||||
/// Runs the CLI [main] in-process, capturing stdout, stderr, and [exitCode].
|
||||
///
|
||||
/// Uses [IOOverrides.runZoned] so every `dart:io` write inside the CLI lands
|
||||
/// in the capturing sinks rather than the real terminal. The global
|
||||
/// [exitCode] is reset to 0 both before and after the call so that
|
||||
/// individual tests do not pollute each other.
|
||||
Future<CliResult> runCli(List<String> args) async {
|
||||
final out = _CapturingStdout();
|
||||
final err = _CapturingStdout();
|
||||
exitCode = 0;
|
||||
try {
|
||||
await IOOverrides.runZoned<Future<void>>(
|
||||
() => _cliEntry.main(args),
|
||||
stdout: () => out,
|
||||
stderr: () => err,
|
||||
);
|
||||
} catch (_) {
|
||||
// Errors surface through exitCode / captured stderr, not as exceptions.
|
||||
}
|
||||
final result = (exitCode: exitCode, stdout: out.text, stderr: err.text);
|
||||
exitCode = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// A [Stdout] implementation that captures all written text into a buffer.
|
||||
class _CapturingStdout implements Stdout {
|
||||
final StringBuffer _buffer = StringBuffer();
|
||||
|
||||
String get text => _buffer.toString();
|
||||
|
||||
@override
|
||||
Encoding encoding = utf8;
|
||||
|
||||
@override
|
||||
bool get hasTerminal => false;
|
||||
|
||||
@override
|
||||
int get terminalColumns => throw UnsupportedError('no terminal');
|
||||
|
||||
@override
|
||||
int get terminalLines => throw UnsupportedError('no terminal');
|
||||
|
||||
@override
|
||||
bool get supportsAnsiEscapes => false;
|
||||
|
||||
@override
|
||||
IOSink get nonBlocking => this;
|
||||
|
||||
@override
|
||||
String lineTerminator = '\n';
|
||||
|
||||
@override
|
||||
void write(Object? object) => _buffer.write(object);
|
||||
|
||||
@override
|
||||
void writeln([Object? object = '']) => _buffer.writeln(object);
|
||||
|
||||
@override
|
||||
void writeAll(Iterable<dynamic> objects, [String separator = '']) =>
|
||||
_buffer.writeAll(objects, separator);
|
||||
|
||||
@override
|
||||
void writeCharCode(int charCode) => _buffer.writeCharCode(charCode);
|
||||
|
||||
@override
|
||||
void add(List<int> data) => _buffer.write(utf8.decode(data));
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace? stackTrace]) {}
|
||||
|
||||
@override
|
||||
Future<void> addStream(Stream<List<int>> stream) async {
|
||||
await for (final chunk in stream) {
|
||||
_buffer.write(utf8.decode(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> flush() => Future.value();
|
||||
|
||||
@override
|
||||
Future<void> close() => Future.value();
|
||||
|
||||
@override
|
||||
Future<void> get done => Future.value();
|
||||
}
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'script_utils.dart';
|
||||
|
||||
Future<void> writeFakeGhScript({
|
||||
required File ghScript,
|
||||
required List<Map<String, Object?>> issueListResponse,
|
||||
required Map<int, List<Map<String, Object?>>> commentResponses,
|
||||
Map<String, List<Map<String, Object?>>>? issueListResponsesByRepo,
|
||||
Map<String, Map<int, List<Map<String, Object?>>>>? commentResponsesByRepo,
|
||||
File? ghLog,
|
||||
int? postCommentIssueNumber,
|
||||
Set<int>? postCommentIssueNumbers,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedIssueListResponsesByRepo =
|
||||
issueListResponsesByRepo ??
|
||||
<String, List<Map<String, Object?>>>{'owner/sample': issueListResponse};
|
||||
final normalizedCommentResponsesByRepo =
|
||||
commentResponsesByRepo ??
|
||||
<String, Map<int, List<Map<String, Object?>>>>{
|
||||
'owner/sample': commentResponses,
|
||||
};
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
'owner/sample': {
|
||||
...?postCommentIssueNumbers,
|
||||
...?postCommentIssueNumber == null ? null : {postCommentIssueNumber},
|
||||
},
|
||||
};
|
||||
final searchItems =
|
||||
normalizedIssueListResponsesByRepo.entries
|
||||
.expand(
|
||||
(entry) => entry.value.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/${entry.key}',
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(growable: false)
|
||||
..sort((left, right) {
|
||||
final leftUpdatedAt = left['updated_at'] as String? ?? '';
|
||||
final rightUpdatedAt = right['updated_at'] as String? ?? '';
|
||||
return leftUpdatedAt.compareTo(rightUpdatedAt);
|
||||
});
|
||||
final openSearchItems = searchItems
|
||||
.where((item) => item['state'] == 'open')
|
||||
.toList(growable: false);
|
||||
final allSearchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
final openSearchResponse = jsonEncode({
|
||||
'total_count': openSearchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': openSearchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(r''' query=""''')
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' if [[ "$arg" == q=* ]]; then''')
|
||||
..writeln(r''' query="${arg#q=}"''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(r''' if [[ "$query" == *"state:open"* ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(openSearchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(r''' else''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(allSearchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
for (final entry in normalizedIssueListResponsesByRepo.entries) {
|
||||
final repo = entry.key;
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == repos/$repo/issues\\?* ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedCommentResponsesByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/${entry.key}/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 700,
|
||||
'html_url': 'https://example.test/comment/700',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeFlakySearchGhScript({
|
||||
required File ghScript,
|
||||
required File failureStateFile,
|
||||
required List<Map<String, Object?>> issueListResponse,
|
||||
required Map<int, List<Map<String, Object?>>> commentResponses,
|
||||
File? ghLog,
|
||||
}) async {
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/owner/sample',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(
|
||||
' if [[ ! -f "${bashScriptPath(failureStateFile.path)}" ]]; then',
|
||||
)
|
||||
..writeln(' touch "${bashScriptPath(failureStateFile.path)}"')
|
||||
..writeln(r''' echo "error connecting to api.github.com" >&2''')
|
||||
..writeln(
|
||||
r''' echo "check your internet connection or https://githubstatus.com" >&2''',
|
||||
)
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
for (final entry in commentResponses.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/owner/sample/issues/${entry.key}/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeWebhookForwardGhScript({
|
||||
required File ghScript,
|
||||
required String repo,
|
||||
required Map<String, Object?> issue,
|
||||
required List<Map<String, Object?>> comments,
|
||||
List<Map<String, Object?>> issueListResponse = const [],
|
||||
File? ghLog,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
}) async {
|
||||
final issueNumber = issue['number'] as int;
|
||||
final payload = {
|
||||
'action': 'created',
|
||||
'issue': issue,
|
||||
'comment': comments.last,
|
||||
'repository': {'full_name': repo},
|
||||
};
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(searchIssue) => <String, Object?>{
|
||||
...searchIssue,
|
||||
'repository_url': 'https://api.github.com/repos/$repo',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(comments))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 702,
|
||||
'html_url': 'https://example.test/comment/702',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "webhook" && "$2" == "forward" ]]; then''')
|
||||
..writeln(r''' url=""''')
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' if [[ "$arg" == --url=* ]]; then''')
|
||||
..writeln(r''' url="${arg#--url=}"''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(r''' if [[ -z "$url" ]]; then''')
|
||||
..writeln(r''' echo "missing --url" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' curl -sS -X POST \''')
|
||||
..writeln(r''' -H "Content-Type: application/json" \''')
|
||||
..writeln(r''' -H "X-GitHub-Event: issue_comment" \''')
|
||||
..writeln(r''' --data-binary @- "$url" <<'JSON' >/dev/null''')
|
||||
..writeln(jsonEncode(payload))
|
||||
..writeln('JSON')
|
||||
..writeln(r''' while true; do''')
|
||||
..writeln(r''' sleep 60''')
|
||||
..writeln(r''' done''')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeGitHubGosmeeGhScript({
|
||||
required File ghScript,
|
||||
required String repo,
|
||||
required int webhookId,
|
||||
required int issueNumber,
|
||||
required List<Map<String, Object?>> comments,
|
||||
List<Map<String, Object?>> issueListResponse = const [],
|
||||
File? ghLog,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
}) async {
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/$repo',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(comments))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln('if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks" ]]; then')
|
||||
..writeln(r''' active_flag=""''')
|
||||
..writeln(r''' for ((i = 1; i <= $#; i += 1)); do''')
|
||||
..writeln(r''' if [[ "${!i}" == "active=true" ]]; then''')
|
||||
..writeln(r''' prev_index=$((i - 1))''')
|
||||
..writeln(r''' active_flag="${!prev_index}"''')
|
||||
..writeln(r''' break''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(
|
||||
r''' if [[ "$active_flag" != "-F" && "$active_flag" != "--field" ]]; then''',
|
||||
)
|
||||
..writeln(
|
||||
r''' echo "expected active=true to be sent with -F/--field, got: $*" >&2''',
|
||||
)
|
||||
..writeln(r''' exit 1''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'id': webhookId}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 703,
|
||||
'html_url': 'https://example.test/comment/703',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks/$webhookId" ]]; then',
|
||||
)
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'script_utils.dart';
|
||||
|
||||
Future<void> writeFakeGlabScript({
|
||||
required File glabScript,
|
||||
required Map<String, List<Map<String, Object?>>> issueListResponsesByRepo,
|
||||
required Map<String, Map<int, List<Map<String, Object?>>>>
|
||||
commentResponsesByRepo,
|
||||
File? glabLog,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
for (final entry in issueListResponsesByRepo.entries)
|
||||
entry.key: entry.value
|
||||
.map((issue) => issue['iid'])
|
||||
.whereType<int>()
|
||||
.toSet(),
|
||||
};
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (glabLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(glabLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'username': viewerLogin ?? 'glab-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final entry in issueListResponsesByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(entry.key);
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "api" && "$4" == "projects/'''
|
||||
'$repo'
|
||||
r'''/issues?state=opened"* ]]; then''',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in commentResponsesByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(repoEntry.key);
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"projects/$repo/issues/${entry.key}/notes?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(repoEntry.key);
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"projects/$repo/issues/$issueNumber/notes" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 901,
|
||||
'body': 'posted',
|
||||
'noteable_url':
|
||||
'https://gitlab.example.test/group/subgroup/sample/issues/$issueNumber',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected glab args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await glabScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(glabScript);
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'script_utils.dart';
|
||||
|
||||
Future<void> writeGosmeeForwardScript({
|
||||
required File gosmeeScript,
|
||||
required String publicUrl,
|
||||
required Map<String, Object?> payload,
|
||||
File? gosmeeLog,
|
||||
}) async {
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (gosmeeLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(gosmeeLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "--output" && "$2" == "json" && "$3" == "client" && "$4" == "--new-url" ]]; then''',
|
||||
)
|
||||
..writeln(
|
||||
r''' printf '%s\n' '''
|
||||
"'$publicUrl'",
|
||||
)
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "client" ]]; then''')
|
||||
..writeln(r''' url="$2"''')
|
||||
..writeln(r''' target="$3"''')
|
||||
..writeln(
|
||||
r''' if [[ "$url" != '''
|
||||
"'$publicUrl'"
|
||||
r''' ]]; then''',
|
||||
)
|
||||
..writeln(r''' echo "unexpected gosmee url: $url" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' curl -sS -X POST \''')
|
||||
..writeln(r''' -H "Content-Type: application/json" \''')
|
||||
..writeln(r''' -H "X-Gitea-Event: issue_comment" \''')
|
||||
..writeln(r''' --data-binary @- "$target" <<'JSON' >/dev/null''')
|
||||
..writeln(jsonEncode(payload))
|
||||
..writeln('JSON')
|
||||
..writeln(r''' while true; do''')
|
||||
..writeln(r''' sleep 60''')
|
||||
..writeln(r''' done''')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gosmee args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await gosmeeScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(gosmeeScript);
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'script_utils.dart';
|
||||
|
||||
Future<void> writeResponderScript(
|
||||
File responderScript,
|
||||
Map<String, Object?> response, {
|
||||
String stderr = '',
|
||||
int exitCode = 0,
|
||||
}) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cat >/dev/null
|
||||
${stderr.isEmpty ? '' : "printf '%s\\n' ${jsonEncode(stderr)} >&2"}
|
||||
cat <<'JSON'
|
||||
${jsonEncode(response)}
|
||||
JSON
|
||||
exit $exitCode
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeJsonlResponderScript(
|
||||
File responderScript,
|
||||
Map<String, Object?> response,
|
||||
) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cat >/dev/null
|
||||
cat <<'JSON'
|
||||
{"type":"thread.started","thread_id":"test-thread"}
|
||||
{"type":"turn.started"}
|
||||
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":${jsonEncode(jsonEncode(response))}}}
|
||||
{"type":"turn.completed"}
|
||||
JSON
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeDelayedLoggingResponderScript(
|
||||
File responderScript, {
|
||||
required File eventLog,
|
||||
required Duration delay,
|
||||
required Map<String, Object?> response,
|
||||
}) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
timestamp_ms() {
|
||||
python3 - <<'PY'
|
||||
import time
|
||||
print(int(time.time() * 1000))
|
||||
PY
|
||||
}
|
||||
printf 'start:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat >/dev/null
|
||||
sleep ${delay.inSeconds}
|
||||
printf 'end:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat <<'JSON'
|
||||
${jsonEncode(response)}
|
||||
JSON
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'script_utils.dart';
|
||||
|
||||
Future<void> writeFakeTeaScript({
|
||||
required File teaScript,
|
||||
required Map<String, List<Map<String, Object?>>> issueListResponsesByRepo,
|
||||
required Map<String, Map<int, List<Map<String, Object?>>>>
|
||||
commentResponsesByRepo,
|
||||
File? teaLog,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
for (final entry in issueListResponsesByRepo.entries)
|
||||
entry.key: entry.value
|
||||
.map((issue) => issue['index'] ?? issue['number'])
|
||||
.whereType<int>()
|
||||
.toSet(),
|
||||
};
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (teaLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(teaLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'tea-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final entry in issueListResponsesByRepo.entries) {
|
||||
final repo = entry.key;
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "api" && "$4" == "repos/'''
|
||||
'$repo'
|
||||
r'''/issues?state=open"* ]]; then''',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in commentResponsesByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/${entry.key}/comments?limit=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 701,
|
||||
'url': 'https://gitea.example.test/comment/701',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected tea args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await teaScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(teaScript);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:git/git.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<void> initGitRepo(Directory directory) async {
|
||||
await GitDir.init(directory.path, allowContent: true);
|
||||
await runGit([
|
||||
'config',
|
||||
'user.email',
|
||||
'test@example.com',
|
||||
], workingDirectory: directory.path);
|
||||
await runGit([
|
||||
'config',
|
||||
'user.name',
|
||||
'Test User',
|
||||
], workingDirectory: directory.path);
|
||||
await File(p.join(directory.path, 'README.md')).writeAsString('repo');
|
||||
await runGit(['add', '.'], workingDirectory: directory.path);
|
||||
await runGit(['commit', '-m', 'init'], workingDirectory: directory.path);
|
||||
}
|
||||
|
||||
const Set<String> gitContextEnvironmentKeys = {
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
||||
'GIT_COMMON_DIR',
|
||||
'GIT_DIR',
|
||||
'GIT_IMPLICIT_WORK_TREE',
|
||||
'GIT_INDEX_FILE',
|
||||
'GIT_OBJECT_DIRECTORY',
|
||||
'GIT_PREFIX',
|
||||
'GIT_WORK_TREE',
|
||||
};
|
||||
|
||||
Future<void> runGit(
|
||||
List<String> arguments, {
|
||||
required String workingDirectory,
|
||||
}) async {
|
||||
final environment = Map<String, String>.from(Platform.environment)
|
||||
..removeWhere((key, _) => gitContextEnvironmentKeys.contains(key));
|
||||
final result = await Process.run(
|
||||
'git',
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'git',
|
||||
arguments,
|
||||
'${result.stdout}\n${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import 'dart:io';
|
||||
|
||||
Future<void> makeScriptExecutable(File script) async {
|
||||
if (Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await Process.run('chmod', ['+x', script.path]);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'chmod',
|
||||
['+x', script.path],
|
||||
'${result.stdout}\n${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String bashScriptPath(String hostPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return hostPath;
|
||||
}
|
||||
|
||||
final normalized = hostPath.replaceAll('\\', '/');
|
||||
final drivePrefixMatch = RegExp(r'^([A-Za-z]):/(.*)$').firstMatch(normalized);
|
||||
if (drivePrefixMatch == null) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
final driveLetter = drivePrefixMatch.group(1)!.toLowerCase();
|
||||
final rest = drivePrefixMatch.group(2)!;
|
||||
return '/mnt/$driveLetter/$rest';
|
||||
}
|
||||
|
||||
String hostPathFromScript(String scriptPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
final trimmed = scriptPath.trim();
|
||||
final mntPathMatch = RegExp(r'^/mnt/([a-zA-Z])/(.*)$').firstMatch(trimmed);
|
||||
if (mntPathMatch == null) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
final driveLetter = mntPathMatch.group(1)!.toUpperCase();
|
||||
final rest = mntPathMatch.group(2)!.replaceAll('/', '\\');
|
||||
return '$driveLetter:\\$rest';
|
||||
}
|
||||
|
|
@ -1,983 +1,8 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:git/git.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
Future<void> makeScriptExecutable(File script) async {
|
||||
if (Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await Process.run('chmod', ['+x', script.path]);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'chmod',
|
||||
['+x', script.path],
|
||||
'${result.stdout}\n${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String bashScriptPath(String hostPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return hostPath;
|
||||
}
|
||||
|
||||
final normalized = hostPath.replaceAll('\\', '/');
|
||||
final drivePrefixMatch = RegExp(r'^([A-Za-z]):/(.*)$').firstMatch(normalized);
|
||||
if (drivePrefixMatch == null) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
final driveLetter = drivePrefixMatch.group(1)!.toLowerCase();
|
||||
final rest = drivePrefixMatch.group(2)!;
|
||||
return '/mnt/$driveLetter/$rest';
|
||||
}
|
||||
|
||||
String hostPathFromScript(String scriptPath) {
|
||||
if (!Platform.isWindows) {
|
||||
return scriptPath;
|
||||
}
|
||||
|
||||
final trimmed = scriptPath.trim();
|
||||
final mntPathMatch = RegExp(r'^/mnt/([a-zA-Z])/(.*)$').firstMatch(trimmed);
|
||||
if (mntPathMatch == null) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
final driveLetter = mntPathMatch.group(1)!.toUpperCase();
|
||||
final rest = mntPathMatch.group(2)!.replaceAll('/', '\\');
|
||||
return '$driveLetter:\\$rest';
|
||||
}
|
||||
|
||||
Future<void> writeFakeGhScript({
|
||||
required File ghScript,
|
||||
required List<Map<String, Object?>> issueListResponse,
|
||||
required Map<int, List<Map<String, Object?>>> commentResponses,
|
||||
Map<String, List<Map<String, Object?>>>? issueListResponsesByRepo,
|
||||
Map<String, Map<int, List<Map<String, Object?>>>>? commentResponsesByRepo,
|
||||
File? ghLog,
|
||||
int? postCommentIssueNumber,
|
||||
Set<int>? postCommentIssueNumbers,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedIssueListResponsesByRepo =
|
||||
issueListResponsesByRepo ??
|
||||
<String, List<Map<String, Object?>>>{'owner/sample': issueListResponse};
|
||||
final normalizedCommentResponsesByRepo =
|
||||
commentResponsesByRepo ??
|
||||
<String, Map<int, List<Map<String, Object?>>>>{
|
||||
'owner/sample': commentResponses,
|
||||
};
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
'owner/sample': {
|
||||
...?postCommentIssueNumbers,
|
||||
...?postCommentIssueNumber == null ? null : {postCommentIssueNumber},
|
||||
},
|
||||
};
|
||||
final searchItems =
|
||||
normalizedIssueListResponsesByRepo.entries
|
||||
.expand(
|
||||
(entry) => entry.value.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/${entry.key}',
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(growable: false)
|
||||
..sort((left, right) {
|
||||
final leftUpdatedAt = left['updated_at'] as String? ?? '';
|
||||
final rightUpdatedAt = right['updated_at'] as String? ?? '';
|
||||
return leftUpdatedAt.compareTo(rightUpdatedAt);
|
||||
});
|
||||
final openSearchItems = searchItems
|
||||
.where((item) => item['state'] == 'open')
|
||||
.toList(growable: false);
|
||||
final allSearchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
final openSearchResponse = jsonEncode({
|
||||
'total_count': openSearchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': openSearchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(r''' query=""''')
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' if [[ "$arg" == q=* ]]; then''')
|
||||
..writeln(r''' query="${arg#q=}"''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(r''' if [[ "$query" == *"state:open"* ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(openSearchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(r''' else''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(allSearchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
for (final entry in normalizedIssueListResponsesByRepo.entries) {
|
||||
final repo = entry.key;
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == repos/$repo/issues\\?* ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedCommentResponsesByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/${entry.key}/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 700,
|
||||
'html_url': 'https://example.test/comment/700',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeFlakySearchGhScript({
|
||||
required File ghScript,
|
||||
required File failureStateFile,
|
||||
required List<Map<String, Object?>> issueListResponse,
|
||||
required Map<int, List<Map<String, Object?>>> commentResponses,
|
||||
File? ghLog,
|
||||
}) async {
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/owner/sample',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(
|
||||
' if [[ ! -f "${bashScriptPath(failureStateFile.path)}" ]]; then',
|
||||
)
|
||||
..writeln(' touch "${bashScriptPath(failureStateFile.path)}"')
|
||||
..writeln(r''' echo "error connecting to api.github.com" >&2''')
|
||||
..writeln(
|
||||
r''' echo "check your internet connection or https://githubstatus.com" >&2''',
|
||||
)
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
for (final entry in commentResponses.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/owner/sample/issues/${entry.key}/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeFakeTeaScript({
|
||||
required File teaScript,
|
||||
required Map<String, List<Map<String, Object?>>> issueListResponsesByRepo,
|
||||
required Map<String, Map<int, List<Map<String, Object?>>>>
|
||||
commentResponsesByRepo,
|
||||
File? teaLog,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
for (final entry in issueListResponsesByRepo.entries)
|
||||
entry.key: entry.value
|
||||
.map((issue) => issue['index'] ?? issue['number'])
|
||||
.whereType<int>()
|
||||
.toSet(),
|
||||
};
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (teaLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(teaLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'tea-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final entry in issueListResponsesByRepo.entries) {
|
||||
final repo = entry.key;
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "api" && "$4" == "repos/'''
|
||||
'$repo'
|
||||
r'''/issues?state=open"* ]]; then''',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in commentResponsesByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/${entry.key}/comments?limit=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = repoEntry.key;
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 701,
|
||||
'url': 'https://gitea.example.test/comment/701',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected tea args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await teaScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(teaScript);
|
||||
}
|
||||
|
||||
Future<void> writeFakeGlabScript({
|
||||
required File glabScript,
|
||||
required Map<String, List<Map<String, Object?>>> issueListResponsesByRepo,
|
||||
required Map<String, Map<int, List<Map<String, Object?>>>>
|
||||
commentResponsesByRepo,
|
||||
File? glabLog,
|
||||
Map<String, Set<int>>? postCommentIssueNumbersByRepo,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
bool failViewerLookup = false,
|
||||
}) async {
|
||||
final normalizedPostCommentIssueNumbersByRepo =
|
||||
postCommentIssueNumbersByRepo ??
|
||||
<String, Set<int>>{
|
||||
for (final entry in issueListResponsesByRepo.entries)
|
||||
entry.key: entry.value
|
||||
.map((issue) => issue['iid'])
|
||||
.whereType<int>()
|
||||
.toSet(),
|
||||
};
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (glabLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(glabLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
if (failViewerLookup) {
|
||||
buffer
|
||||
..writeln(r''' echo "viewer lookup failed" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln('fi');
|
||||
} else {
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'username': viewerLogin ?? 'glab-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final entry in issueListResponsesByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(entry.key);
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "api" && "$4" == "projects/'''
|
||||
'$repo'
|
||||
r'''/issues?state=opened"* ]]; then''',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
|
||||
for (final repoEntry in commentResponsesByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(repoEntry.key);
|
||||
for (final entry in repoEntry.value.entries) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"projects/$repo/issues/${entry.key}/notes?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(entry.value))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
for (final repoEntry in normalizedPostCommentIssueNumbersByRepo.entries) {
|
||||
final repo = Uri.encodeComponent(repoEntry.key);
|
||||
for (final issueNumber in repoEntry.value) {
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"projects/$repo/issues/$issueNumber/notes" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 901,
|
||||
'body': 'posted',
|
||||
'noteable_url':
|
||||
'https://gitlab.example.test/group/subgroup/sample/issues/$issueNumber',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
}
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected glab args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await glabScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(glabScript);
|
||||
}
|
||||
|
||||
Future<void> writeResponderScript(
|
||||
File responderScript,
|
||||
Map<String, Object?> response, {
|
||||
String stderr = '',
|
||||
int exitCode = 0,
|
||||
}) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cat >/dev/null
|
||||
${stderr.isEmpty ? '' : "printf '%s\\n' ${jsonEncode(stderr)} >&2"}
|
||||
cat <<'JSON'
|
||||
${jsonEncode(response)}
|
||||
JSON
|
||||
exit $exitCode
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeJsonlResponderScript(
|
||||
File responderScript,
|
||||
Map<String, Object?> response,
|
||||
) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cat >/dev/null
|
||||
cat <<'JSON'
|
||||
{"type":"thread.started","thread_id":"test-thread"}
|
||||
{"type":"turn.started"}
|
||||
{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":${jsonEncode(jsonEncode(response))}}}
|
||||
{"type":"turn.completed"}
|
||||
JSON
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeDelayedLoggingResponderScript(
|
||||
File responderScript, {
|
||||
required File eventLog,
|
||||
required Duration delay,
|
||||
required Map<String, Object?> response,
|
||||
}) async {
|
||||
await responderScript.writeAsString('''#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
timestamp_ms() {
|
||||
python3 - <<'PY'
|
||||
import time
|
||||
print(int(time.time() * 1000))
|
||||
PY
|
||||
}
|
||||
printf 'start:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat >/dev/null
|
||||
sleep ${delay.inSeconds}
|
||||
printf 'end:%s:%s\n' "\$CWS_ISSUE_NUMBER" "\$(timestamp_ms)" >> "${bashScriptPath(eventLog.path)}"
|
||||
cat <<'JSON'
|
||||
${jsonEncode(response)}
|
||||
JSON
|
||||
''');
|
||||
await makeScriptExecutable(responderScript);
|
||||
}
|
||||
|
||||
Future<void> writeWebhookForwardGhScript({
|
||||
required File ghScript,
|
||||
required String repo,
|
||||
required Map<String, Object?> issue,
|
||||
required List<Map<String, Object?>> comments,
|
||||
List<Map<String, Object?>> issueListResponse = const [],
|
||||
File? ghLog,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
}) async {
|
||||
final issueNumber = issue['number'] as int;
|
||||
final payload = {
|
||||
'action': 'created',
|
||||
'issue': issue,
|
||||
'comment': comments.last,
|
||||
'repository': {'full_name': repo},
|
||||
};
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(searchIssue) => <String, Object?>{
|
||||
...searchIssue,
|
||||
'repository_url': 'https://api.github.com/repos/$repo',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(comments))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 702,
|
||||
'html_url': 'https://example.test/comment/702',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "webhook" && "$2" == "forward" ]]; then''')
|
||||
..writeln(r''' url=""''')
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' if [[ "$arg" == --url=* ]]; then''')
|
||||
..writeln(r''' url="${arg#--url=}"''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(r''' if [[ -z "$url" ]]; then''')
|
||||
..writeln(r''' echo "missing --url" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' curl -sS -X POST \''')
|
||||
..writeln(r''' -H "Content-Type: application/json" \''')
|
||||
..writeln(r''' -H "X-GitHub-Event: issue_comment" \''')
|
||||
..writeln(r''' --data-binary @- "$url" <<'JSON' >/dev/null''')
|
||||
..writeln(jsonEncode(payload))
|
||||
..writeln('JSON')
|
||||
..writeln(r''' while true; do''')
|
||||
..writeln(r''' sleep 60''')
|
||||
..writeln(r''' done''')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeGitHubGosmeeGhScript({
|
||||
required File ghScript,
|
||||
required String repo,
|
||||
required int webhookId,
|
||||
required int issueNumber,
|
||||
required List<Map<String, Object?>> comments,
|
||||
List<Map<String, Object?>> issueListResponse = const [],
|
||||
File? ghLog,
|
||||
File? postedBodyFile,
|
||||
String? viewerLogin,
|
||||
}) async {
|
||||
final searchItems = issueListResponse
|
||||
.map(
|
||||
(issue) => <String, Object?>{
|
||||
...issue,
|
||||
'repository_url': 'https://api.github.com/repos/$repo',
|
||||
},
|
||||
)
|
||||
.toList(growable: false);
|
||||
final searchResponse = jsonEncode({
|
||||
'total_count': searchItems.length,
|
||||
'incomplete_results': false,
|
||||
'items': searchItems,
|
||||
});
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (ghLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(ghLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer.writeln(r'''if [[ "$1" == "api" && "$2" == "user" ]]; then''');
|
||||
buffer
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'login': viewerLogin ?? 'test-user'}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "api" && "$4" == "search/issues" ]]; then''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(searchResponse)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments?per_page=100" ]]; then',
|
||||
)
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode(comments))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln('if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks" ]]; then')
|
||||
..writeln(r''' active_flag=""''')
|
||||
..writeln(r''' for ((i = 1; i <= $#; i += 1)); do''')
|
||||
..writeln(r''' if [[ "${!i}" == "active=true" ]]; then''')
|
||||
..writeln(r''' prev_index=$((i - 1))''')
|
||||
..writeln(r''' active_flag="${!prev_index}"''')
|
||||
..writeln(r''' break''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' done''')
|
||||
..writeln(
|
||||
r''' if [[ "$active_flag" != "-F" && "$active_flag" != "--field" ]]; then''',
|
||||
)
|
||||
..writeln(
|
||||
r''' echo "expected active=true to be sent with -F/--field, got: $*" >&2''',
|
||||
)
|
||||
..writeln(r''' exit 1''')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(jsonEncode({'id': webhookId}))
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == '
|
||||
'"repos/$repo/issues/$issueNumber/comments" ]]; then',
|
||||
)
|
||||
..writeln(r''' for arg in "$@"; do''')
|
||||
..writeln(r''' :''');
|
||||
if (postedBodyFile != null) {
|
||||
buffer
|
||||
..writeln(r''' if [[ "$arg" == body=* ]]; then''')
|
||||
..writeln(
|
||||
r''' printf '%s' "${arg#body=}" > '''
|
||||
'"${bashScriptPath(postedBodyFile.path)}"',
|
||||
)
|
||||
..writeln(r''' fi''');
|
||||
}
|
||||
buffer
|
||||
..writeln(r''' done''')
|
||||
..writeln(" cat <<'JSON'")
|
||||
..writeln(
|
||||
jsonEncode({
|
||||
'id': 703,
|
||||
'html_url': 'https://example.test/comment/703',
|
||||
'body': 'posted',
|
||||
}),
|
||||
)
|
||||
..writeln('JSON')
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
'if [[ "\$1" == "api" && "\$4" == "repos/$repo/hooks/$webhookId" ]]; then',
|
||||
)
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gh args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await ghScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(ghScript);
|
||||
}
|
||||
|
||||
Future<void> writeGosmeeForwardScript({
|
||||
required File gosmeeScript,
|
||||
required String publicUrl,
|
||||
required Map<String, Object?> payload,
|
||||
File? gosmeeLog,
|
||||
}) async {
|
||||
final buffer = StringBuffer()
|
||||
..writeln('#!/usr/bin/env bash')
|
||||
..writeln('set -euo pipefail');
|
||||
|
||||
if (gosmeeLog != null) {
|
||||
buffer.writeln(
|
||||
r'''printf '%s\n' "$*" >> '''
|
||||
'"${bashScriptPath(gosmeeLog.path)}"',
|
||||
);
|
||||
}
|
||||
|
||||
buffer
|
||||
..writeln(
|
||||
r'''if [[ "$1" == "--output" && "$2" == "json" && "$3" == "client" && "$4" == "--new-url" ]]; then''',
|
||||
)
|
||||
..writeln(
|
||||
r''' printf '%s\n' '''
|
||||
"'$publicUrl'",
|
||||
)
|
||||
..writeln(' exit 0')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''if [[ "$1" == "client" ]]; then''')
|
||||
..writeln(r''' url="$2"''')
|
||||
..writeln(r''' target="$3"''')
|
||||
..writeln(
|
||||
r''' if [[ "$url" != '''
|
||||
"'$publicUrl'"
|
||||
r''' ]]; then''',
|
||||
)
|
||||
..writeln(r''' echo "unexpected gosmee url: $url" >&2''')
|
||||
..writeln(' exit 1')
|
||||
..writeln(r''' fi''')
|
||||
..writeln(r''' curl -sS -X POST \''')
|
||||
..writeln(r''' -H "Content-Type: application/json" \''')
|
||||
..writeln(r''' -H "X-Gitea-Event: issue_comment" \''')
|
||||
..writeln(r''' --data-binary @- "$target" <<'JSON' >/dev/null''')
|
||||
..writeln(jsonEncode(payload))
|
||||
..writeln('JSON')
|
||||
..writeln(r''' while true; do''')
|
||||
..writeln(r''' sleep 60''')
|
||||
..writeln(r''' done''')
|
||||
..writeln('fi');
|
||||
|
||||
buffer
|
||||
..writeln(r'''echo "unexpected gosmee args: $*" >&2''')
|
||||
..writeln('exit 1');
|
||||
|
||||
await gosmeeScript.writeAsString(buffer.toString());
|
||||
await makeScriptExecutable(gosmeeScript);
|
||||
}
|
||||
|
||||
Future<void> initGitRepo(Directory directory) async {
|
||||
await GitDir.init(directory.path, allowContent: true);
|
||||
await runGit([
|
||||
'config',
|
||||
'user.email',
|
||||
'test@example.com',
|
||||
], workingDirectory: directory.path);
|
||||
await runGit([
|
||||
'config',
|
||||
'user.name',
|
||||
'Test User',
|
||||
], workingDirectory: directory.path);
|
||||
await File(p.join(directory.path, 'README.md')).writeAsString('repo');
|
||||
await runGit(['add', '.'], workingDirectory: directory.path);
|
||||
await runGit(['commit', '-m', 'init'], workingDirectory: directory.path);
|
||||
}
|
||||
|
||||
const Set<String> gitContextEnvironmentKeys = {
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
||||
'GIT_COMMON_DIR',
|
||||
'GIT_DIR',
|
||||
'GIT_IMPLICIT_WORK_TREE',
|
||||
'GIT_INDEX_FILE',
|
||||
'GIT_OBJECT_DIRECTORY',
|
||||
'GIT_PREFIX',
|
||||
'GIT_WORK_TREE',
|
||||
};
|
||||
|
||||
Future<void> runGit(
|
||||
List<String> arguments, {
|
||||
required String workingDirectory,
|
||||
}) async {
|
||||
final environment = Map<String, String>.from(Platform.environment)
|
||||
..removeWhere((key, _) => gitContextEnvironmentKeys.contains(key));
|
||||
final result = await Process.run(
|
||||
'git',
|
||||
arguments,
|
||||
workingDirectory: workingDirectory,
|
||||
environment: environment,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
throw ProcessException(
|
||||
'git',
|
||||
arguments,
|
||||
'${result.stdout}\n${result.stderr}',
|
||||
result.exitCode,
|
||||
);
|
||||
}
|
||||
}
|
||||
export 'support/script_utils.dart';
|
||||
export 'support/fake_gh.dart';
|
||||
export 'support/fake_tea.dart';
|
||||
export 'support/fake_glab.dart';
|
||||
export 'support/fake_responders.dart';
|
||||
export 'support/fake_gosmee.dart';
|
||||
export 'support/git_utils.dart';
|
||||
export 'support/cli_utils.dart';
|
||||
|
|
|
|||
Loading…
Reference in New Issue