oto/lib/oto/commands/command.dart
2025-05-26 14:26:38 +09:00

543 lines
17 KiB
Dart

// ignore_for_file: constant_identifier_names, depend_on_referenced_packages, avoid_init_to_null
import 'dart:async';
import 'dart:io';
import 'package:dart_framework/platform/process.dart';
import 'package:dart_framework/utils/path.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:oto_cli/oto/application.dart';
import 'package:oto_cli/oto/commands/build/app_data_creator.dart';
import 'package:oto_cli/oto/commands/build/build_dart.dart';
import 'package:oto_cli/oto/commands/build/build_dart_compile.dart';
import 'package:oto_cli/oto/commands/build/build_dot_net.dart';
import 'package:oto_cli/oto/commands/build/build_flutter.dart';
import 'package:oto_cli/oto/commands/build/build_ios.dart';
import 'package:oto_cli/oto/commands/build/build_msbuild.dart';
import 'package:oto_cli/oto/commands/build/testflight_ios.dart';
import 'package:oto_cli/oto/commands/build/xcodeproj.dart';
import 'package:oto_cli/oto/commands/file/directory.dart';
import 'package:oto_cli/oto/commands/file/file.dart';
import 'package:oto_cli/oto/commands/file/file_diff_check.dart';
import 'package:oto_cli/oto/commands/ftp/download.dart';
import 'package:oto_cli/oto/commands/git/git.dart';
import 'package:oto_cli/oto/commands/aws/awscli.dart';
import 'package:oto_cli/oto/commands/docker/docker.dart';
import 'package:oto_cli/oto/commands/git/git_hub.dart';
import 'package:oto_cli/oto/commands/gradle/gradle.dart';
import 'package:oto_cli/oto/commands/jira/jira.dart';
import 'package:oto_cli/oto/commands/process/process.dart';
import 'package:oto_cli/oto/commands/proto/protobuf.dart';
import 'package:oto_cli/oto/commands/util/json.dart';
import 'package:oto_cli/oto/commands/util/print.dart';
import 'package:oto_cli/oto/commands/util/set_value.dart';
import 'package:oto_cli/oto/commands/util/smb_authorize.dart';
import 'package:oto_cli/oto/commands/util/string_util.dart';
import 'package:oto_cli/oto/commands/util/timer.dart';
import 'package:oto_cli/oto/commands/web/web.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:oto_cli/oto/data/jenkins_data.dart';
import 'package:oto_cli/oto/commands/file/delete.dart';
import 'package:oto_cli/oto/commands/file/rename.dart';
import 'package:oto_cli/oto/commands/shell/shell.dart';
import 'package:oto_cli/oto/commands/file/copy.dart';
import 'package:oto_cli/oto/commands/util/publish_ios.dart';
import 'package:oto_cli/oto/commands/util/slack.dart';
import 'package:oto_cli/oto/commands/util/zip.dart';
import 'package:oto_cli/oto/commands/ftp/upload.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe_handle.dart';
enum CommandType {
SimpleCommand,
Print,
SetValue,
CreateAppData,
BuildDart,
BuildFlutter,
BuildDartCompile,
BuildDotNet,
BuildiOS,
TestflightUpload,
TestflightStatusCheck,
TestflightDistribute,
XcodeprojAddFile,
BuildMSBuild,
CodeSign,
CodeSignVerify,
ProductBuild,
Notarize,
ArchiveiOS,
ExportiOS,
PublishiOS,
Upload,
Download,
Copy,
Rename,
Delete,
FileDiffCheck,
FileRead,
FileWrite,
Files,
FileInfo,
DirectoryCreate,
Slack,
SlackBuild,
StringSub,
StringReplace,
StringReplacePattern,
StringIndex,
URLInfo,
Jira,
Zip,
Shell,
ShellFile,
Git,
GitCommit,
GitPush,
GitCheckout,
GitRev,
GitCount,
GitReset,
GitStashPush,
GitStashApply,
GitHub,
GitHubPullRequestCreate,
GitHubPullRequestList,
GitHubPullRequestClose,
Protobuf,
JsonReader,
JsonReaderFile,
JsonWriterFile,
AwsCli,
Docker,
Gradle,
WebRequest,
WebFile,
ProcessPortUse,
ProcessRun,
ProcessKill,
SMBAuth,
Delay
}
abstract class Command {
static final Map<CommandType, Command Function()> _commandMap = {
CommandType.SimpleCommand: () => SimpleCommand(),
CommandType.Print: () => PrintCommand(),
CommandType.SetValue: () => SetValueCommand(),
CommandType.CreateAppData: () => CreateAppData(),
CommandType.BuildDart: () => BuildDart(),
CommandType.BuildFlutter: () => BuildFlutter(),
CommandType.BuildDartCompile: () => BuildDartCompile(),
CommandType.BuildDotNet: () => BuildDotNet(),
CommandType.BuildiOS: () => BuildiOS(),
CommandType.TestflightUpload: () => TestflightUpload(),
CommandType.TestflightStatusCheck: () => TestflightStatusCheck(),
CommandType.TestflightDistribute: () => TestflightDistribute(),
CommandType.XcodeprojAddFile: () => XcodeprojAddFile(),
CommandType.BuildMSBuild: () => BuildMSBuild(),
CommandType.CodeSign: () => CodeSign(),
CommandType.CodeSignVerify: () => CodeSignVerify(),
CommandType.ProductBuild: () => ProductBuild(),
CommandType.Notarize: () => Notarize(),
CommandType.ArchiveiOS: () => ArchiveiOS(),
CommandType.ExportiOS: () => ExportiOS(),
CommandType.PublishiOS: () => PublishiOS(),
CommandType.Upload: () => Upload(),
CommandType.Download: () => Download(),
CommandType.Copy: () => Copy(),
CommandType.Rename: () => Rename(),
CommandType.Delete: () => Delete(),
CommandType.FileDiffCheck: () => FileDiffCheck(),
CommandType.FileRead: () => FileRead(),
CommandType.FileWrite: () => FileWrite(),
CommandType.Files: () => Files(),
CommandType.FileInfo: () => FileInfo(),
CommandType.DirectoryCreate: () => DirectoryCreate(),
CommandType.Slack: () => Slack(),
CommandType.SlackBuild: () => SlackBuild(),
CommandType.StringSub: () => StringSub(),
CommandType.StringReplace: () => StringReplace(),
CommandType.StringReplacePattern: () => StringReplacePattern(),
CommandType.StringIndex: () => StringIndex(),
CommandType.URLInfo: () => URLInfo(),
CommandType.Jira: () => Jira(),
CommandType.Zip: () => Zip(),
CommandType.Shell: () => Shell(),
CommandType.ShellFile: () => ShellFile(),
CommandType.Git: () => Git(),
CommandType.GitCommit: () => GitCommit(),
CommandType.GitPush: () => GitPush(),
CommandType.GitCheckout: () => GitCheckout(),
CommandType.GitRev: () => GitRev(),
CommandType.GitCount: () => GitCount(),
CommandType.GitReset: () => GitReset(),
CommandType.GitStashPush: () => GitStashPush(),
CommandType.GitStashApply: () => GitStashApply(),
CommandType.GitHub: () => GitHub(),
CommandType.GitHubPullRequestCreate: () => GitHubPullRequestCreate(),
CommandType.GitHubPullRequestList: () => GitHubPullRequestList(),
CommandType.GitHubPullRequestClose: () => GitHubPullRequestClose(),
CommandType.Protobuf: () => Protobuf(),
CommandType.JsonReader: () => JsonReader(),
CommandType.JsonReaderFile: () => JsonReaderFile(),
CommandType.JsonWriterFile: () => JsonWriterFile(),
CommandType.AwsCli: () => AwsCli(),
CommandType.Docker: () => Docker(),
CommandType.Gradle: () => Gradle(),
CommandType.WebRequest: () => WebRequest(),
CommandType.WebFile: () => WebFile(),
CommandType.ProcessPortUse: () => ProcessPortUse(),
CommandType.ProcessRun: () => ProcessRun(),
CommandType.ProcessKill: () => ProcessKill(),
CommandType.SMBAuth: () => SMBAuth(),
CommandType.Delay: () => Delay(),
};
factory Command.byType(CommandType type) {
return _commandMap[type]!();
}
Command();
late String id;
static RegExp regEx = RegExp(r'(<!)([_a-zA-Z0-9.-]+)(>)');
static RegExp regSetEx = RegExp(r'(<@)([_a-zA-Z0-9.-]+)(>)');
static Map<String, dynamic> replaceAllTagsMap(Map<String, dynamic> map,
{bool replace = false}) {
var newMap = <String, dynamic>{};
var deleteKeys = <String>[];
for (var pair in map.entries) {
var key = pair.key;
var keyReplacedTag = Command.replaceTagValue(key);
if (keyReplacedTag != key) {
deleteKeys.add(key);
}
if (map[key] is String) {
newMap[keyReplacedTag] = Command.replaceTagValue(map[key]);
} else if (map[key] is List) {
newMap[keyReplacedTag] = replaceAllTagsList(map[key], replace: replace);
} else if (map[key] is Map) {
newMap[keyReplacedTag] = replaceAllTagsMap(map[key], replace: replace);
} else {
newMap[keyReplacedTag] = map[key];
}
if (replace) {
map[keyReplacedTag] = newMap[keyReplacedTag];
}
}
if (replace) {
for (var key in deleteKeys) {
map.remove(key);
}
return map;
}
return newMap;
}
static List replaceAllTagsList(List list, {bool replace = false}) {
var newList = <dynamic>[];
for (var i = 0; i < list.length; ++i) {
if (list[i] is String) {
newList.add(Command.replaceTagValue(list[i]));
} else if (list[i] is List) {
newList.add(replaceAllTagsList(list[i]));
} else if (list[i] is Map) {
newList.add(replaceAllTagsMap(list[i]));
} else {
newList.add(list[i]);
}
if (replace) {
list[i] = newList[i];
}
}
if (replace) {
return newList;
}
return newList;
}
static Future setPropertyValue(String tag, dynamic value,
{bool? showPrint}) async {
var match = regSetEx.firstMatch(tag);
tag = match!.group(2)!;
if (tag.startsWith('property')) {
var key = tag.replaceFirst('property.', '');
Application.instance.property[key] = value;
// await CLI.println('[Return] $key : $value', color: Color.yellowStrong);
if (showPrint ?? true) {
if (value is List) {
Application.log('[Return] $key :\n');
var index = 0;
var length = value.length.toString().length;
for (var item in value) {
var indexStr = index.toString().padLeft(length);
Application.log('$indexStr - $item');
index++;
}
} else {
Application.log('[Return] $key : $value');
}
}
}
return simpleFuture;
}
static dynamic _getTagValue(String value) {
var setted = false;
dynamic data;
if (value.startsWith('property.')) {
var map = Application.instance.property;
var param = value.replaceFirst('property.', '');
if (map.containsKey(param)) {
data = map[param];
setted = true;
} else {
var params = param.split('.');
if (params.length > 1) {
var subMap = map;
for (var index = 0; index < params.length; index++) {
if (subMap.containsKey(params[index])) {
var value = subMap[params[index]];
if (value is Map<String, dynamic>) {
subMap = value;
} else {
data = value;
setted = true;
}
}
}
}
}
if (!setted) {
Application.log('"$value" is not exist in property');
data = null;
setted = true;
}
}
if (value.startsWith('state.')) {
var map = Application.instance.commandStates;
var param = value.replaceFirst('state.', '');
if (map.containsKey(param)) {
var value = map[param].toString().replaceAll('CommandState.', '');
data = value == '' ? null : value;
} else {
data = null;
}
setted = true;
}
if (!setted) {
throw Exception(
'Command.getTagValue: "$value" is not defined in property.');
}
return data;
}
static String getAbs(String path) {
path = getPathNormal(path);
if (path == '.' || path.startsWith('./') || path.startsWith('.\\')) {
path = path.replaceFirst('.', getPathNormal(Application.current));
}
if (path == '..' || path.startsWith('../') || path.startsWith('..\\')) {
path =
path.replaceFirst('..', '${getPathNormal(Application.current)}/..');
}
return File(path).absolute.path;
}
static String getPathNormal(String path) {
return replaceTagValue(Path.getNormal(path));
}
static dynamic replaceTagValue(String raw) {
var list = regEx.allMatches(raw);
if (list.length == 1) {
var item = list.first;
var sub = list.first.input.substring(item.start, item.end);
if (sub == raw) {
return replaceSingleTagValue(raw);
}
}
for (var match in list) {
var param = match.group(2);
var target = '<!${param!}>';
raw = raw.replaceAll(target, _getTagValue(param).toString());
}
return raw;
}
static dynamic replaceSingleTagValue(String raw) {
dynamic value;
var list = regEx.allMatches(raw);
for (var match in list) {
var param = match.group(2);
value = _getTagValue(param!);
}
return value;
}
late ExeHandleData? exeHandle = null;
late String _workspace;
String get workspace {
return _workspace;
}
Map<String, dynamic> get property {
return Application.instance.property;
}
DataCommon? get commonData {
return Application.instance.commonData;
}
String getWorkspace(String? dataWorkspace) {
if (dataWorkspace == null) {
//모델에 정의된 workspace가 없을경우 property에 설정된 workspace 우선
if (property.containsKey('workspace')) {
return getAbs(property['workspace']);
}
//property에도 설정이 되어 있지 않으면 현재 실행 프로젝트 기준으로 workspace 설정
else {
return getAbs(commonData!.workspace);
}
} else {
return getAbs(dataWorkspace);
}
}
String getCDPath(String path) {
String drive = '';
if (Platform.isWindows) {
drive = '${path[0]}: $and ';
}
return '${drive}cd $path';
}
String replaceTag(String raw) {
return Command.replaceTagValue(raw);
}
dynamic replaceSingleTag(String raw) {
return Command.replaceSingleTagValue(raw);
}
Future setProperty(String tag, dynamic value, {bool? showPrint}) async {
await Command.setPropertyValue(tag, value, showPrint: showPrint);
}
Map<String, dynamic> getParam(DataCommand command) {
Map<String, dynamic> map;
if (command.param == null) {
map = <String, dynamic>{};
} else {
map = Command.replaceAllTagsMap(command.param);
}
map['workspace'] = _workspace = getWorkspace(map['workspace']);
return map;
}
Future execute(DataCommand command) async {
return simpleFuture;
}
Future executeHandle(DataCommand command, ExeHandleData handleData) async {
exeHandle = handleData;
try {
return await execute(command);
} on Exception {
return await exeHandle!.pipelineFail!.execute();
}
}
Future<List<int>> getMergedPassExitCode(
Map<String, dynamic>? param, List<int>? passExitCodes) async {
var c = Completer<List<int>>();
var passExitCodesMerged = [0];
if (param != null) {
var data = DataParam.fromJson(param);
if (data.passExitCodes != null) {
passExitCodesMerged.addAll(data.passExitCodes!);
}
}
if (passExitCodes != null) {
passExitCodesMerged.addAll(passExitCodes);
}
c.complete(passExitCodesMerged);
return c.future;
}
Future completeProcess(ProcessData processData, DataCommand command,
{List<int>? passExitCodes}) async {
var exitCode = await processData.process.exitCode;
var passExitCodesMerge =
await getMergedPassExitCode(command.param, passExitCodes);
String? resultMessage = null;
if (exitCode != 0 && passExitCodesMerge.contains(exitCode)) {
resultMessage = processData.stderr.toString();
}
return await complete(exitCode, command,
errorMessage: processData.stderr.toString(),
passExitCodes: passExitCodes,
resultMessage: resultMessage);
}
Future<dynamic> complete(int exitCode, DataCommand command,
{String? errorMessage,
List<int>? passExitCodes,
String? resultMessage}) async {
var passExitCodesMerge =
await getMergedPassExitCode(command.param, passExitCodes);
if (command.param != null) {
var data = DataParam.fromJson(command.param);
if (data.setExitCode != null) {
await setProperty(data.setExitCode!, exitCode);
}
if (data.setResult != null) {
await setProperty(data.setResult!, resultMessage);
}
}
if (exeHandle == null) {
if (passExitCodesMerge.contains(exitCode)) {
return simpleFuture;
} else {
Application.log('Exit Code: $exitCode');
var data = ExceptionData();
data.phase = command.name;
data.message = errorMessage;
throw Exception(data);
}
} else {
if (passExitCodesMerge.contains(exitCode)) {
return await exeHandle!.pipelineSuccess!.execute();
} else {
return await exeHandle!.pipelineFail!.execute();
}
}
}
}
//Templete
class SimpleCommand extends Command {
@override
Future execute(DataCommand command) async {
Application.log(command.param);
return simpleFuture;
}
}
class ExceptionData {
String? phase;
String? message;
@override
String toString() {
var str = ' [$phase]\n\n';
str += message ??= '';
return str;
}
}