Refactor: Update project structure and fix issues

This commit is contained in:
toki 2026-04-04 19:08:49 +09:00
parent 45e79bdebd
commit c31eed3ed6
29 changed files with 259 additions and 173 deletions

119
CLAUDE.md Normal file
View file

@ -0,0 +1,119 @@
# OTO CLI Guide for AI Assistants
## Project Overview
OTO CLI is a Dart command-line tool that executes build/deploy pipelines defined in YAML files.
It integrates with Jenkins, runs local test builds, and supports a scheduler mode.
## Execution Flow
```
bin/main.dart
└── Application (singleton)
└── DataComposer ← parses YAML + Jenkins env
└── Pipeline
└── Command.byType(CommandType) → Command.execute(DataCommand)
```
1. **`bin/main.dart`** Entry point. Registers three CLI commands: `template`, `exe`, `scheduler`.
2. **`lib/cli/commands/command_exe.dart`** Parses CLI flags (`-j` Jenkins, `-t` test, `-f` file path) and calls `Application.build()`.
3. **`lib/oto/application.dart`** Singleton. Owns `property` map, `commonData`, `commandStates`. Calls `registerAllCommands()` then runs the pipeline.
4. **`lib/oto/core/data_composer.dart`** Reads YAML and Jenkins env into structured data.
5. **`lib/oto/pipeline/pipeline.dart`** Iterates `DataCommand` list and dispatches to `Command.byType()`.
6. **`lib/oto/commands/command_registry.dart`** Single place where all `CommandType → Command` mappings are registered.
## BuildType Enum
Defined in `lib/oto/application.dart`:
| Value | Description |
|-------|-------------|
| `jenkins` | CI mode reads workspace and env from Jenkins environment variables |
| `test` | Local test mode uses hardcoded test data |
| `file` | Reads pipeline YAML from a local file path |
| `scheduler` | Scheduler daemon mode |
## Tag System (`lib/oto/core/tag_system.dart`)
Tags are resolved inside YAML values at runtime.
### Read tag: `<!namespace.key>`
Substitutes a value from the runtime store.
```yaml
param:
workspace: <!property.workspace> # flat property access
version: <!property.build.version> # nested property access
state: <!state.myCommandId> # command state (ready/progress/complete)
```
- If the tag occupies the **entire** string, the original type is returned (e.g. a List stays a List).
- If the tag is **embedded** in a string, it is converted via `toString()`.
### Write tag: `<@namespace.key>`
Stores the command result back into a property.
```yaml
param:
set-version: <@property.appVersion> # saves result into property["appVersion"]
```
Used via `Command.setProperty()` / `TagSystem.setPropertyValue()`.
## Adding a New Command
1. **Define data model** in the appropriate `lib/oto/data/*_data.dart` file, extending `DataParam`.
2. **Add enum value** to `CommandType` in `lib/oto/commands/command.dart`.
3. **Implement command class** extending `Command` with `execute(DataCommand)`.
4. **Register** in `lib/oto/commands/command_registry.dart`.
5. Run `dart run build_runner build` if you added a new `@JsonSerializable` class.
## Key Directory Structure
```
lib/
├── bin/main.dart # entry point
├── cli/commands/ # CLI-layer commands (exe, template, scheduler)
└── oto/
├── application.dart # singleton orchestrator, BuildType enum
├── commands/
│ ├── command.dart # Command base class, CommandType enum
│ ├── command_registry.dart # all command registrations
│ ├── build/ # iOS/Flutter/Dart/MSBuild build commands
│ ├── file/ # file operations (copy, rename, delete, zip…)
│ ├── git/ # git commands
│ ├── infra/ # FTP, SMB
│ ├── notification/ # Slack, Mattermost
│ ├── process/ # process management
│ └── util/ # timers, JSON, string utils
├── core/
│ ├── tag_system.dart # tag substitution engine
│ ├── data_composer.dart # YAML + env → structured data
│ └── defined_data.dart # built-in YAML templates
├── data/
│ ├── base_data.dart # DataParam (base), DataCommon (Jenkins env)
│ ├── command_data.dart # DataCommand (re-exports all data types)
│ └── *_data.dart # domain-specific data models
└── pipeline/
└── pipeline.dart # command dispatch loop
```
## Data Model Conventions
- All command parameters extend `DataParam` (`lib/oto/data/base_data.dart`).
- JSON serialization uses `json_annotation`. Generated files are `*.g.dart`.
- `*.g.dart` files are normally generated by `dart run build_runner build` but can be edited directly when needed.
- `DataCommon` holds Jenkins environment data (workspace, job name, build number, etc.).
- `DataCommand` (in `command_data.dart`) is the unified container passed to every `Command.execute()`.
## Error Handling
- `Application.build()` in `application.dart` uses `catch (e, stacktrace)` (not `on Exception`) to also catch `Error` subtypes such as `TypeError`.
- On error, `exit(10)` is called to signal failure to the parent process.
## Workspace Resolution (Command base class)
`getWorkspace()` in `command.dart` resolves in this order:
1. `workspace` field on the command's data model
2. `property['workspace']` in the global property map
3. `commonData.workspace` (Jenkins environment)

