From 9c92c64e0b1c66e3979f1b0f485ae65b96d753d1 Mon Sep 17 00:00:00 2001 From: "leedongmyung[desktop]" Date: Sun, 29 Oct 2023 21:06:12 +0900 Subject: [PATCH] =?UTF-8?q?build=5Fmanager=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/oto_cli.dart | 2 + lib/oto/application.dart | 151 ++++++++ lib/oto/build/app_data_creator.dart | 26 ++ lib/oto/build/build_dart.dart | 45 +++ lib/oto/build/build_dart_compile.dart | 63 +++ lib/oto/build/build_dot_net.dart | 26 ++ lib/oto/build/build_flutter.dart | 22 ++ lib/oto/build/build_ios.dart | 102 +++++ lib/oto/command.dart | 137 +++++++ lib/oto/data/data.dart | 300 +++++++++++++++ lib/oto/data/data.g.dart | 313 +++++++++++++++ lib/oto/data/jenkins_data.dart | 24 ++ lib/oto/data/jenkins_data.g.dart | 37 ++ lib/oto/file/copy.dart | 81 ++++ lib/oto/file/delete.dart | 40 ++ lib/oto/file/rename.dart | 41 ++ lib/oto/shell/jenkins_env_params.bat | 14 + lib/oto/shell/jenkins_env_params.sh | 17 + lib/oto/shell/shell.dart | 37 ++ lib/oto/test_data.dart | 535 ++++++++++++++++++++++++++ lib/oto/uploader/ftp.dart | 438 +++++++++++++++++++++ lib/oto/uploader/uploader.dart | 74 ++++ lib/oto/util/execute_app.dart | 27 ++ lib/oto/util/publish_ios.dart | 55 +++ lib/oto/util/slack.dart | 45 +++ lib/oto/util/zip.dart | 80 ++++ 26 files changed, 2732 insertions(+) create mode 100644 lib/oto/application.dart create mode 100644 lib/oto/build/app_data_creator.dart create mode 100644 lib/oto/build/build_dart.dart create mode 100644 lib/oto/build/build_dart_compile.dart create mode 100644 lib/oto/build/build_dot_net.dart create mode 100644 lib/oto/build/build_flutter.dart create mode 100644 lib/oto/build/build_ios.dart create mode 100644 lib/oto/command.dart create mode 100644 lib/oto/data/data.dart create mode 100644 lib/oto/data/data.g.dart create mode 100644 lib/oto/data/jenkins_data.dart create mode 100644 lib/oto/data/jenkins_data.g.dart create mode 100644 lib/oto/file/copy.dart create mode 100644 lib/oto/file/delete.dart create mode 100644 lib/oto/file/rename.dart create mode 100644 lib/oto/shell/jenkins_env_params.bat create mode 100644 lib/oto/shell/jenkins_env_params.sh create mode 100644 lib/oto/shell/shell.dart create mode 100644 lib/oto/test_data.dart create mode 100644 lib/oto/uploader/ftp.dart create mode 100644 lib/oto/uploader/uploader.dart create mode 100644 lib/oto/util/execute_app.dart create mode 100644 lib/oto/util/publish_ios.dart create mode 100644 lib/oto/util/slack.dart create mode 100644 lib/oto/util/zip.dart diff --git a/bin/oto_cli.dart b/bin/oto_cli.dart index a9b7350..7355cd4 100644 --- a/bin/oto_cli.dart +++ b/bin/oto_cli.dart @@ -3,8 +3,10 @@ import 'package:oto_cli/cli/commands/command_start.dart'; import 'package:oto_cli/cli/cli.dart'; import 'package:oto_cli/cli/commands/command_template.dart'; +import 'package:oto_cli/oto/application.dart'; void main(List arguments) async { + var app = Application.to; CLI.initialize('OTO', arguments, [ CommandTemplate(), CommandInstall(), diff --git a/lib/oto/application.dart b/lib/oto/application.dart new file mode 100644 index 0000000..d971620 --- /dev/null +++ b/lib/oto/application.dart @@ -0,0 +1,151 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:dart_framework/utils/system_util.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:oto_cli/oto/data/jenkins_data.dart'; +import 'package:oto_cli/oto/test_data.dart'; + +class Application { + Application._privateConstructor(); + static final Application _instance = Application._privateConstructor(); + static Application get to => _instance; + + Map? _property; + Map? get property { + return _property; + } + + DataJenkins? _jenkinsData; + DataJenkins? get jenkinsData { + return _jenkinsData; + } + + Future build(List arguments) async { + // await test(); + // return; + + Map? buildMap; + Map? jenkinsMap; + var envArgs = envArguments(arguments); + bool isTest = envArgs.containsKey('isTest'); //for Unit Test + if (isTest) { + //Test map + buildMap = TestData.buildMap; + jenkinsMap = TestData.jenkinsMap!; + print('Start test build...'); + } else { + String os = Platform.operatingSystem; + var enable = true; + print('Current OS: $os'); + print('Start build...'); + String jsonStr = ''; + if (Platform.isMacOS || Platform.isLinux) { + await Process.run('zsh', ['./lib/shell/jenkins_env_params.sh']) + .then((ProcessResult result) => jsonStr = result.stdout); + jenkinsMap = getMapFromJson(jsonStr)!; + } else if (Platform.isWindows) { + print('Win Test!!'); + await Process.run('cmd', [ + '/C', + '${Directory.current.path}/lib/shell/jenkins_env_params.bat' + ]).then((ProcessResult result) => jsonStr = result.stdout); + jenkinsMap = getMapFromJson(jsonStr); + } else { + enable = false; + print( + 'is not Mac/Windows/Linux. build must be execute in Mac/Windows os system'); + } + if (enable) { + printBuildStep('Jenkins Variables'); + print(jsonStr); + } + } + + if (jenkinsMap != null) { + _jenkinsData = DataJenkins.fromJson(jenkinsMap); + if (!isTest) { + var buildStr = await getBuildData(); + buildMap = getMapFromYaml(buildStr); + } + + execute(buildMap!); + } + } + + Future execute(Map buildMap) async { + var c = Completer(); + DataBuild? build; + build = DataBuild.fromJson(buildMap); + + _property = build.common; + for (var command in build.commands) { + String commandStr = + command.command.toString().replaceAll('CommandType.', ''); + printBuildStep('Phase Start: $commandStr'); + if (!await Command(command.command).execute(command)) { + exit(10); + } + } + + c.complete(); + printBuildStep('Build Successfully Complete'); + exit(0); + return c.future; + } + + void printBuildStep(String name) { + print( + '************************************************************************************'); + print('* $name'); + print( + '************************************************************************************'); + } + + Future getBuildData() async { + var c = Completer(); + var file = File('${_jenkinsData!.workspace}/build_data.conf'); + var content = ''; + if (Platform.isWindows) { + content = file + .readAsStringSync(encoding: SystemEncoding()) + .replaceAll('BuildData=', ''); + var regEx = RegExp(r'(%)([a-zA-Z0-9]+)(%)'); + var list = regEx.allMatches(content); + var params = []; + for (var match in list) { + var param = match.group(2); + if (!params.contains(param)) { + params.add(param!); + } + } + + var temp = File('${_jenkinsData!.workspace}/param_temp.bat'); + var bat = ''; + for (var param in params) { + bat += '@echo %$param%\n'; + } + temp.writeAsStringSync(bat); + + var resultParam = ''; + await Process.run('cmd', ['/C', temp.path]) + .then((ProcessResult result) => resultParam = result.stdout); + var values = resultParam.split('\n'); + var length = params.length; + for (var i = 0; i < length; ++i) { + var value = values[i] + .toString() + .replaceAll('\\', '/') + .replaceAll(RegExp('\r|\n'), ''); + content = content.replaceAll('%${params[i]}%', value); + } + temp.deleteSync(); + } else { + content = await file.readAsString(); + } + print(content); + + c.complete(content); + return c.future; + } +} diff --git a/lib/oto/build/app_data_creator.dart b/lib/oto/build/app_data_creator.dart new file mode 100644 index 0000000..a0c92b6 --- /dev/null +++ b/lib/oto/build/app_data_creator.dart @@ -0,0 +1,26 @@ +import 'dart:async'; + +import 'package:dart_framework/core/app_data_manager.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class CreateAppData implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var jenkins = Application.to.jenkinsData!; + var data = DataAppDataCreator.fromJson(command.param); + var workspace = data.workspace ?? jenkins.workspace; + workspace = workspace.replaceAll('\\', '/'); + var appData = await AppDataManager.to + .create(data.scm, workspace, data.relativeAssetPath, data.name); + Command.setReturnValue(data.returnVersion, appData.version); + Command.setReturnValue(data.returnHash, appData.gitHash.substring(0, 7)); + + c.complete(true); + return c.future; + } +} diff --git a/lib/oto/build/build_dart.dart b/lib/oto/build/build_dart.dart new file mode 100644 index 0000000..436a577 --- /dev/null +++ b/lib/oto/build/build_dart.dart @@ -0,0 +1,45 @@ +import 'dart:async'; +import 'package:dart_framework/platform/process.dart'; +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class BuildDart implements Command { + Function? get error => null; + + Future appendShell( + StringBuffer shell, DataCommand command) async { + var c = Completer(); + c.complete(shell); + return c.future; + } + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataBuildDart.fromJson(command.param); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var targetPath = + Path.getNormal(Command.replaceTag(data.targetPath ?? workspace)); + + var shell = StringBuffer(); + shell.write('cd $targetPath'); + shell.write(' && flutter clean'); + shell.write(' && flutter packages upgrade'); + shell.write(' && flutter pub get'); + if (data.executeBuildRunner == null ? true : data.executeBuildRunner!) { + shell.write( + ' && flutter pub run build_runner build --delete-conflicting-outputs'); + } + + var process = await ProcessExecutor.run(await appendShell(shell, command), + workspace: workspace); + print('#### Exit Code: ${process.exitCode.toString()}'); + var passCode = [0, 64]; + //exitCode 65: package not defiend in pubspec.yaml + var success = passCode.contains(process.exitCode); + c.complete(success); + return c.future; + } +} diff --git a/lib/oto/build/build_dart_compile.dart b/lib/oto/build/build_dart_compile.dart new file mode 100644 index 0000000..8402d5d --- /dev/null +++ b/lib/oto/build/build_dart_compile.dart @@ -0,0 +1,63 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:oto_cli/oto/build/build_dart.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:dart_framework/utils/path.dart'; +// ignore: depend_on_referenced_packages +import 'package:path/path.dart' as path; + +import '../application.dart'; + +class BuildDartCompile extends BuildDart { + @override + Future appendShell( + StringBuffer shell, DataCommand command) async { + var c = Completer(); + var data = DataBuildDartCompile.fromJson(command.param); + var extension = getExeExtension(); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var targetPath = + Path.getNormal(Command.replaceTag(data.targetPath ?? workspace)); + var binPath = path.join(targetPath, 'bin'); + + //find build target + var targetDir = Directory(binPath); + var findedDartFile = ''; + var fileList = targetDir.listSync(); + for (var file in fileList) { + findedDartFile = path.basename(file.path); + break; + } + findedDartFile = path.join(targetPath, 'bin', findedDartFile); + var targetDartFile = + Command.replaceTag(data.targetDartFile ?? findedDartFile); + + //clear build path + var buildFolderName = 'release'; + var buildPath = path.join(targetPath, buildFolderName); + var buildDir = Directory(buildPath); + var buildFileName = data.buildFileName ?? 'release'; + if (!buildDir.existsSync()) { + buildDir.createSync(); + } + buildPath = + path.join(targetPath, buildFolderName, '$buildFileName$extension'); + var preBuildFile = File(buildPath); + if (preBuildFile.existsSync()) { + preBuildFile.deleteSync(); + } + + shell.write(' && dart compile exe "$targetDartFile" -o "$buildPath"'); + c.complete(shell); + return c.future; + } + + String getExeExtension() { + if (Platform.isWindows) { + return '.exe'; + } else { + return ''; + } + } +} diff --git a/lib/oto/build/build_dot_net.dart b/lib/oto/build/build_dot_net.dart new file mode 100644 index 0000000..d29ce71 --- /dev/null +++ b/lib/oto/build/build_dot_net.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'package:dart_framework/platform/process.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class BuildDotNet implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataBuildDotNet.fromJson(command.param); + var workspace = Application.to.jenkinsData!.workspace; + var shell = StringBuffer(); + // shell.writeln('C:\\Windows\\System32\\chcp.com 65001'); + shell.writeln('cd $workspace'); + shell.writeln( + 'MSBuild ${data.projectFile} /t:build /p:Configuration=Release;Platform=x86'); + var process = await ProcessExecutor.run(shell, workspace: workspace); + // var exitCode = await process.process.exitCode; + // c.complete(process.isSuccess); + c.complete(process.exitCode == 0); + return c.future; + } +} diff --git a/lib/oto/build/build_flutter.dart b/lib/oto/build/build_flutter.dart new file mode 100644 index 0000000..7454446 --- /dev/null +++ b/lib/oto/build/build_flutter.dart @@ -0,0 +1,22 @@ +import 'dart:async'; + +import 'package:oto_cli/oto/build/build_dart.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +// ignore: constant_identifier_names +enum BuildPlatform { Windows, Macos, Web, Apk, Appbundle, iOS, Linux } + +class BuildFlutter extends BuildDart { + @override + Future appendShell( + StringBuffer shell, DataCommand command) async { + var c = Completer(); + var data = DataBuildFlutter.fromJson(command.param); + var platform = + data.platform.toString().replaceAll('BuildPlatform.', '').toLowerCase(); + var additional = data.additional == null ? '' : ' ${data.additional!}'; + shell.write(' && flutter build $platform$additional'); + c.complete(shell); + return c.future; + } +} diff --git a/lib/oto/build/build_ios.dart b/lib/oto/build/build_ios.dart new file mode 100644 index 0000000..9d492d2 --- /dev/null +++ b/lib/oto/build/build_ios.dart @@ -0,0 +1,102 @@ +import 'dart:async'; + +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:dart_framework/platform/process.dart'; +import 'package:dart_framework/utils/path.dart'; + +class BuildiOS implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataBuildiOS.fromJson(command.param); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var workspacePath = Path.getNormal(Command.replaceTag(data.workspacePath)); + var destination = data.destination ?? 'generic/platform=iOS build'; + var cleanPod = data.cleanPod ?? false; + + var shell = StringBuffer(); + shell.write('security unlock-keychain -pr0bins'); + shell.write(' && cd $workspace/ios'); + shell.write(' && export LANG=en_US.UTF-8'); + shell.write(' && rm -rf Pods'); + shell.write(' && rm -rf Podfile.lock'); + if (cleanPod) { + shell.write(' && pod cache clean --all'); + shell.write(' && pod repo update'); + shell.write(' && pod install --repo-update'); + } else { + shell.write(' && pod install'); + } + shell.write( + ' && xcodebuild -workspace $workspacePath -scheme ${data.scheme} -destination $destination'); + + var process = await ProcessExecutor.run(shell, workspace: workspace); + print('#### Exit Code: ${process.exitCode.toString()}'); + var success = process.exitCode == 0; + if (success) { + c.complete(success); + return c.future; + } else { + if (process.exitCode == 31) { + data.cleanPod = true; + command.param = data.toJson(); + return await execute(command); + } else { + c.complete(success); + return c.future; + } + } + } +} + +class ArchiveiOS implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataArchiveiOS.fromJson(command.param); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var workspacePath = Path.getNormal(Command.replaceTag(data.workspacePath)); + var archivePath = Path.getNormal(Command.replaceTag(data.archivePath)); + + var shell = StringBuffer(); + shell.write( + 'xcodebuild -workspace $workspacePath -scheme ${data.scheme} -archivePath $archivePath archive'); + + var process = await ProcessExecutor.run(shell, workspace: workspace); + print('#### Exit Code: ${process.exitCode.toString()}'); + var success = process.exitCode == 0; + c.complete(success); + return c.future; + } +} + +class ExportiOS implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataExportiOS.fromJson(command.param); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var archivePath = Path.getNormal(Command.replaceTag(data.archivePath)); + var exportPath = Path.getNormal(Command.replaceTag(data.exportPath)); + var exportOptionPlistPath = + Path.getNormal(Command.replaceTag(data.exportOptionPlistPath)); + + var shell = StringBuffer(); + shell.write( + 'xcodebuild -exportArchive -archivePath $archivePath -exportPath $exportPath -exportOptionsPlist $exportOptionPlistPath'); + + var process = await ProcessExecutor.run(shell, workspace: workspace); + print('#### Exit Code: ${process.exitCode.toString()}'); + var success = process.exitCode == 0; + c.complete(success); + return c.future; + } +} diff --git a/lib/oto/command.dart b/lib/oto/command.dart new file mode 100644 index 0000000..7510584 --- /dev/null +++ b/lib/oto/command.dart @@ -0,0 +1,137 @@ +import 'dart:async'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/build/app_data_creator.dart'; +import 'package:oto_cli/oto/build/build_dart.dart'; +import 'package:oto_cli/oto/build/build_dart_compile.dart'; +import 'package:oto_cli/oto/build/build_dot_net.dart'; +import 'package:oto_cli/oto/build/build_flutter.dart'; +import 'package:oto_cli/oto/build/build_ios.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:oto_cli/oto/file/delete.dart'; +import 'package:oto_cli/oto/file/rename.dart'; +import 'package:oto_cli/oto/shell/shell.dart'; +import 'package:oto_cli/oto/file/copy.dart'; +import 'package:oto_cli/oto/util/execute_app.dart'; +import 'package:oto_cli/oto/util/publish_ios.dart'; +import 'package:oto_cli/oto/util/slack.dart'; +import 'package:oto_cli/oto/util/zip.dart'; +import 'package:oto_cli/oto/uploader/uploader.dart'; + +enum CommandType { + SimpleCommand, + CreateAppData, + BuildDart, + BuildFlutter, + BuildDartCompile, + BuildDotNet, + BuildiOS, + ArchiveiOS, + ExportiOS, + PublishiOS, + ExecuteApp, + Upload, + Copy, + Rename, + Delete, + Slack, + SlackBuild, + Zip, + Shell +} + +abstract class Command { + static final Map _commandMap = { + CommandType.SimpleCommand: SimpleCommand(), + CommandType.CreateAppData: CreateAppData(), + CommandType.BuildDart: BuildDart(), + CommandType.BuildFlutter: BuildFlutter(), + CommandType.BuildDartCompile: BuildDartCompile(), + CommandType.BuildDotNet: BuildDotNet(), + CommandType.BuildiOS: BuildiOS(), + CommandType.ArchiveiOS: ArchiveiOS(), + CommandType.ExportiOS: ExportiOS(), + CommandType.PublishiOS: PublishiOS(), + CommandType.ExecuteApp: ExecuteApp(), + CommandType.Upload: Uploader(), + CommandType.Copy: Copy(), + CommandType.Rename: Rename(), + CommandType.Delete: Delete(), + CommandType.Slack: Slack(), + CommandType.SlackBuild: SlackBuild(), + CommandType.Zip: Zip(), + CommandType.Shell: Shell() + }; + + factory Command(CommandType type) { + return _commandMap[type]!; + } + + static final Map _cache = {}; + static String replaceTag(String raw) { + var regEx = RegExp(r'()'); + var list = regEx.allMatches(raw); + for (var match in list) { + var param = match.group(2); + if (!_cache.containsKey(param)) { + _cache[param!] = _getTagValue(param); + } + var target = ''; + raw = raw.replaceAll(target, _cache[param]); + } + return raw; + } + + static void setReturnValue(String tag, dynamic value) { + var regEx = RegExp(r'()'); + var match = regEx.firstMatch(tag); + tag = match!.group(2)!; + if (tag.startsWith('common')) { + var key = tag.replaceFirst('common.', ''); + Application.to.property![key] = value; + } + } + + static dynamic _getTagValue(String value) { + var setted = false; + dynamic data; + if (value.startsWith('common.')) { + var map = Application.to.property; + var param = value.replaceFirst('common.', ''); + if (map!.containsKey(param)) { + data = map[param]; + setted = true; + } else { + print('"$value" is not exist in common'); + data = ''; + setted = true; + } + } + + if (!setted) { + throw Exception( + 'Command.getTagValue: "$value" is not defined in common.'); + } + return data; + } + + // ignore: missing_return + Future execute(DataCommand command) async { + var c = Completer(); + c.complete(true); + return c.future; + } +} + +//Templete +class SimpleCommand implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + print(command.param); + //Start here + c.complete(true); + return c.future; + } +} diff --git a/lib/oto/data/data.dart b/lib/oto/data/data.dart new file mode 100644 index 0000000..ced27e6 --- /dev/null +++ b/lib/oto/data/data.dart @@ -0,0 +1,300 @@ +import 'package:dart_framework/utils/slack/slack_sender.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:dart_framework/core/app_data_manager.dart'; +import 'package:oto_cli/oto/command.dart'; +part 'data.g.dart'; + +/// ***************************** Base Data ****************************/// +@JsonSerializable() +class DataBuild { + late Map common; + late List commands; + + DataBuild(); + + factory DataBuild.fromJson(Map json) => + _$DataBuildFromJson(json); + Map toJson() => _$DataBuildToJson(this); +} + +@JsonSerializable() +class DataCommand { + DataCommand(); + + late CommandType command; + late dynamic param; + + factory DataCommand.fromJson(Map json) => + _$DataCommandFromJson(json); + Map toJson() => _$DataCommandToJson(this); +} + +/// ***************************** Command List ****************************/// +//SimpleCommand +@JsonSerializable() +class DataSimpleCommand { + DataSimpleCommand(); + + late String message; + + factory DataSimpleCommand.fromJson(Map json) => + _$DataSimpleCommandFromJson(json); + Map toJson() => _$DataSimpleCommandToJson(this); +} + +//AppDataCreator +@JsonSerializable() +class DataAppDataCreator { + DataAppDataCreator(); + + late SCMType scm; + late String? workspace; + late String relativeAssetPath; + late String returnVersion; + late String returnHash; + late String name; + + factory DataAppDataCreator.fromJson(Map json) => + _$DataAppDataCreatorFromJson(json); + Map toJson() => _$DataAppDataCreatorToJson(this); +} + +//BuildDart +@JsonSerializable() +class DataBuildDart { + DataBuildDart(); + + late String? targetPath; + late bool? executeBuildRunner; + + factory DataBuildDart.fromJson(Map json) => + _$DataBuildDartFromJson(json); + Map toJson() => _$DataBuildDartToJson(this); +} + +//BuildFlutter +@JsonSerializable() +class DataBuildFlutter extends DataBuildDart { + DataBuildFlutter(); + + late String platform; + late String? additional; + + factory DataBuildFlutter.fromJson(Map json) => + _$DataBuildFlutterFromJson(json); + @override + Map toJson() => _$DataBuildFlutterToJson(this); +} + +//BuildDartCompile +@JsonSerializable() +class DataBuildDartCompile extends DataBuildDart { + DataBuildDartCompile(); + + late String? targetDartFile; + late String? buildFileName; + + factory DataBuildDartCompile.fromJson(Map json) => + _$DataBuildDartCompileFromJson(json); + @override + Map toJson() => _$DataBuildDartCompileToJson(this); +} + +//BuildDotNet +@JsonSerializable() +class DataBuildDotNet { + DataBuildDotNet(); + + late String projectFile; + + factory DataBuildDotNet.fromJson(Map json) => + _$DataBuildDotNetFromJson(json); + Map toJson() => _$DataBuildDotNetToJson(this); +} + +//BuildiOS +@JsonSerializable() +class DataBuildiOS { + DataBuildiOS(); + + late String workspacePath; + late String scheme; + late String? destination; + late bool? cleanPod; + + factory DataBuildiOS.fromJson(Map json) => + _$DataBuildiOSFromJson(json); + Map toJson() => _$DataBuildiOSToJson(this); +} + +//ArchiveiOS +@JsonSerializable() +class DataArchiveiOS { + DataArchiveiOS(); + + late String workspacePath; + late String scheme; + late String archivePath; + + factory DataArchiveiOS.fromJson(Map json) => + _$DataArchiveiOSFromJson(json); + Map toJson() => _$DataArchiveiOSToJson(this); +} + +//ExportiOS +@JsonSerializable() +class DataExportiOS { + DataExportiOS(); + + late String archivePath; + late String exportOptionPlistPath; + late String exportPath; + + factory DataExportiOS.fromJson(Map json) => + _$DataExportiOSFromJson(json); + Map toJson() => _$DataExportiOSToJson(this); +} + +//PublishiOS +@JsonSerializable() +class DataPublishiOS { + DataPublishiOS(); + + late String bundleID; + late String version; + late String appName; + late String ipaURL; + late String menifestURL; + late String displayImageURL; + late String fullSizeImageURL; + late String destinationPath; + + factory DataPublishiOS.fromJson(Map json) => + _$DataPublishiOSFromJson(json); + Map toJson() => _$DataPublishiOSToJson(this); +} + +//ExecuteApp +@JsonSerializable() +class DataExecuteApp { + DataExecuteApp(); + + late String executable; + late String desktopServicePath; + + factory DataExecuteApp.fromJson(Map json) => + _$DataExecuteAppFromJson(json); + Map toJson() => _$DataExecuteAppToJson(this); +} + +//Uploader +@JsonSerializable() +class DataUploader { + DataUploader(); + + late bool sftp; + late int port; + late String host; + late String user; + late String password; + late Map uploadMap; + + factory DataUploader.fromJson(Map json) => + _$DataUploaderFromJson(json); + Map toJson() => _$DataUploaderToJson(this); +} + +class DataFile { + DataFile(); + late List? ignorePattern; +} + +//Copy & Move +@JsonSerializable() +class DataCopy extends DataFile { + DataCopy(); + + late bool? isMove; + late Map copyMap; + + factory DataCopy.fromJson(Map json) => + _$DataCopyFromJson(json); + Map toJson() => _$DataCopyToJson(this); +} + +//Delete +@JsonSerializable() +class DataDelete extends DataFile { + DataDelete(); + + late List list; + + factory DataDelete.fromJson(Map json) => + _$DataDeleteFromJson(json); + Map toJson() => _$DataDeleteToJson(this); +} + +//Rename +@JsonSerializable() +class DataRename extends DataFile { + DataRename(); + + late Map renameMap; + + factory DataRename.fromJson(Map json) => + _$DataRenameFromJson(json); + Map toJson() => _$DataRenameToJson(this); +} + +//Slack +@JsonSerializable() +class DataSlack { + DataSlack(); + + factory DataSlack.fromJson(Map json) => + _$DataSlackFromJson(json); + Map toJson() => _$DataSlackToJson(this); +} + +//Slack Build +@JsonSerializable() +class DataSlackBuild { + DataSlackBuild(); + + late String appName; + late SlackBuildType type; + late String color; + late String version; + late String downloadURL; + late String? channel; + + factory DataSlackBuild.fromJson(Map json) => + _$DataSlackBuildFromJson(json); + Map toJson() => _$DataSlackBuildToJson(this); +} + +//Zip +@JsonSerializable() +class DataZip { + DataZip(); + + late String zipFile; + late List zipList; + + factory DataZip.fromJson(Map json) => + _$DataZipFromJson(json); + Map toJson() => _$DataZipToJson(this); +} + +//Shell +@JsonSerializable() +class DataShell { + DataShell(); + + late String? workspace; + late String path; + + factory DataShell.fromJson(Map json) => + _$DataShellFromJson(json); + Map toJson() => _$DataShellToJson(this); +} diff --git a/lib/oto/data/data.g.dart b/lib/oto/data/data.g.dart new file mode 100644 index 0000000..88f5783 --- /dev/null +++ b/lib/oto/data/data.g.dart @@ -0,0 +1,313 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DataBuild _$DataBuildFromJson(Map json) => DataBuild() + ..common = json['common'] as Map + ..commands = (json['commands'] as List) + .map((e) => DataCommand.fromJson(e as Map)) + .toList(); + +Map _$DataBuildToJson(DataBuild instance) => { + 'common': instance.common, + 'commands': instance.commands, + }; + +DataCommand _$DataCommandFromJson(Map json) => DataCommand() + ..command = $enumDecode(_$CommandTypeEnumMap, json['command']) + ..param = json['param']; + +Map _$DataCommandToJson(DataCommand instance) => + { + 'command': _$CommandTypeEnumMap[instance.command]!, + 'param': instance.param, + }; + +const _$CommandTypeEnumMap = { + CommandType.SimpleCommand: 'SimpleCommand', + CommandType.CreateAppData: 'CreateAppData', + CommandType.BuildDart: 'BuildDart', + CommandType.BuildFlutter: 'BuildFlutter', + CommandType.BuildDartCompile: 'BuildDartCompile', + CommandType.BuildDotNet: 'BuildDotNet', + CommandType.BuildiOS: 'BuildiOS', + CommandType.ArchiveiOS: 'ArchiveiOS', + CommandType.ExportiOS: 'ExportiOS', + CommandType.PublishiOS: 'PublishiOS', + CommandType.ExecuteApp: 'ExecuteApp', + CommandType.Upload: 'Upload', + CommandType.Copy: 'Copy', + CommandType.Rename: 'Rename', + CommandType.Delete: 'Delete', + CommandType.Slack: 'Slack', + CommandType.SlackBuild: 'SlackBuild', + CommandType.Zip: 'Zip', + CommandType.Shell: 'Shell', +}; + +DataSimpleCommand _$DataSimpleCommandFromJson(Map json) => + DataSimpleCommand()..message = json['message'] as String; + +Map _$DataSimpleCommandToJson(DataSimpleCommand instance) => + { + 'message': instance.message, + }; + +DataAppDataCreator _$DataAppDataCreatorFromJson(Map json) => + DataAppDataCreator() + ..scm = $enumDecode(_$SCMTypeEnumMap, json['scm']) + ..workspace = json['workspace'] as String? + ..relativeAssetPath = json['relativeAssetPath'] as String + ..returnVersion = json['returnVersion'] as String + ..returnHash = json['returnHash'] as String + ..name = json['name'] as String; + +Map _$DataAppDataCreatorToJson(DataAppDataCreator instance) => + { + 'scm': _$SCMTypeEnumMap[instance.scm]!, + 'workspace': instance.workspace, + 'relativeAssetPath': instance.relativeAssetPath, + 'returnVersion': instance.returnVersion, + 'returnHash': instance.returnHash, + 'name': instance.name, + }; + +const _$SCMTypeEnumMap = { + SCMType.Svn: 'Svn', + SCMType.Git: 'Git', +}; + +DataBuildDart _$DataBuildDartFromJson(Map json) => + DataBuildDart() + ..targetPath = json['targetPath'] as String? + ..executeBuildRunner = json['executeBuildRunner'] as bool?; + +Map _$DataBuildDartToJson(DataBuildDart instance) => + { + 'targetPath': instance.targetPath, + 'executeBuildRunner': instance.executeBuildRunner, + }; + +DataBuildFlutter _$DataBuildFlutterFromJson(Map json) => + DataBuildFlutter() + ..targetPath = json['targetPath'] as String? + ..executeBuildRunner = json['executeBuildRunner'] as bool? + ..platform = json['platform'] as String + ..additional = json['additional'] as String?; + +Map _$DataBuildFlutterToJson(DataBuildFlutter instance) => + { + 'targetPath': instance.targetPath, + 'executeBuildRunner': instance.executeBuildRunner, + 'platform': instance.platform, + 'additional': instance.additional, + }; + +DataBuildDartCompile _$DataBuildDartCompileFromJson( + Map json) => + DataBuildDartCompile() + ..targetPath = json['targetPath'] as String? + ..executeBuildRunner = json['executeBuildRunner'] as bool? + ..targetDartFile = json['targetDartFile'] as String? + ..buildFileName = json['buildFileName'] as String?; + +Map _$DataBuildDartCompileToJson( + DataBuildDartCompile instance) => + { + 'targetPath': instance.targetPath, + 'executeBuildRunner': instance.executeBuildRunner, + 'targetDartFile': instance.targetDartFile, + 'buildFileName': instance.buildFileName, + }; + +DataBuildDotNet _$DataBuildDotNetFromJson(Map json) => + DataBuildDotNet()..projectFile = json['projectFile'] as String; + +Map _$DataBuildDotNetToJson(DataBuildDotNet instance) => + { + 'projectFile': instance.projectFile, + }; + +DataBuildiOS _$DataBuildiOSFromJson(Map json) => DataBuildiOS() + ..workspacePath = json['workspacePath'] as String + ..scheme = json['scheme'] as String + ..destination = json['destination'] as String? + ..cleanPod = json['cleanPod'] as bool?; + +Map _$DataBuildiOSToJson(DataBuildiOS instance) => + { + 'workspacePath': instance.workspacePath, + 'scheme': instance.scheme, + 'destination': instance.destination, + 'cleanPod': instance.cleanPod, + }; + +DataArchiveiOS _$DataArchiveiOSFromJson(Map json) => + DataArchiveiOS() + ..workspacePath = json['workspacePath'] as String + ..scheme = json['scheme'] as String + ..archivePath = json['archivePath'] as String; + +Map _$DataArchiveiOSToJson(DataArchiveiOS instance) => + { + 'workspacePath': instance.workspacePath, + 'scheme': instance.scheme, + 'archivePath': instance.archivePath, + }; + +DataExportiOS _$DataExportiOSFromJson(Map json) => + DataExportiOS() + ..archivePath = json['archivePath'] as String + ..exportOptionPlistPath = json['exportOptionPlistPath'] as String + ..exportPath = json['exportPath'] as String; + +Map _$DataExportiOSToJson(DataExportiOS instance) => + { + 'archivePath': instance.archivePath, + 'exportOptionPlistPath': instance.exportOptionPlistPath, + 'exportPath': instance.exportPath, + }; + +DataPublishiOS _$DataPublishiOSFromJson(Map json) => + DataPublishiOS() + ..bundleID = json['bundleID'] as String + ..version = json['version'] as String + ..appName = json['appName'] as String + ..ipaURL = json['ipaURL'] as String + ..menifestURL = json['menifestURL'] as String + ..displayImageURL = json['displayImageURL'] as String + ..fullSizeImageURL = json['fullSizeImageURL'] as String + ..destinationPath = json['destinationPath'] as String; + +Map _$DataPublishiOSToJson(DataPublishiOS instance) => + { + 'bundleID': instance.bundleID, + 'version': instance.version, + 'appName': instance.appName, + 'ipaURL': instance.ipaURL, + 'menifestURL': instance.menifestURL, + 'displayImageURL': instance.displayImageURL, + 'fullSizeImageURL': instance.fullSizeImageURL, + 'destinationPath': instance.destinationPath, + }; + +DataExecuteApp _$DataExecuteAppFromJson(Map json) => + DataExecuteApp() + ..executable = json['executable'] as String + ..desktopServicePath = json['desktopServicePath'] as String; + +Map _$DataExecuteAppToJson(DataExecuteApp instance) => + { + 'executable': instance.executable, + 'desktopServicePath': instance.desktopServicePath, + }; + +DataUploader _$DataUploaderFromJson(Map json) => DataUploader() + ..sftp = json['sftp'] as bool + ..port = json['port'] as int + ..host = json['host'] as String + ..user = json['user'] as String + ..password = json['password'] as String + ..uploadMap = Map.from(json['uploadMap'] as Map); + +Map _$DataUploaderToJson(DataUploader instance) => + { + 'sftp': instance.sftp, + 'port': instance.port, + 'host': instance.host, + 'user': instance.user, + 'password': instance.password, + 'uploadMap': instance.uploadMap, + }; + +DataCopy _$DataCopyFromJson(Map json) => DataCopy() + ..ignorePattern = (json['ignorePattern'] as List?) + ?.map((e) => e as String) + .toList() + ..isMove = json['isMove'] as bool? + ..copyMap = Map.from(json['copyMap'] as Map); + +Map _$DataCopyToJson(DataCopy instance) => { + 'ignorePattern': instance.ignorePattern, + 'isMove': instance.isMove, + 'copyMap': instance.copyMap, + }; + +DataDelete _$DataDeleteFromJson(Map json) => DataDelete() + ..ignorePattern = (json['ignorePattern'] as List?) + ?.map((e) => e as String) + .toList() + ..list = (json['list'] as List).map((e) => e as String).toList(); + +Map _$DataDeleteToJson(DataDelete instance) => + { + 'ignorePattern': instance.ignorePattern, + 'list': instance.list, + }; + +DataRename _$DataRenameFromJson(Map json) => DataRename() + ..ignorePattern = (json['ignorePattern'] as List?) + ?.map((e) => e as String) + .toList() + ..renameMap = Map.from(json['renameMap'] as Map); + +Map _$DataRenameToJson(DataRename instance) => + { + 'ignorePattern': instance.ignorePattern, + 'renameMap': instance.renameMap, + }; + +DataSlack _$DataSlackFromJson(Map json) => DataSlack(); + +Map _$DataSlackToJson(DataSlack instance) => + {}; + +DataSlackBuild _$DataSlackBuildFromJson(Map json) => + DataSlackBuild() + ..appName = json['appName'] as String + ..type = $enumDecode(_$SlackBuildTypeEnumMap, json['type']) + ..color = json['color'] as String + ..version = json['version'] as String + ..downloadURL = json['downloadURL'] as String + ..channel = json['channel'] as String?; + +Map _$DataSlackBuildToJson(DataSlackBuild instance) => + { + 'appName': instance.appName, + 'type': _$SlackBuildTypeEnumMap[instance.type]!, + 'color': instance.color, + 'version': instance.version, + 'downloadURL': instance.downloadURL, + 'channel': instance.channel, + }; + +const _$SlackBuildTypeEnumMap = { + SlackBuildType.android: 'android', + SlackBuildType.ios: 'ios', + SlackBuildType.windows: 'windows', + SlackBuildType.mac: 'mac', + SlackBuildType.web: 'web', +}; + +DataZip _$DataZipFromJson(Map json) => DataZip() + ..zipFile = json['zipFile'] as String + ..zipList = + (json['zipList'] as List).map((e) => e as String).toList(); + +Map _$DataZipToJson(DataZip instance) => { + 'zipFile': instance.zipFile, + 'zipList': instance.zipList, + }; + +DataShell _$DataShellFromJson(Map json) => DataShell() + ..workspace = json['workspace'] as String? + ..path = json['path'] as String; + +Map _$DataShellToJson(DataShell instance) => { + 'workspace': instance.workspace, + 'path': instance.path, + }; diff --git a/lib/oto/data/jenkins_data.dart b/lib/oto/data/jenkins_data.dart new file mode 100644 index 0000000..2f881d3 --- /dev/null +++ b/lib/oto/data/jenkins_data.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'jenkins_data.g.dart'; + +@JsonSerializable() +class DataJenkins { + late String workspace; + late String jenkinsHome; + late String jenkinsUrl; + late String buildUrl; + late String jobUrl; + late String jobName; + late String gitCommit; + late int buildNumber; + late int buildID; + late String buildDisplayName; + late String gitBranch; + late String buildTag; + + DataJenkins(); + + factory DataJenkins.fromJson(Map json) => + _$DataJenkinsFromJson(json); + Map toJson() => _$DataJenkinsToJson(this); +} diff --git a/lib/oto/data/jenkins_data.g.dart b/lib/oto/data/jenkins_data.g.dart new file mode 100644 index 0000000..d8df864 --- /dev/null +++ b/lib/oto/data/jenkins_data.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'jenkins_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DataJenkins _$DataJenkinsFromJson(Map json) => DataJenkins() + ..workspace = json['workspace'] as String + ..jenkinsHome = json['jenkinsHome'] as String + ..jenkinsUrl = json['jenkinsUrl'] as String + ..buildUrl = json['buildUrl'] as String + ..jobUrl = json['jobUrl'] as String + ..jobName = json['jobName'] as String + ..gitCommit = json['gitCommit'] as String + ..buildNumber = json['buildNumber'] as int + ..buildID = json['buildID'] as int + ..buildDisplayName = json['buildDisplayName'] as String + ..gitBranch = json['gitBranch'] as String + ..buildTag = json['buildTag'] as String; + +Map _$DataJenkinsToJson(DataJenkins instance) => + { + 'workspace': instance.workspace, + 'jenkinsHome': instance.jenkinsHome, + 'jenkinsUrl': instance.jenkinsUrl, + 'buildUrl': instance.buildUrl, + 'jobUrl': instance.jobUrl, + 'jobName': instance.jobName, + 'gitCommit': instance.gitCommit, + 'buildNumber': instance.buildNumber, + 'buildID': instance.buildID, + 'buildDisplayName': instance.buildDisplayName, + 'gitBranch': instance.gitBranch, + 'buildTag': instance.buildTag, + }; diff --git a/lib/oto/file/copy.dart b/lib/oto/file/copy.dart new file mode 100644 index 0000000..90e204f --- /dev/null +++ b/lib/oto/file/copy.dart @@ -0,0 +1,81 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class Copy implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataCopy.fromJson(command.param); + var result = true; + var isMove = data.isMove == null ? false : data.isMove!; + var entries = data.copyMap.entries; + try { + var ignoredList = []; + for (var entry in entries) { + var start = Path.getNormal(Command.replaceTag(entry.key)); + var dest = Path.getNormal(Command.replaceTag(entry.value)); + var destDir = Directory(dest); + var name = Path.getName(start); + if (name == "*") { + //Delete & recreate dest folder + if (destDir.existsSync()) destDir.deleteSync(recursive: true); + destDir.createSync(recursive: true); + var startParentStr = Path.getParent(start); + if (startParentStr != null) { + var startParent = Directory(startParentStr); + if (startParent.existsSync()) { + var files = startParent.listSync(); + for (var file in files) { + if (file is File) { + await Path.copy(file.path, dest, false, + ignorePattern: data.ignorePattern, + ignoredList: ignoredList, + isMove: isMove); + } else if (file is Directory) { + await Path.copy(file.path, dest, true, + ignorePattern: data.ignorePattern, + ignoredList: ignoredList, + isMove: isMove); + } + } + } else { + throw ArgumentError('Start - $start is not exist'); + } + } + } else { + if (!destDir.existsSync()) destDir.createSync(recursive: true); + + var type = Path.getType(start); + if (FileType.none == type) { + print('[FileNotFound] - $start is not exist'); + } else { + await Path.copy(start, dest, FileType.directory == type, + ignorePattern: data.ignorePattern, + ignoredList: ignoredList, + isMove: isMove); + } + } + } + + if (ignoredList.isNotEmpty) { + print('==================== Ignored list ===================='); + for (var element in ignoredList) { + print(element); + } + print('======================================================'); + } + } on Exception catch (e) { + print('Copy General Exception: $e'); + result = false; + } + c.complete(result); + return c.future; + } +} diff --git a/lib/oto/file/delete.dart b/lib/oto/file/delete.dart new file mode 100644 index 0000000..4f247ad --- /dev/null +++ b/lib/oto/file/delete.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class Delete implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataDelete.fromJson(command.param); + var result = true; + try { + var ignoredList = []; + for (var item in data.list) { + var path = Path.getNormal(Command.replaceTag(item)); + var name = Path.getName(path); + if (name == "*") { + //Delete & recreate dest folder + } else {} + } + + if (ignoredList.isNotEmpty) { + print('==================== Ignored list ===================='); + for (var element in ignoredList) { + print(element); + } + print('======================================================'); + } + } on Exception catch (e) { + print('Copy General Exception: $e'); + result = false; + } + c.complete(result); + return c.future; + } +} diff --git a/lib/oto/file/rename.dart b/lib/oto/file/rename.dart new file mode 100644 index 0000000..0bab90e --- /dev/null +++ b/lib/oto/file/rename.dart @@ -0,0 +1,41 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class Rename implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataRename.fromJson(command.param); + var result = true; + var entries = data.renameMap.entries; + try { + for (var entry in entries) { + var start = Path.getNormal(Command.replaceTag(entry.key)); + var targetName = Command.replaceTag(entry.value); + var type = Path.getType(start); + var parentPath = Path.getParent(start)!; + var newPath = path.join(parentPath, targetName); + if (type == FileType.directory) { + await Directory(start).rename(newPath); + } else { + await File(start).rename(newPath); + } + + print('Rename: $start --> ${path.basename(newPath)}'); + } + } on Exception catch (e) { + print('Copy General Exception: $e'); + result = false; + } + c.complete(result); + return c.future; + } +} diff --git a/lib/oto/shell/jenkins_env_params.bat b/lib/oto/shell/jenkins_env_params.bat new file mode 100644 index 0000000..1d340eb --- /dev/null +++ b/lib/oto/shell/jenkins_env_params.bat @@ -0,0 +1,14 @@ +@echo { +@echo "workspace":"%WORKSPACE%", +@echo "jenkinsHome":"%JENKINS_HOME%", +@echo "jenkinsUrl":"%JENKINS_URL%", +@echo "buildUrl":"%BUILD_URL%", +@echo "jobUrl":"%JOB_URL%", +@echo "jobName":"%JOB_NAME%", +@echo "gitCommit":"%GIT_COMMIT%", +@echo "buildNumber":%BUILD_NUMBER%, +@echo "buildID":%BUILD_ID%, +@echo "buildDisplayName":"%BUILD_DISPLAY_NAME%", +@echo "gitBranch":"%GIT_BRANCH%", +@echo "buildTag":"%BUILD_TAG%" +@echo } \ No newline at end of file diff --git a/lib/oto/shell/jenkins_env_params.sh b/lib/oto/shell/jenkins_env_params.sh new file mode 100644 index 0000000..9f636f9 --- /dev/null +++ b/lib/oto/shell/jenkins_env_params.sh @@ -0,0 +1,17 @@ +# shellcheck disable=SC2016 +echo -e ' +{ + "workspace":"'${WORKSPACE}'", + "jenkinsHome":"'${JENKINS_HOME}'", + "jenkinsUrl":"'${JENKINS_URL}'", + "buildUrl":"'${BUILD_URL}'", + "jobUrl":"'${JOB_URL}'", + "jobName":"'${JOB_NAME}'", + "gitCommit":"'${GIT_COMMIT}'", + "buildNumber":'${BUILD_NUMBER}', + "buildID":'${BUILD_ID}', + "buildDisplayName":"'${BUILD_DISPLAY_NAME}'", + "gitBranch":"'${GIT_BRANCH}'", + "buildTag":"'${BUILD_TAG}'" +}' +echo ${BuildData} > "${WORKSPACE}/build_data.conf" \ No newline at end of file diff --git a/lib/oto/shell/shell.dart b/lib/oto/shell/shell.dart new file mode 100644 index 0000000..276f8b8 --- /dev/null +++ b/lib/oto/shell/shell.dart @@ -0,0 +1,37 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:dart_framework/platform/process.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class Shell implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData!; + var data = DataShell.fromJson(command.param); + print(command.param); + //Start here + var workspace = Command.replaceTag(data.workspace ?? jenkins.workspace); + var path = Command.replaceTag(data.path); + var shell = StringBuffer(); + if (Platform.isWindows) { + shell.writeln('call $path'); + } else { + shell.writeln('source $path'); + } + var process = await ProcessExecutor.run(shell, workspace: workspace); + c.complete(isSuccess(process.exitCode)); + return c.future; + } + + bool isSuccess(int exitCode) { + var success = false; + if (exitCode == 0 || exitCode == 128) { + success = true; + } + return success; + } +} diff --git a/lib/oto/test_data.dart b/lib/oto/test_data.dart new file mode 100644 index 0000000..c572439 --- /dev/null +++ b/lib/oto/test_data.dart @@ -0,0 +1,535 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:yaml/yaml.dart' as yaml; + +import 'package:oto_cli/oto/build/build_flutter.dart'; + +class TestData { + static Map get buildMap { + Map map = { + "common": {"workspace": ""}, + "commands": [] + }; + if (Platform.isMacOS) { + map = { + "common": {"workspace": Directory.current.path}, + "commands": [ + // { + // "command": "BuildDart", + // "param": {"targetPath": "./"} + // }, + // { + // "command": "BuildFlutter", + // "param": { + // "targetPath": "../desktop_service_frontend", + // "platform": BuildPlatform.Macos.toString() + // } + // }, + // { + // "command": "BuildFlutter", + // "param": { + // "targetPath": "../desktop_service_frontend", + // "platform": BuildPlatform.Web.toString() + // } + // }, + { + "command": "BuildDartCompile", + "param": { + "targetPath": "/Users/toki/works/dart/desktop_service_cli", + "targetDartFile": "./bin/desktop_service_cli.dart", + "buildFileName": "desktop_service_cli" + } + }, + // { + // "command": "Copy", + // "param": { + // "ignorePattern": [ + // ".git", + // ".dart_tool", + // ".gitignore", + // ".idea", + // "example" + // ], + // "isMove": false, + // "copyMap": { + // "/*": "/Users/toki/server/temp", + // // "../toki_flutter_framework/*": + // // "/Users/toki/server/toki_flutter_framework" + // } + // } + // }, + // { + // "command": "Zip", + // "param": { + // "zipFile": + // "/test_v_.zip", + // "zipList": [ + // "/bin/*", + // "/README.md" + // ] + // } + // }, + // { + // "command": "Upload", + // "param": { + // "sftp": false, + // "host": "toki-labs.com", + // "port": 215, + // "user": "spritex", + // "password": "Na2674126", + // "uploadMap": { + // "README.md": "/HDD1/WEB/_tmp", + // "bin/*": "/HDD1/WEB/_tmp/bin" + // } + // } + // }, + // { + // "command": "Shell", + // "param": { + // "workspace": "", + // "path": "/lib/shell/jenkins_env_params.sh" + // } + // } + ] + }; + + var json = jsonEncode(yaml.loadYaml(''' +--- +common: + workspace: ${Directory.current.path} + +commands: +- command: CreateAppData + param: + scm: Git + relativeAssetPath: assets/data + returnVersion: "" + returnHash: "" + +#- command: BuildDart +# param: +# targetPath: "./" + +#- command: BuildFlutter +# param: +# targetPath: "../tplayer" +# executeBuildRunner: false +# platform: ${BuildPlatform.Apk.toString()} +# additional: "--release --target-platform=android-arm64" + +#- command: BuildiOS +# param: +# workspacePath: "/Users/toki/jenkins/workspace/tplayer_ios/ios/Runner.xcworkspace" +# scheme: Runner +# destination: "generic/platform=iOS build" + +#- command: PublishiOS +# param: +# bundleID: com.tokilabs.tokiplayer +# version: +# appName: TPlayer +# ipaURL: https://toki-labs.com/build/tplayer/ios/tplayer.ipa +# menifestURL: https://toki-labs.com/build/tplayer/ios/menifest.plist +# displayImageURL: https://toki-labs.com/build/tplayer/icon.png +# fullSizeImageURL: https://toki-labs.com/build/tplayer/icon.png +# destinationPath: d:/build + +#- command: ArchiveiOS +# param: +# workspacePath: "/ios/Runner.xcworkspace" +# scheme: Runner +# archivePath: "/build/ios/TPlayer" + +#- command: ExportiOS +# param: +# archivePath: "/build/ios/TPlayer" +# exportOptionPlistPath: "/ExportOptions.plist" +# exportPath: "/build/ios" + +#- command: BuildDartCompile +# param: +# targetPath: d:/work/dart/desktop_service_cli +# buildFileName: desktop_service_cli + +#- command: Copy +# param: +# ignorePattern: [] +# isMove: false +# copyMap: +# "/README.md": "/../temp" +# "/bin/*": "/../temp/bin" + +#- command: Rename +# param: +# renameMap: +# "/../temp/README.md": "README_.md.back" +# "/../temp/bin/main.dart": "main.dart.back" +# "/../temp/test": "test2" + +#- command: Copy +# param: +# isMove: true +# copyMap: +# "/../temp/README.md": "/../temp/bin" + +#- command: Zip +# param: +# zipFile: "/test_v_.zip" +# zipList: +# - "/bin/*" +# - "/README.md" + +#- command: Upload +# param: +# sftp: false +# host: toki-labs.com +# port: 215 +# user: spritex +# password: Na2674126 +# uploadMap: +# README.md: "/HDD1/WEB/_tmp" +# bin/*: "/HDD1/WEB/_tmp/bin" + +#- command: Shell +# param: +# workspace: "" +# path: "/lib/shell/jenkins_env_params.bat" + +#- command: ExecuteApp +# param: +# executable: D:/Program Files/TokiSystemTrading/SystemTradingAPIApp.exe +# desktopServicePath: D:/work/dart/build_manager + +#- command: SlackBuild +# param: +# appName: tplayer +# type: android +# color: ED1458 +# version: +# downloadURL: http://toki-labs.com/build/tplayer/android/tplayer_arm64_.apk +# channel: release + +''')); + map = jsonDecode(json); + } else if (Platform.isWindows) { + var json = jsonEncode(yaml.loadYaml(''' +--- +common: + workspace: ${Directory.current.path} + +commands: +- command: CreateAppData + param: + scm: Git + name: Build Manager + relativeAssetPath: assets/data + returnVersion: "" + returnHash: "" + +#- command: BuildDart +# param: +# targetPath: "./" + +#- command: BuildFlutter +# param: +# targetPath: "../tplayer" +# executeBuildRunner: false +# platform: ${BuildPlatform.Apk.toString()} +# additional: "--release --target-platform=android-arm64" + +#- command: BuildiOS +# param: +# workspacePath: "/ios/Runner.xcworkspace" +# scheme: Runner +# destination: "generic/platform=iOS build" + +#- command: ArchiveiOS +# param: +# workspacePath: "/ios/Runner.xcworkspace" +# scheme: Runner +# archivePath: "/build/ios/TPlayer" + +#- command: ExportiOS +# param: +# archivePath: "/build/ios/TPlayer" +# exportOptionPlistPath: "/ExportOptions.plist" +# exportPath: "/build/ios" + +#- command: PublishiOS +# param: +# bundleID: com.tokilabs.tokiplayer +# version: +# appName: TPlayer +# ipaURL: https://toki-labs.com/build/tplayer/ios/tplayer.ipa +# menifestURL: https://toki-labs.com/build/tplayer/ios/manifest.plist +# displayImageURL: https://toki-labs.com/build/tplayer/icon.png +# fullSizeImageURL: https://toki-labs.com/build/tplayer/icon.png +# destinationPath: d:/build + +#- command: BuildDartCompile +# param: +# targetPath: d:/work/dart/desktop_service_cli +# buildFileName: desktop_service_cli + +#- command: Copy +# param: +# ignorePattern: [] +# isMove: false +# copyMap: +# "/README.md": "/../temp" +# "/bin/*": "/../temp/bin" + +#- command: Rename +# param: +# renameMap: +# "/../temp/README.md": "README_.md.back" +# "/../temp/bin/main.dart": "main.dart.back" +# "/../temp/test": "test2" + +#- command: Copy +# param: +# isMove: true +# copyMap: +# "/../temp/README.md": "/../temp/bin" + +#- command: Zip +# param: +# zipFile: "/test_v_.zip" +# zipList: +# - "/bin/*" +# - "/README.md" + +#- command: Upload +# param: +# sftp: false +# host: toki-labs.com +# port: 215 +# user: spritex +# password: Na2674126 +# uploadMap: +# README.md: "/HDD1/WEB/_tmp" +# bin/*: "/HDD1/WEB/_tmp/bin" + +#- command: Shell +# param: +# workspace: "" +# path: "/lib/shell/jenkins_env_params.bat" + +#- command: ExecuteApp +# param: +# executable: D:/Program Files/TokiSystemTrading/SystemTradingAPIApp.exe +# desktopServicePath: D:/work/dart/build_manager + +#- command: SlackBuild +# param: +# appName: tplayer +# type: android +# color: ED1458 +# version: +# downloadURL: http://toki-labs.com/build/tplayer/android/tplayer_arm64_.apk +# channel: release + +''')); + map = jsonDecode(json); + + // map = { + // "common": {"workspace": Directory.current.path}, + // "commands": [ + // { + // "command": "CreateAppData", + // "param": { + // "scm": "Git", + // "relativeAssetPath": "assets/data", + // "returnVersion": "", + // "returnHash": "" + // } + // }, + // { + // "command": "BuildDart", + // "param": {"targetPath": "./"} + // }, + // { + // "command": "BuildFlutter", + // "param": { + // "targetPath": "../desktop_service_frontend", + // "platform": BuildPlatform.Windows.toString() + // } + // }, + // { + // "command": "BuildDartCompile", + // "param": { + // "targetPath": "d:\\work\\dart\\desktop_service_cli", + // "buildFileName": "desktop_service_cli" + // } + // }, + // { + // "command": "Copy", + // "param": { + // "ignorePattern": [], + // "isMove": false, + // "copyMap": { + // "/README.md": "/../temp", + // "/bin/*": "/../temp/bin" + // } + // } + // }, + // { + // "command": "Zip", + // "param": { + // "zipFile": + // "/test_v_.zip", + // "zipList": [ + // "/bin/*", + // "/README.md" + // ] + // } + // }, + // { + // "command": "Upload", + // "param": { + // "sftp": false, + // "host": "toki-labs.com", + // "port": 215, + // "user": "spritex", + // "password": "Na2674126", + // "uploadMap": { + // "README.md": "/HDD1/WEB/_tmp", + // "bin/*": "/HDD1/WEB/_tmp/bin" + // } + // } + // }, + // { + // "command": "Shell", + // "param": { + // "workspace": "", + // "path": "/lib/shell/jenkins_env_params.bat" + // } + // }, + // { + // "command": "ExecuteApp", + // "param": { + // "executable": + // "D:/Program Files/TokiSystemTrading/SystemTradingAPIApp.exe", + // "desktopServicePath": "D:/work/dart/toki_desktop_service" + // } + // } + // ] + // }; + } else if (Platform.isLinux) { + map = { + "common": {"workspace": Directory.current.path}, + "commands": [ + // { + // "command": "BuildDart", + // "param": {"targetPath": ""} + // }, + { + "command": "BuildDartCompile", + "param": { + "targetPath": "/home/deck/work/desktop_service_cli", + "targetDartFile": "./bin/desktop_service_cli.dart", + "buildFileName": "desktop_service_cli" + } + }, + // { + // "command": "BuildFlutter", + // "param": { + // "targetPath": "../desktop_service_frontend", + // "platform": BuildPlatform.Linux.toString() + // } + // }, + // { + // "command": "Copy", + // "param": { + // "ignorePattern": [ + // ".git", + // ".dart_tool", + // ".gitignore", + // ".idea", + // "example" + // ], + // "isMove": false, + // "copyMap": { + // "/*": "/home/deck/temp", + // // "../toki_flutter_framework/*": + // // "/Users/toki/server/toki_flutter_framework" + // } + // } + // }, + // { + // "command": "Zip", + // "param": { + // "zipFile": + // "/test_v_.zip", + // "zipList": [ + // "/bin/*", + // "/README.md" + // ] + // } + // }, + // { + // "command": "Upload", + // "param": { + // "sftp": false, + // "host": "toki-labs.com", + // "port": 215, + // "user": "spritex", + // "password": "Na2674126", + // "uploadMap": { + // "README.md": "/HDD1/WEB/_tmp", + // "bin/*": "/HDD1/WEB/_tmp/bin" + // } + // } + // }, + // { + // "command": "Shell", + // "param": { + // "workspace": "", + // "path": "/lib/shell/jenkins_env_params.sh" + // } + // } + ] + }; + } + + return map; + } + + static Map? get jenkinsMap { + Map? map; + if (Platform.isMacOS || Platform.isLinux) { + map = { + "workspace": Directory.current.path, + "jenkinsHome": "/var/lib/jenkins", + "jenkinsUrl": "http://toki-labs.com:8083/", + "buildUrl": "http://toki-labs.com:8083/job/toki_os/54/", + "jobUrl": "http://toki-labs.com:8083/job/toki_os/", + "jobName": "toki_os", + "gitCommit": "6087aadcf9f8dbe0d2f024ae32dbcf0f6955d45b", + "buildNumber": 54, + "buildID": 54, + "buildDisplayName": "#54", + "gitBranch": "origin/master", + "buildTag": "jenkins-toki_os-54", + "buildData": buildMap + }; + } else if (Platform.isWindows) { + map = { + "workspace": Directory.current.path, + "jenkinsHome": "/var/lib/jenkins", + "jenkinsUrl": "http://toki-labs.com:8083/", + "buildUrl": "http://toki-labs.com:8083/job/toki_system_trading/18/", + "jobUrl": "http://toki-labs.com:8083/job/toki_system_trading/", + "jobName": "toki_system_trading", + "gitCommit": "abef2bc09c1d82e69ebe897ee079135aac9e797a", + "buildNumber": 18, + "buildID": 18, + "buildDisplayName": "#18", + "gitBranch": "origin/master", + "buildTag": "jenkins-toki_system_trading-18", + "buildData": buildMap + }; + } + return map; + } +} diff --git a/lib/oto/uploader/ftp.dart b/lib/oto/uploader/ftp.dart new file mode 100644 index 0000000..9131d4d --- /dev/null +++ b/lib/oto/uploader/ftp.dart @@ -0,0 +1,438 @@ +import 'dart:async'; +import 'dart:io'; +import 'package:ftpconnect/ftpconnect.dart'; +// ignore: implementation_imports +import 'package:ftpconnect/src/ftp_entry.dart'; +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/command.dart'; + +class FTP { + final String _host; + final int _port; + final int _timeout; + final String _user; + final String _pass; + + bool _isProgress = false; + String? _currentRemoteDir; + FTPConnect? _ftp; + + FTP(String host, + {int port = 21, + String user = 'anonymous', + String password = '', + int timeout = 30}) + : _host = host, + _port = port, + _user = user, + _pass = password, + _timeout = timeout { + _ftp = FTPConnect(host, + port: port, user: user, pass: password, timeout: timeout); + } + + Future connect() async { + int tryCount = 0; + bool connected = false; + while (!connected && tryCount < 10) { + try { + await _ftp!.connect(); + connected = true; + } on Exception catch (e) { + var code = e.hashCode; + var type = e.runtimeType; + print('========================= Connect Failed!'); + print('$code = $type'); + await Future.delayed(Duration(seconds: 1)); + } + } + } + + Future disconnect() async { + await _ftp!.disconnect(); + } + + //Remote target must be folder + Future upload(Map localRemoteMap) async { + var cache = >{}; + Map map = {}; + _currentRemoteDir = null; + try { + _isProgress = true; + var entries = localRemoteMap.entries; + for (var entry in entries) { + var local = Path.getNormalizePath(entry.key); + var remote = Path.getNormalizePath(entry.value); + var parent = Path.getParent(remote); + if (_currentRemoteDir != parent) { + await _ftp!.changeDirectory(parent!); + _currentRemoteDir = parent; + if (cache.containsKey(_currentRemoteDir)) { + map = cache[_currentRemoteDir]!; + } else { + map = await list(parent); + cache[_currentRemoteDir!] = map; + } + } + if (map.containsKey(remote) && map[remote] == FileType.directory) { + await _upload(local, remote); + } + } + } finally { + _isProgress = false; + } + } + + Future _upload(String local, String remote) async { + local = Command.replaceTag(local); + remote = Command.replaceTag(remote); + if (await Directory(local).exists()) { + var name = Path.getName(local); + var childRemoteFolder = '$remote/$name'; + await createFolder(childRemoteFolder); + var list = Directory(local).listSync(); + for (var item in list) { + if (await Directory(item.path).exists()) { + await _upload(item.path, childRemoteFolder); + } else { + await _ftp!.changeDirectory(childRemoteFolder); + await _ftp!.uploadFile(File(item.path)); + } + } + } else { + await _ftp!.changeDirectory(remote); + await _ftp!.uploadFile(File(local)); + } + } + + Map> _cache = >{}; + //Local target must be folder + Future download(Map remoteLocalMap) async { + var c = Completer(); + _cache = >{}; + try { + _isProgress = true; + var entries = remoteLocalMap.entries; + for (var entry in entries) { + await _download(entry.key, entry.value); + } + } finally { + _isProgress = false; + c.complete(); + } + return c.future; + } + + Future _download(String remote, String local) async { + Map? map; + remote = Path.getNormalizePath(remote); + local = Path.getNormalizePath(local); + var parent = Path.getParent(remote); + var name = Path.getName(remote); + if (_cache.containsKey(parent)) { + map = _cache[parent]!; + } else { + map ??= await list(parent!); + } + if (map.containsKey(remote)) { + if (map[remote] == FileType.directory) { + var childLocalFolder = '$local/$name'; + var dir = Directory(childLocalFolder); + if (!await dir.exists()) dir.createSync(); + Map childList; + if (_cache.containsKey(remote)) { + childList = _cache[remote]!; + } else { + childList = await list(remote); + _cache[remote] = childList; + } + var entries = childList.entries; + for (var entry in entries) { + await _download(entry.key, childLocalFolder); + } + } else { + await _ftp!.changeDirectory(parent!); + await _ftp!.downloadFile(name, File('$local/$name')); + } + } + } + + Future> list(String path) async { + var c = Completer>(); + var map = {}; + try { + await _ftp!.changeDirectory(path); + var list = await _ftp!.listDirectoryContent(); + var count = 0; + for (var item in list) { + FileType? type; + switch (item.type) { + case FTPEntryType.DIR: + type = FileType.directory; + break; + case FTPEntryType.FILE: + type = FileType.file; + break; + case FTPEntryType.LINK: + // TODO: Handle this case. + break; + case FTPEntryType.UNKNOWN: + // TODO: Handle this case. + break; + } + var name = item.name; + if (name != '.' && name != '..') { + count++; + var remotePath = '$path/$name'; + map[remotePath] = type!; +// print('$count = $type = $remotePath'); + } + } + } finally {} + c.complete(map); + return c.future; + } + + Future delete(List remoteList) async { + var cache = >{}; + Map? map; + _currentRemoteDir = null; + try { + _isProgress = true; + for (var item in remoteList) { + var parent = Path.getParent(item); + var name = Path.getName(item); + if (_currentRemoteDir != parent) { + await _ftp!.changeDirectory(parent!); + _currentRemoteDir = parent; + if (cache.containsKey(_currentRemoteDir)) { + map = cache[_currentRemoteDir!]!; + } else { + map = await list(parent); + cache[_currentRemoteDir!] = map; + } + } + if (map!.containsKey(item)) { + if (map[item] == FileType.directory) { + await _ftp!.deleteDirectory(name); + } else { + await _ftp!.deleteFile(name); + } + print('Deleted: $item'); + } + } + } finally { + _isProgress = false; + } + } + + Future createFolder(String path) async { + var c = Completer(); + try { + var parent = Path.getParent(path); + var name = Path.getName(path); + await _ftp!.changeDirectory(parent!); + if (await _ftp!.checkFolderExistence(name)) { + c.complete(false); + print('Create failed: Already exist file or folder'); + } else { + await _ftp!.makeDirectory(name); + c.complete(true); + print('Created Folder: $path'); + } + } finally {} + return c.future; + } +} + +// ignore: slash_for_doc_comments +/********************************* SFTP ********************************** +Need sshpass + +//Brew install - https://brew.sh/index_ko +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + +//sshpass install - https://gist.github.com/arunoda/7790979 +brew install https://raw.githubusercontent.com/kadwanev/bigboybrew/master/Library/Formula/sshpass.rb + +*************************************************************************/ + +class SFTP extends FTP { + SFTP(String host, + {int port = 22, + String user = 'anonymous', + String password = '', + int timeout = 30}) + : super(host, + port: port, user: user, password: password, timeout: timeout); + + @override + Future connect() async {} + + @override + Future disconnect() async {} + + //Remote target must be folder + @override + Future upload(Map localRemoteMap) async { + Map? map; + var entries = localRemoteMap.entries; + for (var entry in entries) { + var local = Path.getNormalizePath(entry.key); + var remote = Path.getNormalizePath(entry.value); + String type; + var parent = Path.getParent(remote); + if (map == null || !map.containsKey(local)) map = await list(parent!); + if (map[remote] == FileType.directory) { + if (await Directory(local).exists()) { + type = '[D]'; + await Process.run('sshpass', [ + '-p', + _pass, + 'scp', + '-P', + '$_port', + '-r', + local, + '$_user@$_host:$remote' + ]).then((ProcessResult results) => print(results.stdout)); + } else { + type = '[F]'; + await Process.run('sshpass', [ + '-p', + _pass, + 'scp', + '-P', + '$_port', + local, + '$_user@$_host:$remote' + ]).then((ProcessResult results) => print(results.stdout)); + } + print('Uploaded: $type$local --> $remote'); + } else { + throw ArgumentError('Remote target should be "Folder"'); + } + } + } + + //Local target must be folder + @override + Future download(Map remoteLocalMap) async { + Map? map; + var entries = remoteLocalMap.entries; + for (var entry in entries) { + var remote = Path.getNormalizePath(entry.key); + var local = Path.getNormalizePath(entry.value); + String type; + var parent = remote.substring(0, remote.lastIndexOf('/')); + if (map == null || !map.containsKey(remote)) map = await list(parent); + if (map[remote] == FileType.directory) { + type = '[D]'; + await Process.run('sshpass', [ + '-p', + _pass, + 'scp', + '-P', + '$_port', + '-r', + '$_user@$_host:$remote', + local + ]).then((ProcessResult result) => print(result.stdout)); + } else { + type = '[F]'; + await Process.run('sshpass', [ + '-p', + _pass, + 'scp', + '-P', + '$_port', + '$_user@$_host:$remote', + local + ]).then((ProcessResult result) => print(result.stdout)); + } + print('Download: $type$remote --> $local'); + } + } + + @override + Future> list(String path) async { + var c = Completer>(); + var map = {}; + var count = 0; + await Process.run('sshpass', [ + '-p', + _pass, + 'ssh', + '$_user@$_host', + '-p$_port', + 'ls -l $path' + ]).then((ProcessResult result) async { + var data = result.stdout; + var list = data.split('\n'); + list.forEach((element) { + if (count != 0 && element.length > 10) { + var info = element.split(' '); + var typeStr = info[0].substring(0, 1); + FileType type; + var fileName = info[info.length - 1]; + switch (typeStr) { + case 'd': + type = FileType.directory; + break; + case '-': + type = FileType.file; + break; + default: + type = FileType.link; + } + var mapKey = '$path/$fileName'; +// print('$count = $type = $mapKey'); + map[mapKey] = type; + } + count++; + }); + }); + c.complete(map); + return c.future; + } + + @override + Future delete(List remoteList) async { + print('######################## delete ${remoteList.length}'); + //check file exist + for (var item in remoteList) { + await Process.run('sshpass', [ + '-p', + _pass, + 'ssh', + '$_user@$_host', + '-p$_port', + 'rm -rf $item' + ]).then((ProcessResult results) => print(results.stdout)); + print('Deleted: $item'); + } + } + + @override + Future createFolder(String path) async { + var c = Completer(); + var parent = path.substring(0, path.lastIndexOf('/')); + var map = await list(parent); + if (map.containsKey(path)) { + c.complete(false); + print('Create failed: Already exist file or folder'); + } else { + await Process.run('sshpass', [ + '-p', + _pass, + 'ssh', + '$_user@$_host', + '-p$_port', + 'mkdir $path' + ]).then((ProcessResult results) => print(results.stdout)); + c.complete(true); + print('Created Folder: $path'); + } + return c.future; + } +} diff --git a/lib/oto/uploader/uploader.dart b/lib/oto/uploader/uploader.dart new file mode 100644 index 0000000..f3fbf0d --- /dev/null +++ b/lib/oto/uploader/uploader.dart @@ -0,0 +1,74 @@ +import 'dart:async'; +import 'dart:core'; +import 'dart:io'; +import 'package:path/path.dart' as path; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:oto_cli/oto/uploader/ftp.dart'; +import 'package:dart_framework/utils/path.dart'; + +class Uploader implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataUploader.fromJson(command.param); + + //Check asterisk + var uploadMap = {}; + var map = data.uploadMap; + int port = data.port == 0 ? (data.sftp ? 22 : 21) : data.port; + + var ftp = data.sftp + ? SFTP(data.host, port: port, user: data.user, password: data.password) + : FTP(data.host, port: port, user: data.user, password: data.password); + print( + '========================= [Try connection] ========================='); + await ftp.connect(); + print( + '========================= [Connected! Start Upload] ================'); + var entries = map.entries; + for (var entry in entries) { + var local = path.join(Application.to.jenkinsData!.workspace, entry.key); + var remote = entry.value; + print('[Upload]: $local ====> $remote'); + + var parentRemote = Path.getParent(remote); + var remoteMap = await ftp.list(parentRemote!); + var parentLocal = Path.getParent(local); + var nameLocal = Path.getName(local); + + if (nameLocal == '*') { + //에스테리크(*) 일경우 리모트 폴더를 삭제해서 전체 교체 + //Remove before folder + if (remoteMap.containsKey(remote) && + remoteMap[remoteMap] == FileType.directory) { + await ftp.delete([remote]); + } + //Create new folder + await ftp.createFolder(remote); + var parentLocalDir = Directory(parentLocal!); + if (parentLocalDir.existsSync()) { + var localFiles = parentLocalDir.listSync(); + var map = {}; + for (var file in localFiles) { + map[file.path] = remote; + } + await ftp.upload(map); + } else { + throw ArgumentError('Start - $parentLocal is not exist'); + } + } else { + uploadMap[local] = remote; + } + } + + if (uploadMap.isNotEmpty) await ftp.upload(uploadMap); + + await ftp.disconnect(); + print( + '========================= [Upload completed!] ======================'); + c.complete(true); + return c.future; + } +} diff --git a/lib/oto/util/execute_app.dart b/lib/oto/util/execute_app.dart new file mode 100644 index 0000000..eb6eeb4 --- /dev/null +++ b/lib/oto/util/execute_app.dart @@ -0,0 +1,27 @@ +import 'dart:async'; + +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:dart_framework/platform/process.dart'; + +class ExecuteApp implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataExecuteApp.fromJson(command.param); + + var exe = Command.replaceTag(data.executable); + var desktop = Command.replaceTag(data.desktopServicePath); + print('Process Start: $exe'); + var content = 'start "" "$exe" & exit'; + + var buffer = StringBuffer(content); + var processData = + await ProcessExecutor.run(buffer, workspace: data.desktopServicePath); + c.complete(true); + return c.future; + } +} diff --git a/lib/oto/util/publish_ios.dart b/lib/oto/util/publish_ios.dart new file mode 100644 index 0000000..eb099ae --- /dev/null +++ b/lib/oto/util/publish_ios.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:dart_framework/utils/path.dart'; +// ignore: depend_on_referenced_packages +import 'package:path/path.dart' as path; + +class PublishiOS implements Command { + Function? get error => null; + + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var data = DataPublishiOS.fromJson(command.param); + var workspace = Path.getNormal(Application.to.jenkinsData!.workspace); + var menifestURL = Command.replaceTag(data.menifestURL); + var destinationPath = Command.replaceTag(data.destinationPath); + + var templatePath = path.normalize( + '${path.dirname(Platform.script.toFilePath())}/../assets/template'); + var indexContent = await File('$templatePath/index.html').readAsString(); + indexContent = indexContent.replaceAll("MENIFEST_URL", menifestURL); + + var destinationDir = Directory(destinationPath); + if (!destinationDir.existsSync()) { + await destinationDir.create(recursive: true); + } + var indexFile = File('$destinationPath/index.html'); + await indexFile.writeAsString(indexContent); + + print('Created "index.html" --> "$destinationPath/index.html"'); + + var menifestContent = + await File('$templatePath/manifest.plist').readAsString(); + menifestContent = menifestContent + .replaceFirst('IPA_URL', Command.replaceTag(data.ipaURL)) + .replaceFirst('BUNDLE_ID', Command.replaceTag(data.bundleID)) + .replaceFirst('VERSION', Command.replaceTag(data.version)) + .replaceFirst('APP_NAME', Command.replaceTag(data.appName)) + .replaceFirst( + 'DISPLAY_IMAGE_URL', Command.replaceTag(data.displayImageURL)) + .replaceFirst( + 'FULL_SIZE_IMAGE_URL', Command.replaceTag(data.fullSizeImageURL)); + var menifestFile = File('$destinationPath/manifest.plist'); + await menifestFile.writeAsString(menifestContent); + + print('Created "manifest.plist" --> "$destinationPath/manifest.plist"'); + + c.complete(true); + return c.future; + } +} diff --git a/lib/oto/util/slack.dart b/lib/oto/util/slack.dart new file mode 100644 index 0000000..b941a65 --- /dev/null +++ b/lib/oto/util/slack.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; +import 'package:color/color.dart'; +import 'package:dart_framework/utils/slack/slack_sender.dart'; + +class Slack implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataSlack.fromJson(command.param); + var result = true; + + //To-do: compose parameters + + c.complete(result); + return c.future; + } +} + +class SlackBuild implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataSlackBuild.fromJson(command.param); + var result = true; + + var version = Command.replaceTag(data.version); + var download = Command.replaceTag(data.downloadURL); + var channel = data.channel ?? 'build'; + var color = Color.hex(data.color); + var sender = SlackSender(); + await sender.sendBuild(data.appName, data.type, color, version, download, + channel: channel); + + c.complete(result); + return c.future; + } +} diff --git a/lib/oto/util/zip.dart b/lib/oto/util/zip.dart new file mode 100644 index 0000000..3538237 --- /dev/null +++ b/lib/oto/util/zip.dart @@ -0,0 +1,80 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:dart_framework/utils/path.dart'; +import 'package:oto_cli/oto/application.dart'; +import 'package:oto_cli/oto/command.dart'; +import 'package:oto_cli/oto/data/data.dart'; + +class Zip implements Command { + @override + Future execute(DataCommand command) async { + Completer c = Completer(); + var common = Application.to.property; + var jenkins = Application.to.jenkinsData; + var data = DataZip.fromJson(command.param); + try { + var zipFilePath = Path.getNormal(Command.replaceTag(data.zipFile)); + print('Start Compress...'); + var parent = Directory(Path.getParent(zipFilePath)!); + if (!parent.existsSync()) parent.createSync(recursive: true); + var zipFile = File(zipFilePath); + if (zipFile.existsSync()) zipFile.deleteSync(); + var tempDir = Directory(zipFilePath); + if (!tempDir.existsSync()) { + var encoder = ZipFileEncoder(); + encoder.create(zipFilePath); + var list = data.zipList; + for (var path in list) { + path = Path.getNormal(Command.replaceTag(path)); + var name = Path.getName(path); + if (name == '*') { + parent = Directory(Path.getParent(path)!); + if (parent.existsSync()) { + var list = parent.listSync(); + for (var item in list) { + var dir = Directory(item.path); + var file = File(item.path); + if (dir.existsSync()) { + print('- Compress Dir --> ${dir.path}'); + encoder.addDirectory(dir); + } + if (file.existsSync()) { + encoder.addFile(file); + print('- Compress File --> ${file.path}'); + } + } + } else { + print('================== "$path" is not exist'); + } + } else { + var dir = Directory(path); + var file = File(path); + var added = false; + if (dir.existsSync()) { + print('- Compress Dir --> ${dir.path}'); + encoder.addDirectory(dir); + added = true; + } + if (file.existsSync()) { + encoder.addFile(file); + print('- Compress File --> ${file.path}'); + added = true; + } + if (!added) print('================== "$path" is not exist'); + } + } + encoder.close(); + c.complete(true); + print('Compress success: $zipFilePath'); + } else { + throw Exception('"${tempDir.path}" is Directory'); + } + } on Exception catch (e) { + print('Zip General Exception: $e'); + } + + return c.future; + } +}