fix: allow safe fresh-clone DryRun builds
This commit is contained in:
29
README.md
29
README.md
@@ -30,7 +30,7 @@
|
||||
- 선택 삭제
|
||||
- 로컬 저장/불러오기
|
||||
- Oracle/MariaDB 4개 국내 시장 종목 검색과 실행 파일 옆 `Res` INI 기반 컷/UI 구성
|
||||
- 빌드 시 읽기 전용 원본 `bin\Debug\Cuts`를 실행 파일 옆 `Cuts`로 같은 구조로 배치
|
||||
- 원본 자산이 있는 전체 빌드는 읽기 전용 `bin\Debug\Cuts`를 실행 파일 옆 `Cuts`로 같은 구조로 배치
|
||||
- 기존 운영 흐름을 반영한 `PREPARE` / `TAKE IN` / `NEXT` / `TAKE OUT` UI
|
||||
- `F8`, `Esc` 단축키와 원본 배경 선택용 F2의 안전 예약
|
||||
- 원본의 공급자 비의존 SQL/데이터 요청 71개를 .NET 8 Core 프로젝트로 이관
|
||||
@@ -61,6 +61,17 @@ Oracle/MariaDB 조회 계층, 원본 10초 DB 상태 감시, Tornado/K3D 어댑
|
||||
4. 실행 프로필이 `MBN_STOCK_WEBVIEW.LegacyParityApp - Explicit DryRun (Package)`인지 확인합니다.
|
||||
5. `F5`로 빌드·배포·실행합니다.
|
||||
|
||||
새로 받은 소스에 외부 `MBN_STOCK_N\...\bin\Debug`가 없어도 Debug/F5는 안전한
|
||||
**소스 전용 DryRun**으로 빌드됩니다. 이 모드에서는 검증된 내장 메뉴로 화면을 확인할 수 있지만
|
||||
DB 검색과 실제 장면 송출은 사용할 수 없습니다. Test/Live 송출 안전 게이트는 완화되지 않으며,
|
||||
소스 전용 빌드는 과거 로컬 송출 프로필·환경 변수·Development Live 실행 인수를 무시하고
|
||||
DryRun과 DB 미연결을 강제합니다. 출력과 중간 패키징 파일도 이전 전체 빌드의 `Cuts`, `Res`
|
||||
또는 DB 설정을 잘못 재사용하지 않도록 `bin\SourceOnly`와 `obj\SourceOnly`에 분리됩니다.
|
||||
|
||||
전체 장면·DB 기능이 필요한 개발자는 Git에 자산이나 자격증명을 추가하지 말고 승인된
|
||||
`MBN_STOCK_N\MBN_STOCK_N\bin\Debug`를 별도로 준비한 뒤
|
||||
[Cuts/Res 런타임 배치](docs/LEGACY_RUNTIME_ASSETS.md)의 전체 빌드 절차를 사용하세요.
|
||||
|
||||
이 프로젝트는 MSIX 패키지 ID가 필요한 앱입니다. `bin` 아래의 EXE를 직접 실행하지 말고 반드시 Package 프로필이나 설치된 MSIX로 실행하세요.
|
||||
|
||||
기존 중복 형식 오류(`CS0121`, `CS0436`)는 루트 앱 프로젝트가 하위 Core 소스까지 다시 컴파일하던 문제였으며, 현재 `src\**\*.cs`를 앱 컴파일 대상에서 제외해 해결했습니다. Visual Studio가 이전 진단을 계속 표시하면 `빌드 > 솔루션 정리` 후 다시 빌드하세요.
|
||||
@@ -100,12 +111,24 @@ dotnet build .\src\MBN_STOCK_WEBVIEW.LegacyParityApp\MBN_STOCK_WEBVIEW.LegacyPar
|
||||
```
|
||||
|
||||
위 명령은 서명되지 않은 개발 패키지를 `AppPackages`에 만듭니다. 배포용 MSIX는 `Package.appxmanifest`의 `Identity/Publisher` 값과 정확히 일치하는 인증서로 서명해야 합니다. 인증서와 개인 키는 저장소에 커밋하지 않습니다.
|
||||
Release MSIX는 전체 런타임 자산을 필수로 검증합니다. 원본 위치가 기본 형제 경로와 다르면
|
||||
`-p:LegacyRuntimeSourceRoot="D:\path\to\MBN_STOCK_N\MBN_STOCK_N\bin\Debug"`를
|
||||
추가해야 합니다.
|
||||
|
||||
## 구성과 보안
|
||||
|
||||
원본 `Res/MmoneyCoder.ini`의 값은 저장소에 복사하지 않았습니다. 일반 Visual Studio 개발 빌드는 원본 INI를 Git 밖에서 실행 파일 옆 `Res`로 복사해 기존 경로를 재현하고, MSIX는 `%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\Res\MmoneyCoder.ini` 또는 기존 `database.local.json`을 사용합니다. 환경 변수는 선택된 파일보다 우선합니다. 설정 우선순위, 보안과 실제 DB 스모크 방법은 [DB 운영 가이드](docs/DATABASE.md)에 정리했습니다.
|
||||
원본 `Res/MmoneyCoder.ini`의 값은 저장소에 복사하지 않았습니다. 원본 자산을 사용하는 일반
|
||||
Visual Studio 전체 개발 빌드만 원본 INI를 Git 밖에서 실행 파일 옆 `Res`로 복사해 기존 경로를
|
||||
재현합니다. 소스 전용 빌드는 자격증명 없이 DB 미연결 상태로 시작하고, MSIX는
|
||||
`%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\Res\MmoneyCoder.ini` 또는 기존
|
||||
`database.local.json`을 사용합니다. 환경 변수는 선택된 파일보다 우선합니다. 설정 우선순위,
|
||||
보안과 실제 DB 스모크 방법은 [DB 운영 가이드](docs/DATABASE.md)에 정리했습니다.
|
||||
|
||||
MSIX 설치 폴더는 읽기 전용입니다. 빌드 시 읽기 전용 원본 `Cuts` 179개와 허용된 UI용 `Res` 파일은 실행 출력·게시·MSIX에 포함되지만, DB 자격증명 INI·백업·인증서·벤더 DLL은 포함하지 않습니다. 현재 원본에 없는 `s5006`/`s6001` 영상과 일부 배경은 별도 원본 제작 자산을 확보해야 합니다. 로그, 플레이리스트, 운영자 설정과 미리보기 파일은 `ApplicationData.Current.LocalFolder` 또는 `LocalCacheFolder`에 저장합니다.
|
||||
MSIX 설치 폴더는 읽기 전용입니다. 전체 자산 빌드에서는 읽기 전용 원본 `Cuts` 179개와 허용된
|
||||
UI용 `Res` 파일이 실행 출력·게시·MSIX에 포함되지만, DB 자격증명 INI·백업·인증서·벤더 DLL은
|
||||
포함하지 않습니다. 현재 원본에 없는 `s5006`/`s6001` 영상과 일부 배경은 별도 원본 제작 자산을
|
||||
확보해야 합니다. 로그, 플레이리스트, 운영자 설정과 미리보기 파일은
|
||||
`ApplicationData.Current.LocalFolder` 또는 `LocalCacheFolder`에 저장합니다.
|
||||
|
||||
## 프로젝트 구조
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug
|
||||
|
||||
Visual Studio가 시작하는 프로젝트는
|
||||
`src\MBN_STOCK_WEBVIEW.LegacyParityApp\MBN_STOCK_WEBVIEW.LegacyParityApp.csproj`다.
|
||||
이 프로젝트는 빌드할 때 원본 파일을 저장소 안으로 복제하지 않고 외부 Content로 연결해
|
||||
실행 파일 옆의 다음 구조를 만든다.
|
||||
이 프로젝트는 원본 경로가 존재하는 전체 빌드에서 파일을 저장소 안으로 복제하지 않고 외부
|
||||
Content로 연결해 실행 파일 옆의 다음 구조를 만든다.
|
||||
|
||||
```text
|
||||
MBN_STOCK_WEBVIEW.LegacyParityApp.exe
|
||||
@@ -22,13 +22,41 @@ Res\...
|
||||
Web\...
|
||||
```
|
||||
|
||||
원본 경로가 다른 장비에서는 다음 빌드 속성으로 `bin\Debug` 위치를 지정한다.
|
||||
## 소스 전용 빌드와 전체 자산 빌드
|
||||
|
||||
기본 `LegacyRuntimeAssetsMode`는 `Auto`다.
|
||||
|
||||
- 기본 형제 경로에 원본이 있으면 모든 필수 자산을 검증하고 전체 자산 빌드를 만든다.
|
||||
- 원본이 없는 새 Git clone의 Debug/F5는 빌드를 막지 않고 소스 전용 DryRun을 만든다.
|
||||
- 원본 경로를 명시하거나 Release MSIX를 만들면 기본값이 `Required`로 바뀌어 누락 자산을
|
||||
다시 오류로 차단한다.
|
||||
|
||||
소스 전용 앱은 내장 컷/UI 목록으로 화면 검토가 가능하고 DB는 미연결 상태로 표시한다.
|
||||
과거 로컬 송출 프로필, 운영자 송출·배경 경로, 프로세스 환경 override와 Development Live 실행
|
||||
인수는 사용하지 않고 안전한 DryRun 기본값을 강제한다. 출력과 중간 패키징 파일은 이전 전체 빌드의
|
||||
자산이나 `MmoneyCoder.ini`를 재사용하지 않도록 다음 별도 경로에 생성된다.
|
||||
|
||||
```text
|
||||
src\MBN_STOCK_WEBVIEW.LegacyParityApp\bin\SourceOnly\...
|
||||
src\MBN_STOCK_WEBVIEW.LegacyParityApp\obj\SourceOnly\...
|
||||
```
|
||||
|
||||
원본 경로가 다른 장비에서 전체 기능을 빌드하려면 다음 속성으로 `bin\Debug` 위치를 지정한다.
|
||||
명시한 경로는 자동으로 `Required` 취급된다.
|
||||
|
||||
```text
|
||||
/p:LegacyRuntimeSourceRoot="D:\path\to\MBN_STOCK_N\MBN_STOCK_N\bin\Debug"
|
||||
```
|
||||
|
||||
필수 폴더와 `Cuts\5001.t2s`, 주요 UI INI가 없으면 빌드는 원인을 표시하고 중단한다.
|
||||
필수 폴더와 `Cuts\5001.t2s`, 주요 UI INI가 없으면 전체 자산 빌드는 원인을 표시하고 중단한다.
|
||||
경로 탐색이나 CI에서 소스 전용 동작을 명시적으로 확인할 때만 다음 조합을 사용할 수 있다.
|
||||
이는 전체 기능 또는 배포 승인용 빌드가 아니다.
|
||||
|
||||
```text
|
||||
/p:LegacyRuntimeAssetsMode=Auto
|
||||
```
|
||||
|
||||
Git에는 `Cuts`, DB 자격증명, 벤더 DLL, 라이선스, 인증서 또는 로컬 구성을 추가하지 않는다.
|
||||
|
||||
## Cuts
|
||||
|
||||
|
||||
@@ -44,10 +44,19 @@ public partial class App : Application
|
||||
#else
|
||||
const bool isDebugBuild = false;
|
||||
#endif
|
||||
var developmentLive = DevelopmentLiveLaunchBootstrap.TryApply(
|
||||
DevelopmentLiveLaunchBootstrap.ResolveLaunchArguments(
|
||||
|
||||
#if SOURCE_ONLY_RUNTIME
|
||||
const bool isSourceOnlyBuild = true;
|
||||
#else
|
||||
const bool isSourceOnlyBuild = false;
|
||||
#endif
|
||||
var launchArguments = isSourceOnlyBuild
|
||||
? null
|
||||
: DevelopmentLiveLaunchBootstrap.ResolveLaunchArguments(
|
||||
args.Arguments,
|
||||
Environment.GetCommandLineArgs()),
|
||||
Environment.GetCommandLineArgs());
|
||||
var developmentLive = DevelopmentLiveLaunchBootstrap.TryApply(
|
||||
launchArguments,
|
||||
isDebugBuild);
|
||||
System.Diagnostics.Debug.WriteLine(
|
||||
$"Development Live bootstrap: {developmentLive.Code}");
|
||||
|
||||
@@ -26,12 +26,45 @@
|
||||
<Version>0.1.0</Version>
|
||||
<!--
|
||||
Keep the legacy runtime assets outside this repository while reproducing the
|
||||
original executable-folder layout. Override this property at build time when
|
||||
the read-only MBN_STOCK_N checkout is in a different location:
|
||||
original executable-folder layout. A fresh source checkout builds in safe
|
||||
source-only DryRun mode when the sibling runtime tree is absent. Supplying an
|
||||
explicit root or creating a Release MSIX makes the assets required unless the
|
||||
caller explicitly selects Auto mode:
|
||||
/p:LegacyRuntimeSourceRoot="D:\path\to\MBN_STOCK_N\bin\Debug"
|
||||
/p:LegacyRuntimeAssetsMode=Auto
|
||||
-->
|
||||
<LegacyRuntimeSourceRootWasSpecified
|
||||
Condition="'$(LegacyRuntimeSourceRoot)' != ''">true</LegacyRuntimeSourceRootWasSpecified>
|
||||
<LegacyRuntimeSourceRoot Condition="'$(LegacyRuntimeSourceRoot)' == ''">$(MSBuildProjectDirectory)\..\..\..\MBN_STOCK_N\MBN_STOCK_N\bin\Debug</LegacyRuntimeSourceRoot>
|
||||
<LegacyRuntimeSourceRoot>$([System.IO.Path]::GetFullPath('$(LegacyRuntimeSourceRoot)'))</LegacyRuntimeSourceRoot>
|
||||
<LegacyRuntimeAssetsMode
|
||||
Condition="'$(LegacyRuntimeAssetsMode)' == '' and
|
||||
('$(LegacyRuntimeSourceRootWasSpecified)' == 'true' or
|
||||
('$(Configuration)' == 'Release' and
|
||||
('$(GenerateAppxPackageOnBuild)' == 'true' or
|
||||
'$(PublishAppxPackage)' == 'true')))">Required</LegacyRuntimeAssetsMode>
|
||||
<LegacyRuntimeAssetsMode
|
||||
Condition="'$(LegacyRuntimeAssetsMode)' == ''">Auto</LegacyRuntimeAssetsMode>
|
||||
<LegacyRuntimeAssetsEnabled
|
||||
Condition="Exists('$(LegacyRuntimeSourceRoot)')">true</LegacyRuntimeAssetsEnabled>
|
||||
<LegacyRuntimeAssetsEnabled
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == ''">false</LegacyRuntimeAssetsEnabled>
|
||||
<!--
|
||||
Never share an executable directory with a previous full-runtime build.
|
||||
Otherwise omitted Cuts/Res (including the local DB INI) could remain stale
|
||||
and be consumed by a nominal source-only launch.
|
||||
-->
|
||||
<BaseOutputPath
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' != 'true'">bin\SourceOnly\</BaseOutputPath>
|
||||
<IntermediateOutputPath
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' != 'true'">obj\SourceOnly\$(Platform)\$(Configuration)\</IntermediateOutputPath>
|
||||
<DefineConstants
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' != 'true'">$(DefineConstants);SOURCE_ONLY_RUNTIME</DefineConstants>
|
||||
<LegacyRuntimeAssetsValidationEnabled
|
||||
Condition="'$(LegacyRuntimeAssetsMode)' == 'Required' or
|
||||
'$(LegacyRuntimeAssetsEnabled)' == 'true'">true</LegacyRuntimeAssetsValidationEnabled>
|
||||
<LegacyRuntimeAssetsValidationEnabled
|
||||
Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == ''">false</LegacyRuntimeAssetsValidationEnabled>
|
||||
<LegacyCutsSourceRoot>$(LegacyRuntimeSourceRoot)\Cuts</LegacyCutsSourceRoot>
|
||||
<LegacyResSourceRoot>$(LegacyRuntimeSourceRoot)\Res</LegacyResSourceRoot>
|
||||
</PropertyGroup>
|
||||
@@ -40,7 +73,9 @@
|
||||
<!-- Closed runtime contract: every active alias and non-external scene asset must exist. -->
|
||||
<LegacyRequiredScene Include="5001;N5001;5006;5011;5016;50160;5023;5024;5025;5026;5029;8018;8032;5032;5037;5074;5076;5077;5078;5079;5080;5081;5082;5083;5084;5085;5086;50860;5087;5088;6001;6067;8001;8002;8003;8035;8061;8040;8046;8051;8056;8067;5068;5070;5072" />
|
||||
<LegacyRequiredBuiltInAsset Include="images\주유기merge.png;images\35752913_l.jpg;Images\그림_빨강.png;Images\그림_검정.png;Images\그림_파랑.png;Images\프리마켓.png;Images\애프터마켓.png;Images\KRX.png;Images\NXT.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<!-- Explicit allowlist: these legacy Res assets contain UI/menu data only. -->
|
||||
<LegacyPackagedResAsset Include="$(LegacyResSourceRoot)\a.png" />
|
||||
<LegacyPackagedResAsset Include="$(LegacyResSourceRoot)\aa.png" />
|
||||
@@ -88,7 +123,9 @@
|
||||
<LegacyFinancialPreviewAsset Include="$(LegacyResSourceRoot)\profit.bmp" />
|
||||
<!-- Official legacy wordmark, projected into the trusted Web root without a remote dependency. -->
|
||||
<LegacyBrandAsset Include="$(LegacyResSourceRoot)\logo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\LegacySceneRuntimeFactory.cs"
|
||||
Link="Runtime\LegacySceneRuntimeFactory.cs" />
|
||||
<Content Include="..\..\Assets\LockScreenLogo.scale-200.png" Link="Assets\LockScreenLogo.scale-200.png" />
|
||||
@@ -111,25 +148,29 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="$(LegacyCutsSourceRoot)\**\*">
|
||||
<Content Include="$(LegacyCutsSourceRoot)\**\*"
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<Link>Cuts\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<TargetPath>Cuts\%(RecursiveDir)%(Filename)%(Extension)</TargetPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="@(LegacyPackagedResAsset)">
|
||||
<Content Include="@(LegacyPackagedResAsset)"
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<Link>Res\%(Filename)%(Extension)</Link>
|
||||
<TargetPath>Res\%(Filename)%(Extension)</TargetPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="@(LegacyFinancialPreviewAsset)">
|
||||
<Content Include="@(LegacyFinancialPreviewAsset)"
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<Link>Web\Previews\%(Filename)%(Extension)</Link>
|
||||
<TargetPath>Web\Previews\%(Filename)%(Extension)</TargetPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="@(LegacyBrandAsset)">
|
||||
<Content Include="@(LegacyBrandAsset)"
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<Link>Web\Brand\%(Filename)%(Extension)</Link>
|
||||
<TargetPath>Web\Brand\%(Filename)%(Extension)</TargetPath>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
@@ -144,38 +185,59 @@
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="ValidateLegacyRuntimeAssets" BeforeTargets="PrepareForBuild">
|
||||
<Error Condition="!Exists('$(LegacyRuntimeSourceRoot)')"
|
||||
Text="Legacy runtime source root was not found: $(LegacyRuntimeSourceRoot). Set /p:LegacyRuntimeSourceRoot to the read-only MBN_STOCK_N bin\Debug directory." />
|
||||
<Error Condition="!Exists('$(LegacyCutsSourceRoot)')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsMode)' != 'Auto' and
|
||||
'$(LegacyRuntimeAssetsMode)' != 'Required'"
|
||||
Text="LegacyRuntimeAssetsMode must be Auto or Required." />
|
||||
<Message Condition="'$(LegacyRuntimeAssetsMode)' == 'Auto' and
|
||||
'$(LegacyRuntimeAssetsEnabled)' != 'true'"
|
||||
Importance="high"
|
||||
Text="Legacy runtime assets were not found. Building the safe source-only DryRun app. Full runtime use requires a Required build with the approved external root." />
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyRuntimeSourceRoot)')"
|
||||
Text="Legacy runtime source root was not found: $(LegacyRuntimeSourceRoot). Set /p:LegacyRuntimeSourceRoot to the read-only MBN_STOCK_N bin\Debug directory, or use /p:LegacyRuntimeAssetsMode=Auto for a source-only DryRun build." />
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyCutsSourceRoot)')"
|
||||
Text="Legacy Cuts source directory was not found: $(LegacyCutsSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)')"
|
||||
Text="Legacy Res source directory was not found: $(LegacyResSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\pie.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\Grow.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\sell.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\profit.bmp')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
(!Exists('$(LegacyResSourceRoot)\pie.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\Grow.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\sell.bmp') or
|
||||
!Exists('$(LegacyResSourceRoot)\profit.bmp'))"
|
||||
Text="The four required GraphE preview bitmaps are missing from $(LegacyResSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\logo.png')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\logo.png')"
|
||||
Text="The required legacy brand wordmark is missing: Res\logo.png." />
|
||||
<Error Condition="!Exists('$(LegacyCutsSourceRoot)\%(LegacyRequiredScene.Identity).t2s')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyCutsSourceRoot)\%(LegacyRequiredScene.Identity).t2s')"
|
||||
Text="A required active legacy scene file is missing: Cuts\%(LegacyRequiredScene.Identity).t2s." />
|
||||
<Error Condition="!Exists('$(LegacyCutsSourceRoot)\%(LegacyRequiredBuiltInAsset.Identity)')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyCutsSourceRoot)\%(LegacyRequiredBuiltInAsset.Identity)')"
|
||||
Text="A required built-in legacy scene asset is missing: Cuts\%(LegacyRequiredBuiltInAsset.Identity)." />
|
||||
<Error Condition="'$(GenerateAppxPackageOnBuild)' != 'true' and
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
'$(GenerateAppxPackageOnBuild)' != 'true' and
|
||||
'$(PublishAppxPackage)' != 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\MmoneyCoder.ini')"
|
||||
Text="The required legacy database settings file Res\MmoneyCoder.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\종목.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\종목.ini')"
|
||||
Text="The required legacy stock menu file Res\종목.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\업종_코스피.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\업종_코스피.ini')"
|
||||
Text="The required legacy KOSPI industry menu file Res\업종_코스피.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\업종_코스닥.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\업종_코스닥.ini')"
|
||||
Text="The required legacy KOSDAQ industry menu file Res\업종_코스닥.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\해외.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\해외.ini')"
|
||||
Text="The required legacy overseas menu file Res\해외.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\환율.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\환율.ini')"
|
||||
Text="The required legacy exchange menu file Res\환율.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
<Error Condition="!Exists('$(LegacyResSourceRoot)\지수.ini')"
|
||||
<Error Condition="'$(LegacyRuntimeAssetsValidationEnabled)' == 'true' and
|
||||
!Exists('$(LegacyResSourceRoot)\지수.ini')"
|
||||
Text="The required legacy index menu file Res\지수.ini is missing from $(LegacyRuntimeSourceRoot)." />
|
||||
</Target>
|
||||
|
||||
@@ -187,7 +249,7 @@
|
||||
executable-relative layout only for ordinary local output. The CPS-only
|
||||
up-to-date items below are not package payload candidates.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<ItemGroup Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true'">
|
||||
<!-- These CPS-only items participate in Visual Studio's fast up-to-date check. -->
|
||||
<UpToDateCheckInput Include="$(LegacyResSourceRoot)\MmoneyCoder.ini" />
|
||||
<UpToDateCheckBuilt Include="$(TargetDir)Res\MmoneyCoder.ini" />
|
||||
@@ -197,7 +259,9 @@
|
||||
AfterTargets="Build"
|
||||
Inputs="$(LegacyResSourceRoot)\MmoneyCoder.ini"
|
||||
Outputs="$(TargetDir)Res\MmoneyCoder.ini"
|
||||
Condition="'$(GenerateAppxPackageOnBuild)' != 'true' and '$(PublishAppxPackage)' != 'true'">
|
||||
Condition="'$(LegacyRuntimeAssetsEnabled)' == 'true' and
|
||||
'$(GenerateAppxPackageOnBuild)' != 'true' and
|
||||
'$(PublishAppxPackage)' != 'true'">
|
||||
<MakeDir Directories="$(TargetDir)Res" />
|
||||
<Copy SourceFiles="$(LegacyResSourceRoot)\MmoneyCoder.ini"
|
||||
DestinationFiles="$(TargetDir)Res\MmoneyCoder.ini"
|
||||
|
||||
@@ -36,7 +36,8 @@ public sealed partial class MainWindow
|
||||
PlayoutOptionsLoader.DefaultPath);
|
||||
_playoutOptions = PlayoutOptionsLoader.Load(
|
||||
operatorSceneDirectory: _appliedOperatorSettings.SceneDirectory,
|
||||
operatorBackgroundDirectory: _appliedOperatorSettings.BackgroundDirectory);
|
||||
operatorBackgroundDirectory: _appliedOperatorSettings.BackgroundDirectory,
|
||||
forceSafeDryRun: IsSourceOnlyBuild);
|
||||
ResetRefreshState();
|
||||
var startupComposition = LegacyParityStartupCompositionResolver.Resolve(
|
||||
_playoutOptions,
|
||||
|
||||
@@ -22,6 +22,18 @@ public sealed partial class MainWindow : Window
|
||||
private const uint WmDpiChanged = 0x02E0;
|
||||
private const nuint InteractionMetricsSubclassId = 0x4D424E49;
|
||||
|
||||
private static bool IsSourceOnlyBuild
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SOURCE_ONLY_RUNTIME
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CancellationTokenSource _lifetimeCancellation = new();
|
||||
private readonly SemaphoreSlim _intentGate = new(1, 1);
|
||||
private readonly SemaphoreSlim _orderedLocalSelectionIntentGate = new(1, 1);
|
||||
@@ -101,6 +113,12 @@ public sealed partial class MainWindow : Window
|
||||
string? initializationError = null;
|
||||
try
|
||||
{
|
||||
if (IsSourceOnlyBuild)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Source-only builds do not initialize a database runtime.");
|
||||
}
|
||||
|
||||
// A Gate A plan pins database.local.json by path, size and SHA-256.
|
||||
// Never inspect either packaged or operator-selected legacy INI files
|
||||
// during that one-shot validation process.
|
||||
@@ -170,8 +188,9 @@ public sealed partial class MainWindow : Window
|
||||
}
|
||||
catch
|
||||
{
|
||||
initializationError =
|
||||
"데이터베이스가 설정되지 않았습니다. 로컬 설정을 확인하세요.";
|
||||
initializationError = IsSourceOnlyBuild
|
||||
? "소스 전용 DryRun에서는 데이터베이스 연결을 사용하지 않습니다."
|
||||
: "데이터베이스가 설정되지 않았습니다. 로컬 설정을 확인하세요.";
|
||||
stockLookup = new UnavailableStockLookup(initializationError);
|
||||
industrySelectionService = new UnavailableIndustrySelectionService(
|
||||
initializationError);
|
||||
|
||||
@@ -203,6 +203,7 @@
|
||||
<header class="workspace-header">
|
||||
<div class="brand">
|
||||
<span class="brand-wordmark-plate">
|
||||
<span class="brand-wordmark-fallback">매일경제TV</span>
|
||||
<img class="brand-wordmark" src="Brand/logo.png" width="176" height="43"
|
||||
alt="매일경제TV" draggable="false">
|
||||
</span>
|
||||
|
||||
@@ -945,6 +945,23 @@ input[type="checkbox"], input[type="radio"] { accent-color: var(--ui-accent); }
|
||||
background: #fff;
|
||||
box-shadow: 0 0 0 1px rgba(15, 23, 42, .08), 0 1px 2px rgba(15, 23, 42, .08);
|
||||
}
|
||||
.brand-wordmark-fallback,
|
||||
.brand-wordmark {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.brand-wordmark-fallback {
|
||||
visibility: hidden;
|
||||
color: #172033;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.045em;
|
||||
}
|
||||
.brand-wordmark-plate.wordmark-fallback-active .brand-wordmark-fallback {
|
||||
visibility: visible;
|
||||
}
|
||||
.brand-wordmark-plate.wordmark-fallback-active .brand-wordmark {
|
||||
display: none;
|
||||
}
|
||||
.brand-wordmark {
|
||||
display: block;
|
||||
width: 142px;
|
||||
|
||||
@@ -17,6 +17,19 @@
|
||||
const shell = document.querySelector(".operator-shell");
|
||||
const splitter = document.getElementById("workspace-splitter");
|
||||
const playlistPanel = document.querySelector(".playlist-panel");
|
||||
const brandWordmark = document.querySelector("img.brand-wordmark");
|
||||
const brandWordmarkPlate = brandWordmark?.closest(".brand-wordmark-plate");
|
||||
|
||||
function showBrandWordmarkFallback() {
|
||||
brandWordmarkPlate?.classList.add("wordmark-fallback-active");
|
||||
}
|
||||
|
||||
if (brandWordmark) {
|
||||
brandWordmark.addEventListener("error", showBrandWordmarkFallback, { once: true });
|
||||
if (brandWordmark.complete && brandWordmark.naturalWidth === 0) {
|
||||
showBrandWordmarkFallback();
|
||||
}
|
||||
}
|
||||
|
||||
if (!region || !navigation || !navigationItems || !toggle || !toggleLabel ||
|
||||
!stockTab || !settingsTab || !stockWorkspace || !catalogWorkspace ||
|
||||
|
||||
@@ -22,8 +22,17 @@ public static class PlayoutOptionsLoader
|
||||
public static PlayoutOptions Load(
|
||||
string? path = null,
|
||||
string? operatorSceneDirectory = null,
|
||||
string? operatorBackgroundDirectory = null)
|
||||
string? operatorBackgroundDirectory = null,
|
||||
bool forceSafeDryRun = false)
|
||||
{
|
||||
// Source-only builds are for code/UI review on machines without the
|
||||
// approved runtime tree. Do not consume a previous machine-local playout
|
||||
// profile or process override in that build flavor.
|
||||
if (forceSafeDryRun)
|
||||
{
|
||||
return new PlayoutOptions();
|
||||
}
|
||||
|
||||
var options = LoadJson(path ?? DefaultPath);
|
||||
ApplyOperatorAssetOverrides(
|
||||
options,
|
||||
|
||||
@@ -204,6 +204,12 @@ test("appearance controls and accessibility preferences retain clear feedback",
|
||||
test("workspace header keeps one concise title and the live native connection state", () => {
|
||||
assert.match(markup,
|
||||
/class="brand"[\s\S]*?class="brand-wordmark-plate"[\s\S]*?<img class="brand-wordmark" src="Brand\/logo\.png" width="176" height="43"[\s\S]*?alt="매일경제TV" draggable="false">[\s\S]*?class="brand-divider" aria-hidden="true"[\s\S]*?class="brand-product">V-STOCK<\/span>/);
|
||||
assert.match(markup,
|
||||
/class="brand-wordmark-fallback">매일경제TV<\/span>/);
|
||||
assert.match(styles,
|
||||
/\.brand-wordmark-plate\.wordmark-fallback-active \.brand-wordmark-fallback\s*\{[^}]*visibility:\s*visible/);
|
||||
assert.match(styles,
|
||||
/\.brand-wordmark-plate\.wordmark-fallback-active \.brand-wordmark\s*\{[^}]*display:\s*none/);
|
||||
assert.doesNotMatch(markup, /class="brand-symbol"|class="brand-lockup"|>VRi</);
|
||||
assert.match(markup,
|
||||
/class="workspace-heading"[\s\S]*?class="workspace-heading-icon"[\s\S]*?<div class="workspace-heading-copy">[\s\S]*?<h1 id="workspace-title">[^<]+<\/h1>/);
|
||||
|
||||
@@ -510,6 +510,15 @@ test("workspace navigation stores only the approved local presentation preferenc
|
||||
assert.doesNotMatch(navigation, /postMessage|sessionStorage|send\s*\(/);
|
||||
});
|
||||
|
||||
test("missing external wordmark uses the source-only text fallback", () => {
|
||||
assert.match(navigation,
|
||||
/brandWordmark\.addEventListener\("error", showBrandWordmarkFallback, \{ once: true \}\)/);
|
||||
assert.match(navigation,
|
||||
/brandWordmark\.complete && brandWordmark\.naturalWidth === 0/);
|
||||
assert.match(navigation,
|
||||
/classList\.add\("wordmark-fallback-active"\)/);
|
||||
});
|
||||
|
||||
test("splitter clamps pointer and keyboard resizing and Escape restores the gesture", () => {
|
||||
for (const declaration of [
|
||||
/const scheduleMinimumPercent = 34/,
|
||||
|
||||
@@ -88,6 +88,13 @@ public sealed class LegacyDevelopmentLiveStartupContractTests
|
||||
Assert.Contains("#if DEBUG", app, StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isDebugBuild = true;", app, StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isDebugBuild = false;", app, StringComparison.Ordinal);
|
||||
Assert.Contains("#if SOURCE_ONLY_RUNTIME", app, StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isSourceOnlyBuild = true;", app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("const bool isSourceOnlyBuild = false;", app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("isSourceOnlyBuild", app, StringComparison.Ordinal);
|
||||
Assert.Contains("? null", app, StringComparison.Ordinal);
|
||||
Assert.Contains("args.Arguments", app, StringComparison.Ordinal);
|
||||
Assert.Contains("Environment.GetCommandLineArgs()", app, StringComparison.Ordinal);
|
||||
Assert.Contains("ResolveLaunchArguments(", app, StringComparison.Ordinal);
|
||||
|
||||
@@ -268,6 +268,154 @@ public sealed class LegacyRuntimeAssetDeploymentContractTests
|
||||
Assert.Single(rejectionTarget.Elements("Error"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreshCloneUsesIsolatedSourceOnlyOutputWithoutWeakeningFullBuilds()
|
||||
{
|
||||
var project = XDocument.Load(ProjectPath);
|
||||
|
||||
var sourceRootWasSpecified = Assert.Single(
|
||||
project.Descendants("LegacyRuntimeSourceRootWasSpecified"));
|
||||
Assert.Equal(
|
||||
"'$(LegacyRuntimeSourceRoot)' != ''",
|
||||
sourceRootWasSpecified.Attribute("Condition")?.Value);
|
||||
|
||||
var modes = project.Descendants("LegacyRuntimeAssetsMode").ToArray();
|
||||
Assert.Equal(2, modes.Length);
|
||||
var requiredMode = Assert.Single(
|
||||
modes,
|
||||
mode => string.Equals(mode.Value, "Required", StringComparison.Ordinal));
|
||||
var requiredCondition = requiredMode.Attribute("Condition")?.Value;
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeSourceRootWasSpecified)",
|
||||
requiredCondition,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"'$(Configuration)' == 'Release'",
|
||||
requiredCondition,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"$(GenerateAppxPackageOnBuild)",
|
||||
requiredCondition,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains("$(PublishAppxPackage)", requiredCondition, StringComparison.Ordinal);
|
||||
|
||||
var autoMode = Assert.Single(
|
||||
modes,
|
||||
mode => string.Equals(mode.Value, "Auto", StringComparison.Ordinal));
|
||||
Assert.Equal(
|
||||
"'$(LegacyRuntimeAssetsMode)' == ''",
|
||||
autoMode.Attribute("Condition")?.Value);
|
||||
|
||||
var assetsEnabled = Assert.Single(
|
||||
project.Descendants("LegacyRuntimeAssetsEnabled"),
|
||||
element => (element.Attribute("Condition")?.Value ?? string.Empty)
|
||||
.Contains("Exists('$(LegacyRuntimeSourceRoot)')", StringComparison.Ordinal));
|
||||
Assert.Equal("true", assetsEnabled.Value);
|
||||
|
||||
var sourceOnlyOutput = Assert.Single(project.Descendants("BaseOutputPath"));
|
||||
Assert.Equal(@"bin\SourceOnly\", sourceOnlyOutput.Value);
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsEnabled)",
|
||||
sourceOnlyOutput.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
var sourceOnlyIntermediate = Assert.Single(
|
||||
project.Descendants("IntermediateOutputPath"));
|
||||
Assert.Equal(
|
||||
@"obj\SourceOnly\$(Platform)\$(Configuration)\",
|
||||
sourceOnlyIntermediate.Value);
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsEnabled)",
|
||||
sourceOnlyIntermediate.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
var sourceOnlyConstants = Assert.Single(project.Descendants("DefineConstants"));
|
||||
Assert.Contains(
|
||||
"SOURCE_ONLY_RUNTIME",
|
||||
sourceOnlyConstants.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsEnabled)",
|
||||
sourceOnlyConstants.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var validationTarget = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"ValidateLegacyRuntimeAssets",
|
||||
StringComparison.Ordinal));
|
||||
var sourceOnlyMessage = Assert.Single(validationTarget.Elements("Message"));
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsMode)",
|
||||
sourceOnlyMessage.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"source-only DryRun",
|
||||
sourceOnlyMessage.Attribute("Text")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var runtimeValidationErrors = validationTarget.Elements("Error")
|
||||
.Where(error => (error.Attribute("Condition")?.Value ?? string.Empty)
|
||||
.Contains("Exists(", StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
Assert.NotEmpty(runtimeValidationErrors);
|
||||
Assert.All(runtimeValidationErrors, error =>
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsValidationEnabled)",
|
||||
error.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal));
|
||||
|
||||
string[] externalContentIncludes =
|
||||
[
|
||||
@"$(LegacyCutsSourceRoot)\**\*",
|
||||
"@(LegacyPackagedResAsset)",
|
||||
"@(LegacyFinancialPreviewAsset)",
|
||||
"@(LegacyBrandAsset)"
|
||||
];
|
||||
Assert.All(externalContentIncludes, include =>
|
||||
{
|
||||
var content = Assert.Single(
|
||||
project.Descendants("Content"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Include")?.Value,
|
||||
include,
|
||||
StringComparison.Ordinal));
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsEnabled)",
|
||||
content.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
});
|
||||
|
||||
var databaseCopy = Assert.Single(
|
||||
project.Descendants("Target"),
|
||||
element => string.Equals(
|
||||
element.Attribute("Name")?.Value,
|
||||
"CopyLegacyDatabaseIniToLocalOutput",
|
||||
StringComparison.Ordinal));
|
||||
Assert.Contains(
|
||||
"$(LegacyRuntimeAssetsEnabled)",
|
||||
databaseCopy.Attribute("Condition")?.Value,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var mainWindow = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"MainWindow.xaml.cs"));
|
||||
Assert.Contains("#if SOURCE_ONLY_RUNTIME", mainWindow, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"Source-only builds do not initialize a database runtime.",
|
||||
mainWindow,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
var playout = File.ReadAllText(Path.Combine(
|
||||
RepositoryRoot,
|
||||
"src",
|
||||
"MBN_STOCK_WEBVIEW.LegacyParityApp",
|
||||
"MainWindow.Playout.cs"));
|
||||
Assert.Contains("forceSafeDryRun: IsSourceOnlyBuild", playout,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyRuntimeSourceRootIsOverrideableAndValidatedBeforeBuild()
|
||||
{
|
||||
|
||||
@@ -62,6 +62,32 @@ public sealed class PlayoutOptionsLoaderTests
|
||||
Assert.Null(options.MaximumAutomaticRefreshesPerTakeIn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_SourceOnlyIgnoresLocalProfileOperatorRootsAndEnvironment()
|
||||
{
|
||||
using var environment = ClearedEnvironment()
|
||||
.Set("MBN_STOCK_PLAYOUT_MODE", "Live")
|
||||
.Set("MBN_STOCK_PLAYOUT_HOST", "configured-live-host")
|
||||
.Set("MBN_STOCK_PLAYOUT_SCENE_DIRECTORY", "C:\\configured-live-scenes")
|
||||
.Set(
|
||||
PlayoutOptionsLoader.LiveAuthorizationEnvironmentVariable,
|
||||
PlayoutOptionsLoader.RequiredLiveAuthorizationValue);
|
||||
using var file = TemporaryJsonFile.Create("{ intentionally-invalid-json");
|
||||
|
||||
var options = PlayoutOptionsLoader.Load(
|
||||
file.Path,
|
||||
operatorSceneDirectory: "C:\\operator-scenes",
|
||||
operatorBackgroundDirectory: "C:\\operator-backgrounds",
|
||||
forceSafeDryRun: true);
|
||||
|
||||
Assert.Equal(PlayoutMode.DryRun, options.Mode);
|
||||
Assert.Equal("127.0.0.1", options.Host);
|
||||
Assert.Null(options.SceneDirectory);
|
||||
Assert.Null(options.LegacyBackgroundDirectory);
|
||||
Assert.False(options.TrustedLiveOutputEnabled);
|
||||
Assert.Empty(options.TestSceneAllowlist);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyExecutableAssetDefaults_UsesCutsBesideExecutable()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user