View file

@ -4,7 +4,7 @@ import 'package:oto_cli/cli/cli.dart';
class CommandConstant {
static _CommandConstantBase? _instance;
// ignore: library_private_types_in_public_api
// ignore: library_private_types_in_public_api, non_constant_identifier_names
static _CommandConstantBase? get OS {
if (_instance == null) {
if (Platform.isMacOS) {
@ -21,7 +21,6 @@ class CommandConstant {
class _CommandConstantBase {
String get installPath {
var userPath = Platform.environment['HOME'];
return '';
}

View file

@ -48,7 +48,7 @@ class CommandInstall extends CommandBase {
}
Future installFiles(String installPath) async {
var file = await File('$installPath/silentbatch.exe').create();
await File('$installPath/silentbatch.exe').create();
// await file.writeAsBytes(silentbatch);
return simpleFuture;
}

View file

@ -49,7 +49,7 @@ class IsolateScheduler extends IsolateBase {
if (hash != fileHash) {
updated = true;
fileHash = hash;
//
// Reload file contents on every change
var map = Application.getMapFromYamlA(await file.readAsString());
//============ Validate yaml format ============//
@ -113,7 +113,7 @@ class IsolateScheduler extends IsolateBase {
}
}
} else {
//file이
// Exit if the file no longer exists
onError("The file '${file.path}' does not exist.");
if (cron != null) await cron?.close();
return;

View file

@ -226,7 +226,7 @@ class SchedulerManager {
}
Future _startProcess() async {
//Launchd로
// Start directly if Launchd did not auto-launch the process
if (_currentScheduler == null) {
if (Platform.isWindows) {
final program = Platform.resolvedExecutable;
@ -305,7 +305,7 @@ class SchedulerManager {
Future start() async {
_log('Scheduler is running on pid [$pid].');
while (true) {
//
// Check every second for registered scheduler files
await Future.delayed(Duration(seconds: 1));
var settings = _localData.map;
for (var alias in _schedulers) {
@ -373,7 +373,4 @@ class SchedulerManager {
mode: FileMode.append, flush: true);
}
void _logError(Exception e, StackTrace stacktace) {
_log('[${e.toString()}]\n${stacktace.toString()}');
}
}

View file

@ -9,7 +9,7 @@ class SchedulerOsx extends SchedulerManager {
@override
String get exePath {
var exe = Platform.resolvedExecutable;
//homebrew로
// Handle homebrew-installed executable path
if (exe.startsWith('/usr/local')) {
//intel mac
exe = '/usr/local/bin/${path.basenameWithoutExtension(exe)}';

View file

@ -13,10 +13,9 @@ import 'package:oto_cli/oto/commands/command_registry.dart';
import 'package:oto_cli/oto/core/defined_data.dart';
import 'package:oto_cli/oto/pipeline/pipeline.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:oto_cli/oto/data/jenkins_data.dart';
import 'package:oto_cli/oto/core/data_composer.dart';
import 'package:yaml/yaml.dart';
import 'package:path/path.dart' as pathlib;
import 'package:path/path.dart' as path;
enum BuildType { jenkins, test, file, scheduler }
@ -30,7 +29,7 @@ class Application {
if (Application.instance.buildType == BuildType.jenkins) {
return Platform.environment['WORKSPACE']!;
} else {
return pathlib.current;
return path.current;
}
}
@ -96,7 +95,7 @@ class Application {
}
property = Command.replaceAllTagsMap(property, replace: true);
//set command in dic, id
// Register commands; filter out duplicate command IDs
for (var command in build.commands) {
if (dataCommandMap.containsKey(command.id)) {
var ex = ExceptionData();

View file

@ -127,7 +127,7 @@ class BuildiOS extends Command {
}
String? target = data.target;
//version이 ,
// If version is provided, apply it; otherwise retain the existing value from the project
if (version != null && version.isNotEmpty) {
await updateMarketingVersion(
'${data.workspace}/$xcodeproj/project.pbxproj', version);
@ -136,7 +136,7 @@ class BuildiOS extends Command {
version = await getSettingXcodeproj(
data.workspace!, xcodeproj!, target!, 'MARKETING_VERSION');
}
//buildNumber가 ,
// If buildNumber is provided, apply it; otherwise retain the existing value from the project
if (buildNumber != null && buildNumber.isNotEmpty) {
await updateBuildNumber(
'${data.workspace}/$xcodeproj/project.pbxproj', buildNumber);
@ -230,7 +230,7 @@ class BuildiOS extends Command {
static Future<File> initializeFastlane(
String workspace, String apiKeyPath, String appID) async {
var json = jsonDecode(File(apiKeyPath).readAsStringSync());
var fastlaneContent = fastlaneTemplete;
var fastlaneContent = fastlaneTemplate;
fastlaneContent = fastlaneContent
.replaceFirst('[APP_IDENTIFIER]', appID)
.replaceFirst('[API_KEY_ID]', json['key_id'])
@ -283,7 +283,7 @@ class BuildiOS extends Command {
final fileRef = document.findAllElements('FileRef').firstWhere(
(element) =>
element.getAttribute('location')?.endsWith('.xcodeproj') ?? false,
orElse: () => XmlElement(XmlName('Dummy')), // null
orElse: () => XmlElement(XmlName('Dummy')), // sentinel for null-check below
);
final location =
@ -338,7 +338,7 @@ class BuildiOS extends Command {
}
if (insideBuildSettings && trimmed == '};') {
//
// Insert the setting if not already present
if (!hasTargetSetting) {
updatedLines.add(' $settingKey = $settingValue;');
}

View file

@ -1,4 +1,4 @@
var fastlaneTemplete = '''
var fastlaneTemplate = '''
default_platform(:ios)
APP_IDENTIFIER = "[APP_IDENTIFIER]"
@ -13,7 +13,7 @@ platform :ios do
end
desc "지정한 앱 버전 + 빌드 넘버에 대한 TestFlight 상태 추적 (JSON 출력)"
desc "Track TestFlight status for a specific app version and build number (JSON output)"
lane :testflight_status do
require 'spaceship'
require 'json'
@ -43,7 +43,7 @@ platform :ios do
end
desc "TestFlight에 등록된 특정 버전의 최신 빌드 넘버 가져오기 (JSON 출력)"
desc "Get the latest build number for a specific version registered on TestFlight (JSON output)"
lane :latest_build_number do
require 'json'
@ -67,7 +67,7 @@ platform :ios do
end
desc "필터 조건에 맞춰 App Store Connect 버전 상태 JSON 출력"
desc "Output App Store Connect version status as JSON based on filter conditions"
lane :appstore_versions do
require 'spaceship'
require 'json'
@ -81,19 +81,19 @@ platform :ios do
.select { |v| v.platform == "IOS" }
.reject { |v| v.app_store_state == "REPLACED_WITH_NEW_VERSION" }
# 1. READY_FOR_SALE
# 1. Latest among READY_FOR_SALE
latest_ready = versions
.select { |v| v.app_store_state == "READY_FOR_SALE" }
.max_by { |v| v.respond_to?(:created_date) ? v.created_date.to_s : "" }
# 2. PREPARE_FOR_SUBMISSION
# 2. Lowest version among PREPARE_FOR_SUBMISSION
prepare_versions = versions
.select { |v| v.app_store_state == "PREPARE_FOR_SUBMISSION" }
.sort_by { |v| Gem::Version.new(v.version_string) }
lowest_prepare = prepare_versions.first
# 3. (READY_FOR_SALE, PREPARE_FOR_SUBMISSION, REPLACED )
# 3. Other status versions (excluding READY_FOR_SALE, PREPARE_FOR_SUBMISSION, REPLACED)
included_states = ["READY_FOR_SALE", "PREPARE_FOR_SUBMISSION", "REPLACED_WITH_NEW_VERSION"]
others = versions.reject { |v| included_states.include?(v.app_store_state) }

View file

@ -17,15 +17,15 @@ target = project.targets.find { |t| t.name == '{TARGET}' }
group = project.main_group
file_ref = group.new_file('{ADD_FILE_PATH}')
# 📌
# Add file to target
build_file = target.add_file_references([file_ref]).first
# 📌 Copy Bundle Resources에
# Explicitly add to Copy Bundle Resources
target.resources_build_phase.add_file_reference(file_ref)
project.save
puts "✅ {ADD_FILE_PATH} 파일이 프로젝트에 추가되고, Copy Bundle Resources에 포함되었습니다."
puts "✅ {ADD_FILE_PATH} has been added to the project and included in Copy Bundle Resources."
''';
/*

View file

@ -7,7 +7,6 @@ import 'package:dart_framework/utils/system_util.dart';
import 'package:oto_cli/oto/application.dart';
import 'package:oto_cli/oto/core/tag_system.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:oto_cli/oto/data/jenkins_data.dart';
import 'package:oto_cli/oto/pipeline/pipeline_exe_handle.dart';
enum CommandType {
@ -148,11 +147,11 @@ abstract class Command {
String getWorkspace(String? dataWorkspace) {
if (dataWorkspace == null) {
// workspace가 property에 workspace
// No workspace on model: prefer workspace from property
if (property.containsKey('workspace')) {
return getAbs(property['workspace']);
}
//property에도 workspace
// No workspace in property either: fall back to the current project root
else {
return getAbs(commonData!.workspace);
}

View file

@ -10,11 +10,9 @@ import 'package:oto_cli/oto/commands/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;
@ -26,8 +24,7 @@ class FTP {
: _host = host,
_port = port,
_user = user,
_pass = password,
_timeout = timeout {
_pass = password {
_ftp = FTPConnect(host,
port: port, user: user, pass: password, timeout: timeout);
}
@ -59,7 +56,6 @@ class FTP {
Map<String, FileType> map = {};
_currentRemoteDir = null;
try {
_isProgress = true;
var entries = localRemoteMap.entries;
for (var entry in entries) {
var local = Path.getNormalizePath(entry.key);
@ -79,9 +75,7 @@ class FTP {
await _upload(local, remote);
}
}
} finally {
_isProgress = false;
}
} finally {}
}
Future<void> _upload(String local, String remote) async {
@ -112,13 +106,11 @@ class FTP {
var c = Completer();
_cache = <String, Map<String, FileType>>{};
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;
@ -164,7 +156,6 @@ class FTP {
try {
await _ftp!.changeDirectory(path);
var list = await _ftp!.listDirectoryContent();
var count = 0;
for (var item in list) {
FileType? type;
switch (item.type) {
@ -175,15 +166,11 @@ class FTP {
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!;
// Application.log('$count = $type = $remotePath');
@ -199,7 +186,6 @@ class FTP {
Map<String, FileType>? map;
_currentRemoteDir = null;
try {
_isProgress = true;
for (var item in remoteList) {
var parent = Path.getParent(item);
var name = Path.getName(item);
@ -222,9 +208,7 @@ class FTP {
Application.log('Deleted: $item');
}
}
} finally {
_isProgress = false;
}
} finally {}
}
Future<bool> createFolder(String path) async {

View file

@ -40,7 +40,7 @@ class Upload extends Command {
var nameLocal = Path.getName(local);
if (nameLocal == '*') {
//(*)
// Wildcard (*): delete the remote folder first for a full replacement
//Remove before folder
if (remoteMap.containsKey(remote) &&
remoteMap[remote] == FileType.directory) {

View file

@ -25,7 +25,7 @@ class Git extends Command {
}
}
//
// Ignorable errors are suppressed
class GitCommit extends Command {
@override
Future execute(DataCommand command) async {
@ -50,11 +50,11 @@ class GitCommit extends Command {
class GitPull extends Command {
@override
Future execute(DataCommand command) async {
var data = DataGitPush.fromJson(getParam(command));
var data = DataGitPull.fromJson(getParam(command));
var shell = StringBuffer(getCDPath(workspace));
shell.write(
' $and git switch -c ${data.branch!} || git switch ${data.branch!}');
' $and git switch -c ${data.branch} || git switch ${data.branch}');
shell.write(' $and git pull origin ${data.branch}');
var process =
@ -64,7 +64,7 @@ class GitPull extends Command {
}
}
//
// Ignorable errors are suppressed
class GitPush extends Command {
@override
Future execute(DataCommand command) async {
@ -86,7 +86,7 @@ class GitPush extends Command {
}
}
// , , pull수행
// Checks out branch if not present locally; switches to it if it exists; optionally pulls
class GitCheckout extends Git {
@override
Future execute(DataCommand command) async {
@ -122,7 +122,7 @@ class GitCheckout extends Git {
}
}
//Git commit에 count
// Returns the total commit count
class GitCount extends Command {
@override
Future execute(DataCommand command) async {
@ -139,7 +139,7 @@ class GitCount extends Command {
}
}
//git의
// Returns the git revision hash
class GitRev extends Command {
@override
Future execute(DataCommand command) async {
@ -158,7 +158,7 @@ class GitRev extends Command {
}
}
//git
// Resets git working tree to a clean state
class GitReset extends Command {
@override
Future execute(DataCommand command) async {
@ -243,7 +243,7 @@ class GitStashApply extends Command {
}
}
//
// Ignorable errors are suppressed
class GitBranch extends Command {
@override
Future execute(DataCommand command) async {

View file

@ -77,7 +77,7 @@ class Jira extends Command {
slackData.blocks = [];
var blockSection = SlackBlockSection();
blockSection.text = SlackBlockText();
blockSection.text!.text = ':jira: 새 Jira 업무가 할당되었습니다.';
blockSection.text!.text = ':jira: A new Jira task has been assigned.';
slackData.blocks!.add(blockSection);
attachment.blocks = [];

View file

@ -10,7 +10,7 @@ import 'package:oto_cli/oto/commands/command.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'package:color/color.dart';
import 'package:dart_framework/utils/slack/slack_sender.dart';
import 'package:dart_framework/utils/slack/slack_data.dart' as slackData;
import 'package:dart_framework/utils/slack/slack_data.dart' as slack_data;
class Slack extends Command {
@override
@ -53,7 +53,7 @@ class SlackFile extends Command {
@override
Future execute(DataCommand command) async {
var data = DataSlackFile.fromJson(getParam(command));
var slackFile = slackData.SlackFile();
var slackFile = slack_data.SlackFile();
slackFile.title = data.title;
slackFile.initial_comment = data.initialComment;
slackFile.file = File(data.filePath);

View file

@ -5,7 +5,7 @@ import 'package:oto_cli/oto/commands/command.dart';
import 'package:oto_cli/oto/data/command_data.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
// ignore: depend_on_referenced_packages
// ignore: depend_on_referenced_packages, implementation_imports
import 'package:http_parser/src/media_type.dart';
class WebBase extends Command {

View file

@ -6,7 +6,7 @@ import 'package:dart_framework/platform/process.dart';
import 'package:dart_framework/utils/system_util.dart';
import 'package:oto_cli/cli/cli.dart';
import 'package:oto_cli/oto/application.dart';
import 'package:oto_cli/oto/data/jenkins_data.dart';
import 'package:oto_cli/oto/data/base_data.dart';
import 'package:oto_cli/oto/core/defined_data.dart';
abstract class DataComposer {

View file

@ -155,11 +155,11 @@ commands:
# token: bot-token
# channelId: n78a1wrdwjydzdoat6gci8cphe
# message:
# message: .
# message: Bot test message.
# props:
# attachments:
# - pretext: Mattermost Bot Test
# text: .
# text: If sent successfully, this message will appear in the channel.
''';
} else if (Platform.isWindows) {
@ -287,11 +287,11 @@ commands:
# token: bot-token
# channelId: n78a1wrdwjydzdoat6gci8cphe
# message:
# message: .
# message: Bot test message.
# props:
# attachments:
# - pretext: Mattermost Bot Test
# text: .
# text: If sent successfully, this message will appear in the channel.
''';
} else if (Platform.isLinux) {}

View file

@ -4,42 +4,42 @@ import 'dart:io';
import 'package:dart_framework/utils/path.dart';
import 'package:oto_cli/oto/application.dart';
/// OTO
/// OTO Tag System
///
/// YAML .
/// Handles the tag syntax used in YAML pipelines to reference dynamic values.
///
/// ## ( )
/// ## Read tags (value substitution)
///
/// ```yaml
/// exe: BuildiOS
/// param:
/// workspace: <!property.workspace> # property
/// version: <!property.build.version> # property
/// state: <!state.buildStep> # commandStates (ready/progress/complete)
/// workspace: <!property.workspace> # direct property access
/// version: <!property.build.version> # nested property access
/// state: <!state.buildStep> # commandStates access (ready/progress/complete)
/// ```
///
/// (StringAny).
/// toString() .
/// When a tag occupies the entire string, the original type is returned (StringAny).
/// When a tag is embedded within a string, it is inserted via toString().
///
/// ## ( )
/// ## Write tags (value storage)
///
/// ```yaml
/// exe: SetValue
/// param:
/// set-version: <@property.appVersion> # property
/// set-version: <@property.appVersion> # store value into property
/// ```
///
/// `setPropertyValue()` `setProperty()` .
/// Used when calling `setPropertyValue()` or `setProperty()`.
class TagSystem {
/// : `<!namespace.key>`
/// Read tag pattern: `<!namespace.key>`
static RegExp regEx = RegExp(r'(<!)([_a-zA-Z0-9.-]+)(>)');
/// : `<@namespace.key>`
/// Write tag pattern: `<@namespace.key>`
static RegExp regSetEx = RegExp(r'(<@)([_a-zA-Z0-9.-]+)(>)');
/// Map의 / .
/// Recursively replaces tags in all keys and values of a Map.
///
/// [replace] true이면 [map] .
/// If [replace] is true, modifies and returns the original [map] in place.
static Map<String, dynamic> replaceAllTagsMap(Map<String, dynamic> map,
{bool replace = false}) {
var newMap = <String, dynamic>{};
@ -73,7 +73,7 @@ class TagSystem {
return newMap;
}
/// List의 .
/// Recursively replaces tags in all elements of a List.
static List replaceAllTagsList(List list, {bool replace = false}) {
var newList = <dynamic>[];
for (var i = 0; i < list.length; ++i) {
@ -96,7 +96,7 @@ class TagSystem {
return newList;
}
/// (`<@property.key>`) property에 .
/// Stores a value into the property specified by a write tag (`<@property.key>`).
static Future setPropertyValue(String tag, dynamic value,
{bool? showPrint}) async {
var match = regSetEx.firstMatch(tag);
@ -125,10 +125,10 @@ class TagSystem {
return Future.value();
}
/// .
/// Looks up the actual value for a tag name.
///
/// - `property.key` `Application.instance.property[key]`
/// - `property.nested.key` Map
/// - `property.nested.key` traverses nested Maps
/// - `state.commandId` `Application.instance.commandStates[commandId]`
static dynamic _getTagValue(String value) {
var setted = false;
@ -183,7 +183,7 @@ class TagSystem {
return data;
}
/// (`.`, `..`) . .
/// Converts a relative path (`.`, `..`) to an absolute path, also resolving any tags within it.
static String getAbs(String path) {
path = getPathNormal(path);
if (path == '.' || path.startsWith('./') || path.startsWith('.\\')) {
@ -196,15 +196,15 @@ class TagSystem {
return File(path).absolute.path;
}
/// .
/// Normalizes path separators and resolves tags within the path.
static String getPathNormal(String path) {
return replaceTagValue(Path.getNormal(path));
}
/// (`<!...>`) .
/// Replaces tags (`<!...>`) within a string.
///
/// (dynamic) .
/// .
/// If the tag occupies the entire string, returns the original type (dynamic).
/// If the tag is embedded within a string, it is inserted as a string.
static dynamic replaceTagValue(String raw) {
var list = regEx.allMatches(raw);
if (list.length == 1) {
@ -223,7 +223,7 @@ class TagSystem {
return raw;
}
/// .
/// Returns the value in its original type from a string that contains exactly one tag.
static dynamic replaceSingleTagValue(String raw) {
dynamic value;
var list = regEx.allMatches(raw);

View file

@ -4,6 +4,28 @@ import 'package:json_annotation/json_annotation.dart';
part 'base_data.g.dart';
@JsonSerializable()
class DataCommon {
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;
DataCommon();
factory DataCommon.fromJson(Map<String, dynamic> json) =>
_$DataCommonFromJson(json);
Map<String, dynamic> toJson() => _$DataCommonToJson(this);
}
@JsonSerializable()
class DataParam {
DataParam();

View file

@ -6,6 +6,36 @@ part of 'base_data.dart';
// JsonSerializableGenerator
// **************************************************************************
DataCommon _$DataCommonFromJson(Map<String, dynamic> json) => DataCommon()
..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 num).toInt()
..buildID = (json['buildID'] as num).toInt()
..buildDisplayName = json['buildDisplayName'] as String
..gitBranch = json['gitBranch'] as String
..buildTag = json['buildTag'] as String;
Map<String, dynamic> _$DataCommonToJson(DataCommon instance) =>
<String, dynamic>{
'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,
};
DataParam _$DataParamFromJson(Map<String, dynamic> json) => DataParam()
..workspace = json['workspace'] as String?
..passExitCodes = (json['passExitCodes'] as List<dynamic>?)

View file

@ -126,11 +126,11 @@ class DataBuildiOS extends DataParam {
late Map<String, String>? settings;
//Version Setting
late String? target; // xcodeproj target을
late String? version; // autoBuildNumber = false
late String? buildNumber; // autoBuildNumber = false
late String? target; // if null, auto-detects the top-level target from xcodeproj
late String? version; // if set, disables autoBuildNumber
late String? buildNumber; // if set, disables autoBuildNumber
late bool? autoBuildNumber = false;
late String? apiKeyPath; //autoVersionNumber
late String? apiKeyPath; // required when using autoVersionNumber
//set
late String? setVersion;

View file

@ -1,24 +0,0 @@
import 'package:json_annotation/json_annotation.dart';
part 'jenkins_data.g.dart';
@JsonSerializable()
class DataCommon {
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;
DataCommon();
factory DataCommon.fromJson(Map<String, dynamic> json) =>
_$DataCommonFromJson(json);
Map<String, dynamic> toJson() => _$DataCommonToJson(this);
}

View file

@ -1,37 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'jenkins_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
DataCommon _$DataCommonFromJson(Map<String, dynamic> json) => DataCommon()
..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 num).toInt()
..buildID = (json['buildID'] as num).toInt()
..buildDisplayName = json['buildDisplayName'] as String
..gitBranch = json['gitBranch'] as String
..buildTag = json['buildTag'] as String;
Map<String, dynamic> _$DataCommonToJson(DataCommon instance) =>
<String, dynamic>{
'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,
};

View file

@ -13,9 +13,9 @@ class DataSlackSender extends DataParam {
DataSlackSender();
late String token;
late dynamic property; //oto object의
late Map<String, dynamic>? message; //yaml로
late String? messageJson; //json값으로
late dynamic property; // object created internally by oto
late Map<String, dynamic>? message; // value declared in YAML
late String? messageJson; // value declared as JSON string
late Map<String, String>? idMap;
factory DataSlackSender.fromJson(Map<String, dynamic> json) =>

View file

@ -99,7 +99,6 @@ class DataExeHandle implements DataValidator {
factory DataExeHandle.fromJson(Map<String, dynamic> json) =>
_$DataExeHandleFromJson(json);
@override
Map<String, dynamic> toJson() => _$DataExeHandleToJson(this);
}
@ -162,7 +161,6 @@ class DataContain extends DataValidator {
factory DataContain.fromJson(Map<String, dynamic> json) =>
_$DataContainFromJson(json);
@override
Map<String, dynamic> toJson() => _$DataContainToJson(this);
}
@ -225,7 +223,6 @@ class DataForeach extends DataValidator {
factory DataForeach.fromJson(Map<String, dynamic> json) =>
_$DataForeachFromJson(json);
@override
Map<String, dynamic> toJson() => _$DataForeachToJson(this);
}

View file

@ -103,11 +103,11 @@ class DataStringReplacePattern extends DataParam {
DataStringReplacePattern();
int? startAt = 0;
late String startPattern; //
late String endPattern; //
late String text; //
late String value; //
late String setValue; //
late String startPattern; // start delimiter pattern
late String endPattern; // end delimiter pattern
late String text; // original text to process
late String value; // replacement value
late String setValue; // property key to store the result
factory DataStringReplacePattern.fromJson(Map<String, dynamic> json) =>
_$DataStringReplacePatternFromJson(json);

View file

@ -9,7 +9,8 @@ environment:
# Add regular dependencies here.
dependencies:
# path: ^1.8.0
path: ^1.8.0
http: ^1.2.0
json_annotation: ^4.8.1
resource_importer: ^0.2.0
dart_framework: