diff --git a/.coderabbit.yaml b/.coderabbit.yaml index f56d75abf..256439954 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -39,6 +39,6 @@ reviews: The compatibility gate is an exact protocol match, not a release-semver range. If a change makes a CLI/package from the previous protocol generation unable to interoperate, require both `cli/contract.json` `protocolVersion` and `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION` to be incremented in the same PR. - Do not request a protocol bump for ordinary CLI features, bug fixes, UI changes, documentation, or additive wire-format changes that older counterparts can safely ignore. `cliVersion` and `default-tools.json` `version` are release-please managed release metadata. If a PR bumps `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION`, require a matching update to `CliConstants.MINIMUM_REQUIRED_CLI_VERSION` after the corresponding published CLI release tag is available, because setup must install a CLI release that advertises the required protocol. + Do not request a protocol bump for ordinary CLI features, bug fixes, UI changes, documentation, or additive wire-format changes that older counterparts can safely ignore. `projectRunnerVersion` and `default-tools.json` `version` are release-please managed release metadata. If a PR bumps `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION`, require a matching update to `CliConstants.MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` after the corresponding published project runner release tag is available, because setup must install a project runner release that advertises the required protocol. chat: auto_reply: true diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 016fc96e8..aad6ada98 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -2,7 +2,7 @@ name: Build and Test Unity CLI Loop on: push: - tags: ["v*", "cli-v*", "dispatcher-v*"] + tags: ["v*", "uloop-project-runner-v*", "dispatcher-v*"] pull_request: branches: [main, v3-beta] workflow_dispatch: diff --git a/.github/workflows/native-cli-publish.yml b/.github/workflows/native-cli-publish.yml index 8c4f39be5..cc9e8f393 100644 --- a/.github/workflows/native-cli-publish.yml +++ b/.github/workflows/native-cli-publish.yml @@ -12,7 +12,7 @@ on: required: false default: "" release-tag: - description: "CLI release tag to upload assets to. Defaults to cli-v{CLI version}." + description: "Project runner release tag to upload assets to. Defaults to uloop-project-runner-v{version}." required: false default: "" dry-run: diff --git a/.gitignore b/.gitignore index 108a0808d..ae8e36434 100644 --- a/.gitignore +++ b/.gitignore @@ -96,7 +96,7 @@ Packages/src/Docs/** # uloop CLI cache and outputs **/.uloop/* -!**/.uloop/cli-pin.json +!**/.uloop/project-runner-pin.json # Node.js / TypeScript **/node_modules/ diff --git a/.uloop/cli-pin.json b/.uloop/cli-pin.json deleted file mode 100644 index f0aaacd0c..000000000 --- a/.uloop/cli-pin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "3.0.0-beta.43", - "minimumDispatcherVersion": "3.0.1-beta.2", - "packageName": "io.github.hatayama.uloopmcp", - "packageVersion": "3.0.0-beta.44", - "requiredProtocolVersion": 2, - "schemaVersion": 1 -} diff --git a/.uloop/project-runner-pin.json b/.uloop/project-runner-pin.json new file mode 100644 index 000000000..9b475c365 --- /dev/null +++ b/.uloop/project-runner-pin.json @@ -0,0 +1,8 @@ +{ + "projectRunnerVersion": "3.0.0-beta.43", + "minimumDispatcherVersion": "3.0.1-beta.6", + "packageName": "io.github.hatayama.uloopmcp", + "packageVersion": "3.0.0-beta.44", + "requiredProtocolVersion": 3, + "schemaVersion": 1 +} diff --git a/Assets/Tests/Editor/CliPinSynchronizerTests.cs b/Assets/Tests/Editor/CliPinSynchronizerTests.cs index b00472d2e..3bc7741cb 100644 --- a/Assets/Tests/Editor/CliPinSynchronizerTests.cs +++ b/Assets/Tests/Editor/CliPinSynchronizerTests.cs @@ -6,6 +6,7 @@ using UnityEngine.TestTools; using io.github.hatayama.UnityCliLoop.Infrastructure; +using io.github.hatayama.UnityCliLoop.ToolContracts; namespace io.github.hatayama.UnityCliLoop.Tests.Editor { @@ -14,7 +15,7 @@ public sealed class CliPinSynchronizerTests [Test] public void SyncProjectPinFile_WhenDestinationMissing_ShouldCopyPackagePin() { - // Tests that the dispatcher pin contract is published into the project .uloop directory. + // Tests that the project runner pin contract is published into the project .uloop directory. string root = CreateTestRoot(); string packageRoot = Path.Combine(root, "package"); string projectRoot = Path.Combine(root, "project"); @@ -23,13 +24,19 @@ public void SyncProjectPinFile_WhenDestinationMissing_ShouldCopyPackagePin() { Directory.CreateDirectory(packageRoot); Directory.CreateDirectory(projectRoot); - File.WriteAllText(Path.Combine(packageRoot, "cli-pin.json"), "{\"schemaVersion\":1}"); + File.WriteAllText( + Path.Combine(packageRoot, UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME), + "{\"schemaVersion\":1}"); bool changed = CliPinSynchronizer.SyncProjectPinFile(packageRoot, projectRoot); Assert.That(changed, Is.True); Assert.That( - File.ReadAllText(Path.Combine(projectRoot, ".uloop", "cli-pin.json")), + File.ReadAllText( + Path.Combine( + projectRoot, + UnityCliLoopConstants.ULOOP_DIR, + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME)), Is.EqualTo("{\"schemaVersion\":1}")); } finally @@ -45,14 +52,18 @@ public void SyncProjectPinFile_WhenDestinationMatches_ShouldSkipWrite() string root = CreateTestRoot(); string packageRoot = Path.Combine(root, "package"); string projectRoot = Path.Combine(root, "project"); - string projectUloopRoot = Path.Combine(projectRoot, ".uloop"); + string projectUloopRoot = Path.Combine(projectRoot, UnityCliLoopConstants.ULOOP_DIR); try { Directory.CreateDirectory(packageRoot); Directory.CreateDirectory(projectUloopRoot); - string sourcePath = Path.Combine(packageRoot, "cli-pin.json"); - string destinationPath = Path.Combine(projectUloopRoot, "cli-pin.json"); + string sourcePath = Path.Combine( + packageRoot, + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME); + string destinationPath = Path.Combine( + projectUloopRoot, + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME); File.WriteAllText(sourcePath, "{\"schemaVersion\":1}"); File.WriteAllText(destinationPath, "{\"schemaVersion\":1}"); DateTime previousWriteTime = File.GetLastWriteTimeUtc(destinationPath); @@ -71,18 +82,22 @@ public void SyncProjectPinFile_WhenDestinationMatches_ShouldSkipWrite() [Test] public void SyncProjectPinFile_WhenPackagePinChanges_ShouldUpdateProjectPin() { - // Tests that package upgrades update the project dispatcher pin contract. + // Tests that package upgrades update the project runner pin contract. string root = CreateTestRoot(); string packageRoot = Path.Combine(root, "package"); string projectRoot = Path.Combine(root, "project"); - string projectUloopRoot = Path.Combine(projectRoot, ".uloop"); + string projectUloopRoot = Path.Combine(projectRoot, UnityCliLoopConstants.ULOOP_DIR); try { Directory.CreateDirectory(packageRoot); Directory.CreateDirectory(projectUloopRoot); - File.WriteAllText(Path.Combine(packageRoot, "cli-pin.json"), "{\"schemaVersion\":2}"); - string destinationPath = Path.Combine(projectUloopRoot, "cli-pin.json"); + File.WriteAllText( + Path.Combine(packageRoot, UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME), + "{\"schemaVersion\":2}"); + string destinationPath = Path.Combine( + projectUloopRoot, + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME); File.WriteAllText(destinationPath, "{\"schemaVersion\":1}"); bool changed = CliPinSynchronizer.SyncProjectPinFile(packageRoot, projectRoot); @@ -106,14 +121,22 @@ public void SyncProjectPinFile_WhenPackageRootMissing_ShouldSkipWrite() try { Directory.CreateDirectory(projectRoot); + string expectedWarning = + $"Unity CLI Loop skipped {UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME} synchronization because the package root is empty."; LogAssert.Expect( LogType.Warning, - "Unity CLI Loop skipped cli-pin.json synchronization because the package root is empty."); + expectedWarning); bool changed = CliPinSynchronizer.SyncProjectPinFile(string.Empty, projectRoot); Assert.That(changed, Is.False); - Assert.That(File.Exists(Path.Combine(projectRoot, ".uloop", "cli-pin.json")), Is.False); + Assert.That( + File.Exists( + Path.Combine( + projectRoot, + UnityCliLoopConstants.ULOOP_DIR, + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME)), + Is.False); } finally { diff --git a/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs b/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs index b319b7736..a390fe97a 100644 --- a/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs +++ b/Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs @@ -122,22 +122,6 @@ public async Task ProcessRequest_WhenCliMetadataIsMissing_ReturnsCliUpdateRequir Assert.That(data["updateCommand"]?.ToString(), Is.EqualTo(ExpectedCliUpdateCommand())); } - [Test] - public async Task ProcessRequest_WhenClientSendsOnlySemverVersion_ReturnsCliUpdateRequiredError() - { - // Verifies CLIs released before the protocol handshake are treated as outdated. - string response = await JsonRpcProcessor.ProcessRequest( - "{\"jsonrpc\":\"2.0\",\"method\":\"get-version\",\"params\":{},\"id\":1," + - "\"uloop\":{\"cliVersion\":\"3.0.0-beta.24\"}}", - CancellationToken.None); - JObject data = ParseErrorData(response); - - Assert.That(data["type"]?.ToString(), Is.EqualTo("cli_update_required")); - Assert.That(data["currentProtocolVersion"]?.Type, Is.EqualTo(JTokenType.Null)); - Assert.That(data["currentCliVersion"]?.ToString(), Is.EqualTo("3.0.0-beta.24")); - Assert.That(data["updateCommand"]?.ToString(), Is.EqualTo(ExpectedCliUpdateCommand())); - } - [Test] public async Task ProcessRequest_WhenProtocolVersionIsNotAnInteger_ReturnsCliUpdateRequiredError() { diff --git a/Assets/Tests/Editor/NativeCliInstallerTests.cs b/Assets/Tests/Editor/NativeCliInstallerTests.cs index 12ea75b5e..5ddea6fc8 100644 --- a/Assets/Tests/Editor/NativeCliInstallerTests.cs +++ b/Assets/Tests/Editor/NativeCliInstallerTests.cs @@ -135,20 +135,6 @@ public void GetInstallCommand_WhenVPrefixedVersionUsesDispatcherReleaseTag() Does.Contain("https://raw.githubusercontent.com/hatayama/unity-cli-loop/dispatcher-v3.0.0/scripts/install.sh")); } - [Test] - public void GetInstallCommand_WhenCliPrefixedVersionUsesDispatcherReleaseTag() - { - // Verifies legacy CLI release tags are normalized before selecting dispatcher installer assets. - NativeCliInstallCommand command = NativeCliInstaller.BuildInstallCommand( - RuntimePlatform.OSXEditor, - "cli-v3.0.0-beta.40", - false, - "/bin/zsh"); - - Assert.That(command.Arguments, Does.Contain("dispatcher-v3.0.0-beta.40")); - Assert.That(command.Arguments, Does.Not.Contain("dispatcher-vcli-v3.0.0-beta.40")); - } - [Test] public void GetInstallCommand_WhenLocalPackageOnMacUsesPackageLocalInstaller() { diff --git a/Packages/src/Editor/Domain/CliConstants.cs b/Packages/src/Editor/Domain/CliConstants.cs index b6bd11bbc..95343f978 100644 --- a/Packages/src/Editor/Domain/CliConstants.cs +++ b/Packages/src/Editor/Domain/CliConstants.cs @@ -9,13 +9,13 @@ public static class CliConstants // Why: the runtime IPC gate compares this contract generation, not release numbers. // Bump it together with cli/contract.json protocolVersion only when this package can // no longer interoperate with a different CLI protocol generation. - public const int REQUIRED_CLI_PROTOCOL_VERSION = 2; - // Why: setup installs this pinned release; protocol bump PRs can advance it only after - // the matching CLI tag is published. - public const string MINIMUM_REQUIRED_CLI_VERSION = "3.0.0-beta.40"; - public const string MINIMUM_REQUIRED_CLI_RELEASE_TAG = CLI_RELEASE_TAG_PREFIX + MINIMUM_REQUIRED_CLI_VERSION; + public const int REQUIRED_CLI_PROTOCOL_VERSION = 3; + // Why: setup installs this pinned project runner release; protocol bump PRs can advance it only after + // the matching project runner tag is published. + public const string MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION = "3.0.0-beta.43"; + public const string MINIMUM_REQUIRED_PROJECT_RUNNER_RELEASE_TAG = PROJECT_RUNNER_RELEASE_TAG_PREFIX + MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION; // Why: global uloop is a dispatcher; project-local CLI versions are downloaded separately. - public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "3.0.1-beta.2"; + public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "3.0.1-beta.6"; public const string MINIMUM_REQUIRED_DISPATCHER_RELEASE_TAG = DISPATCHER_RELEASE_TAG_PREFIX + MINIMUM_REQUIRED_DISPATCHER_VERSION; // Why: dispatcher setup compatibility is a launcher contract generation, not the IPC protocol generation. public const int REQUIRED_DISPATCHER_CONTRACT_VERSION = 1; @@ -40,7 +40,7 @@ public static class CliConstants public const string POSIX_PATH_SEPARATOR = ":"; public const string WINDOWS_PATH_SEPARATOR = ";"; public const string RELEASE_TAG_PREFIX = "v"; - public const string CLI_RELEASE_TAG_PREFIX = "cli-v"; + public const string PROJECT_RUNNER_RELEASE_TAG_PREFIX = "uloop-project-runner-v"; public const string DISPATCHER_RELEASE_TAG_PREFIX = "dispatcher-v"; public const string BETA_VERSION_MARKER = "-beta."; public const string SKILL_DIR_PREFIX = "uloop-"; diff --git a/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs b/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs index 4dac6a20e..e768fb693 100644 --- a/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs +++ b/Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs @@ -89,7 +89,7 @@ private static JsonRpcRequest ParseRequest(string jsonRequest) { Method = request["method"]?.ToString(), Params = request["params"], - ClientCliVersion = ReadClientCliVersion(request), + ClientProjectRunnerVersion = ReadClientProjectRunnerVersion(request), ClientProtocolVersion = ReadClientProtocolVersion(request), AcceptsDispatchAck = ReadAcceptsDispatchAck(request), AcceptsHeartbeat = ReadAcceptsHeartbeat(request), @@ -97,7 +97,7 @@ private static JsonRpcRequest ParseRequest(string jsonRequest) }; } - private static string ReadClientCliVersion(JObject request) + private static string ReadClientProjectRunnerVersion(JObject request) { JObject metadata = request["uloop"] as JObject; if (metadata == null) @@ -105,8 +105,8 @@ private static string ReadClientCliVersion(JObject request) return null; } - string cliVersion = metadata["cliVersion"]?.ToString(); - return string.IsNullOrWhiteSpace(cliVersion) ? null : cliVersion; + string projectRunnerVersion = metadata["projectRunnerVersion"]?.ToString(); + return string.IsNullOrWhiteSpace(projectRunnerVersion) ? null : projectRunnerVersion; } private static int? ReadClientProtocolVersion(JObject request) @@ -216,7 +216,7 @@ private static async Task ProcessRpcRequest( { return CreateCliProtocolMismatchResponse( request.Id, - request.ClientCliVersion, + request.ClientProjectRunnerVersion, request.ClientProtocolVersion); } diff --git a/Packages/src/Editor/Infrastructure/Api/JsonRpcRequest.cs b/Packages/src/Editor/Infrastructure/Api/JsonRpcRequest.cs index 63d33204e..05368226e 100644 --- a/Packages/src/Editor/Infrastructure/Api/JsonRpcRequest.cs +++ b/Packages/src/Editor/Infrastructure/Api/JsonRpcRequest.cs @@ -16,7 +16,7 @@ internal class JsonRpcRequest /// public JToken Params { get; set; } - public string ClientCliVersion { get; set; } + public string ClientProjectRunnerVersion { get; set; } /// /// IPC protocol generation the client speaks. Null when the client predates the diff --git a/Packages/src/Editor/Infrastructure/CLI/CliInstallationDetector.cs b/Packages/src/Editor/Infrastructure/CLI/CliInstallationDetector.cs index fd5fe6421..e157dd75f 100644 --- a/Packages/src/Editor/Infrastructure/CLI/CliInstallationDetector.cs +++ b/Packages/src/Editor/Infrastructure/CLI/CliInstallationDetector.cs @@ -45,7 +45,8 @@ public sealed class CliInstallationDetector : ICliInstallationDetector private const string SHELL_CONTRACT_STATUS_START_MARKER = "__ULOOP_CONTRACT_STATUS_START__"; private const string SHELL_CONTRACT_STATUS_END_MARKER = "__ULOOP_CONTRACT_STATUS_END__"; private const string SHELL_SUCCESS_EXIT_CODE = "0"; - private const string VERSION_JSON_CLI_VERSION_PROPERTY = "CliVersion"; + private const string VERSION_JSON_PROJECT_RUNNER_VERSION_PROPERTY = "ProjectRunnerVersion"; + private const string VERSION_JSON_LEGACY_CLI_VERSION_PROPERTY = "CliVersion"; private const string VERSION_JSON_DISPATCHER_VERSION_PROPERTY = "DispatcherVersion"; private const string VERSION_JSON_DISPATCHER_CONTRACT_VERSION_PROPERTY = "DispatcherContractVersion"; @@ -376,7 +377,11 @@ private static CliInstallationDetection ParseCliContractOutput(string output, st return dispatcherDetection; } - string version = parsed[VERSION_JSON_CLI_VERSION_PROPERTY]?.ToString(); + string version = parsed[VERSION_JSON_PROJECT_RUNNER_VERSION_PROPERTY]?.ToString(); + if (string.IsNullOrEmpty(version)) + { + version = parsed[VERSION_JSON_LEGACY_CLI_VERSION_PROPERTY]?.ToString(); + } return new CliInstallationDetection(version, executablePath); } catch (JsonException) diff --git a/Packages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs b/Packages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs index 9cb709999..e65506f4a 100644 --- a/Packages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs +++ b/Packages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs @@ -37,22 +37,27 @@ internal static bool SyncProjectPinFile(string packageRoot, string projectRoot) if (string.IsNullOrWhiteSpace(packageRoot)) { Debug.LogWarning( - $"Unity CLI Loop skipped {UnityCliLoopConstants.ULOOP_CLI_PIN_FILE_NAME} synchronization because the package root is empty."); + $"Unity CLI Loop skipped {UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME} synchronization because the package root is empty."); return false; } if (string.IsNullOrWhiteSpace(projectRoot)) { Debug.LogWarning( - $"Unity CLI Loop skipped {UnityCliLoopConstants.ULOOP_CLI_PIN_FILE_NAME} synchronization because the project root is empty."); + $"Unity CLI Loop skipped {UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME} synchronization because the project root is empty."); return false; } - string sourcePath = Path.Combine(packageRoot, UnityCliLoopConstants.ULOOP_CLI_PIN_FILE_NAME); - string destinationPath = Path.Combine( + return SyncProjectPinFileByName( + packageRoot, projectRoot, - UnityCliLoopConstants.ULOOP_DIR, - UnityCliLoopConstants.ULOOP_CLI_PIN_FILE_NAME); + UnityCliLoopConstants.ULOOP_PROJECT_RUNNER_PIN_FILE_NAME); + } + + private static bool SyncProjectPinFileByName(string packageRoot, string projectRoot, string pinFileName) + { + string sourcePath = Path.Combine(packageRoot, pinFileName); + string destinationPath = Path.Combine(projectRoot, UnityCliLoopConstants.ULOOP_DIR, pinFileName); if (!File.Exists(sourcePath)) { diff --git a/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs b/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs index f7fb9aa0d..6698b31f6 100644 --- a/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs +++ b/Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs @@ -958,9 +958,9 @@ private static string BuildReleaseTag(string cliReleaseTag) { return cliReleaseTag; } - if (cliReleaseTag.StartsWith(CliConstants.CLI_RELEASE_TAG_PREFIX, StringComparison.Ordinal)) + if (cliReleaseTag.StartsWith(CliConstants.PROJECT_RUNNER_RELEASE_TAG_PREFIX, StringComparison.Ordinal)) { - return $"{CliConstants.DISPATCHER_RELEASE_TAG_PREFIX}{cliReleaseTag.Substring(CliConstants.CLI_RELEASE_TAG_PREFIX.Length)}"; + return $"{CliConstants.DISPATCHER_RELEASE_TAG_PREFIX}{cliReleaseTag.Substring(CliConstants.PROJECT_RUNNER_RELEASE_TAG_PREFIX.Length)}"; } if (cliReleaseTag.StartsWith(CliConstants.RELEASE_TAG_PREFIX, StringComparison.Ordinal)) { diff --git a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs index 2a81a2baf..9e8600c8e 100644 --- a/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs +++ b/Packages/src/Editor/Presentation/Setup/SetupWizardWindow.cs @@ -1232,7 +1232,7 @@ private async void HandleInstallCli() true); EditorUtility.DisplayDialog( "Installation Failed", - $"Failed to install uloop-cli.\n\n{result.ErrorOutput}\n\n" + $"Failed to install uloop CLI.\n\n{result.ErrorOutput}\n\n" + $"You can install manually:\n {command.ManualCommand}", "OK"); return; diff --git a/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs b/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs index fbfb17a8f..275130f7a 100644 --- a/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs +++ b/Packages/src/Editor/Presentation/UnityCliLoopSettingsWindow.cs @@ -1014,7 +1014,7 @@ private async void HandleInstallSkills() { EditorUtility.DisplayDialog( "CLI Not Found", - "uloop-cli is not installed. Please install the CLI first.", + "uloop CLI is not installed. Please install the CLI first.", "OK"); return; } diff --git a/Packages/src/Editor/ToolContracts/UnityCliLoopConstants.cs b/Packages/src/Editor/ToolContracts/UnityCliLoopConstants.cs index d95d496ae..ff0eaff2a 100644 --- a/Packages/src/Editor/ToolContracts/UnityCliLoopConstants.cs +++ b/Packages/src/Editor/ToolContracts/UnityCliLoopConstants.cs @@ -56,7 +56,7 @@ public static UnityEditor.PackageManager.PackageInfo PackageInfo // .uloop directory public const string ULOOP_DIR = ".uloop"; public const string ULOOP_TOOL_SETTINGS_FILE_NAME = "settings.tools.json"; - public const string ULOOP_CLI_PIN_FILE_NAME = "cli-pin.json"; + public const string ULOOP_PROJECT_RUNNER_PIN_FILE_NAME = "project-runner-pin.json"; // Command name constants public const string TOOL_NAME_COMPILE = "compile"; diff --git a/Packages/src/cli-pin.json b/Packages/src/cli-pin.json deleted file mode 100644 index f0aaacd0c..000000000 --- a/Packages/src/cli-pin.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "cliVersion": "3.0.0-beta.43", - "minimumDispatcherVersion": "3.0.1-beta.2", - "packageName": "io.github.hatayama.uloopmcp", - "packageVersion": "3.0.0-beta.44", - "requiredProtocolVersion": 2, - "schemaVersion": 1 -} diff --git a/Packages/src/project-runner-pin.json b/Packages/src/project-runner-pin.json new file mode 100644 index 000000000..9b475c365 --- /dev/null +++ b/Packages/src/project-runner-pin.json @@ -0,0 +1,8 @@ +{ + "projectRunnerVersion": "3.0.0-beta.43", + "minimumDispatcherVersion": "3.0.1-beta.6", + "packageName": "io.github.hatayama.uloopmcp", + "packageVersion": "3.0.0-beta.44", + "requiredProtocolVersion": 3, + "schemaVersion": 1 +} diff --git a/Packages/src/cli-pin.json.meta b/Packages/src/project-runner-pin.json.meta similarity index 100% rename from Packages/src/cli-pin.json.meta rename to Packages/src/project-runner-pin.json.meta diff --git a/README.md b/README.md index 940b551e4..f52f3f8ad 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Scope(s): io.github.hatayama.uloopmcp Select Window > Unity CLI Loop > Settings. A dedicated window will open. If the **CLI** button is not highlighted in blue, press **Install CLI**. -The installer places the global `uloop` dispatcher on PATH. Project-specific `uloop-cli` binaries are downloaded into the user cache automatically from each project's `.uloop/cli-pin.json`. +The installer places the global `uloop` dispatcher on PATH. Project-specific `uloop-project-runner` binaries are downloaded into the user cache automatically from each project's `.uloop/project-runner-pin.json`. To return to the v2 line, press **Uninstall CLI** in Settings, downgrade the U-LOOP package to a v2 version such as `2.1.1`, then press **Install CLI** again from Settings. @@ -612,7 +612,7 @@ The `.uloop/` directory at the project root stores CLI cache, tool registry, and | File | Purpose | Git-track? | |------|---------|------------| -| `cli-pin.json` | Project CLI version contract used by the global dispatcher | Yes | +| `project-runner-pin.json` | Project runner version contract used by the global dispatcher | Yes | | `settings.tools.json` | Per-tool enable/disable preferences | Optional | | `tools.json` | Auto-generated CLI tool registry | No | | `outputs/` | Runtime outputs (test results, screenshots, hierarchy dumps) | No | @@ -622,7 +622,7 @@ The `.uloop/` directory at the project root stores CLI cache, tool registry, and > > ```gitignore > **/.uloop/* -> !**/.uloop/cli-pin.json +> !**/.uloop/project-runner-pin.json > !**/.uloop/settings.tools.json > ``` > diff --git a/cli/cmd/uloop/main.go b/cli/cmd/dispatcher/main.go similarity index 100% rename from cli/cmd/uloop/main.go rename to cli/cmd/dispatcher/main.go diff --git a/cli/cmd/project-runner/main.go b/cli/cmd/project-runner/main.go new file mode 100644 index 000000000..3ebca6633 --- /dev/null +++ b/cli/cmd/project-runner/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "context" + "os" + + "github.com/hatayama/unity-cli-loop/cli/internal/projectrunner" +) + +func main() { + os.Exit(projectrunner.Run(context.Background(), os.Args[1:], os.Stdout, os.Stderr)) +} diff --git a/cli/cmd/uloop-cli/main.go b/cli/cmd/uloop-cli/main.go deleted file mode 100644 index 3785cdb16..000000000 --- a/cli/cmd/uloop-cli/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "context" - "os" - - "github.com/hatayama/unity-cli-loop/cli/internal/projectcli" -) - -func main() { - os.Exit(projectcli.Run(context.Background(), os.Args[1:], os.Stdout, os.Stderr)) -} diff --git a/cli/contract.go b/cli/contract.go index 5399149cb..ace313b6b 100644 --- a/cli/contract.go +++ b/cli/contract.go @@ -24,8 +24,8 @@ type Contract struct { SchemaVersion int `json:"schemaVersion"` // ProtocolVersion is the C# IPC contract generation this binary speaks. It moves only // when the Unity package and the CLI can no longer interoperate, never per release. - ProtocolVersion int `json:"protocolVersion"` - CliVersion string `json:"cliVersion"` + ProtocolVersion int `json:"protocolVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` } type DispatcherContract struct { @@ -51,7 +51,7 @@ func mustLoadContract() Contract { if contract.SchemaVersion != schemaVersion { panic(fmt.Sprintf("CLI contract schema version mismatch: %d", contract.SchemaVersion)) } - requireString(contract.CliVersion, "cliVersion") + requireString(contract.ProjectRunnerVersion, "projectRunnerVersion") if contract.ProtocolVersion < 1 { panic(fmt.Sprintf("CLI contract protocolVersion must be at least 1, got %d", contract.ProtocolVersion)) } diff --git a/cli/contract.json b/cli/contract.json index 3e84473ba..712a3b9cb 100644 --- a/cli/contract.json +++ b/cli/contract.json @@ -1,5 +1,5 @@ { "schemaVersion": 1, - "protocolVersion": 2, - "cliVersion": "3.0.0-beta.43" + "protocolVersion": 3, + "projectRunnerVersion": "3.0.0-beta.43" } diff --git a/cli/contract_test.go b/cli/contract_test.go index 29583b8eb..6f46a1b7f 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -8,8 +8,8 @@ import ( ) func TestCliContractProvidesRuntimeVersion(t *testing.T) { - // Verifies that the native CLI owns its runtime version from the single CLI module. - requireValidContractVersion(t, "cliVersion", Current.CliVersion) + // Verifies that the project runner owns its runtime version from the single CLI module. + requireValidContractVersion(t, "projectRunnerVersion", Current.ProjectRunnerVersion) } func TestCliContractProvidesProtocolVersion(t *testing.T) { @@ -41,6 +41,7 @@ func TestCliContractDoesNotDeclareDispatcherReleaseFields(t *testing.T) { func TestDispatcherContractDoesNotDeclareCliReleaseFields(t *testing.T) { // Verifies dispatcher releases stay independent from project-local CLI release metadata. fields := requireContractFieldMap(t, dispatcherContractFileName) + requireContractFieldMissing(t, fields, "projectRunnerVersion") requireContractFieldMissing(t, fields, "cliVersion") requireContractFieldMissing(t, fields, "protocolVersion") } diff --git a/cli/dispatcher-contract.json b/cli/dispatcher-contract.json index 58d0c40ee..f1a164c23 100644 --- a/cli/dispatcher-contract.json +++ b/cli/dispatcher-contract.json @@ -1,5 +1,5 @@ { "schemaVersion": 1, - "dispatcherVersion": "3.0.1-beta.5", + "dispatcherVersion": "3.0.1-beta.6", "dispatcherContractVersion": 1 } diff --git a/cli/internal/architecture/architecture_test.go b/cli/internal/architecture/architecture_test.go index 0f19adf6f..ebed4bcfc 100644 --- a/cli/internal/architecture/architecture_test.go +++ b/cli/internal/architecture/architecture_test.go @@ -48,7 +48,7 @@ func TestCliFeaturePackagesDoNotImportCli(t *testing.T) { for _, goPackage := range packages { if goPackage.ImportPath == cliModulePath+"/internal/cli" || goPackage.ImportPath == cliModulePath+"/internal/dispatcher" || - goPackage.ImportPath == cliModulePath+"/internal/projectcli" || + goPackage.ImportPath == cliModulePath+"/internal/projectrunner" || strings.HasPrefix(goPackage.ImportPath, cliModulePath+"/cmd/") { continue } @@ -71,7 +71,7 @@ func TestCliInternalPackagesStayInsideExplicitBoundaries(t *testing.T) { if goPackage.ImportPath == cliModulePath+"/internal/architecture" { continue } - for _, boundary := range []string{"/internal/automation", "/internal/cli", "/internal/dispatcher", "/internal/install", "/internal/project", "/internal/projectcli", "/internal/skills", "/internal/tools", "/internal/uninstall", "/internal/unityipc", "/internal/update", "/internal/version"} { + for _, boundary := range []string{"/internal/automation", "/internal/cli", "/internal/dispatcher", "/internal/install", "/internal/project", "/internal/projectrunner", "/internal/skills", "/internal/tools", "/internal/uninstall", "/internal/unityipc", "/internal/update", "/internal/version"} { if strings.Contains(goPackage.ImportPath, boundary) { goto nextPackage } @@ -83,12 +83,12 @@ func TestCliInternalPackagesStayInsideExplicitBoundaries(t *testing.T) { // Tests that the dispatcher command only enters the dispatcher package. func TestDispatcherCommandOnlyDependsOnDispatcherEntrypoint(t *testing.T) { - assertCommandOnlyDependsOnInternalEntrypoint(t, "./cmd/uloop", cliModulePath+"/internal/dispatcher") + assertCommandOnlyDependsOnInternalEntrypoint(t, "./cmd/dispatcher", cliModulePath+"/internal/dispatcher") } -// Tests that the project-local CLI command only enters the project CLI package. -func TestProjectCliCommandOnlyDependsOnProjectCliEntrypoint(t *testing.T) { - assertCommandOnlyDependsOnInternalEntrypoint(t, "./cmd/uloop-cli", cliModulePath+"/internal/projectcli") +// Tests that the project runner command only enters the project runner package. +func TestProjectRunnerCommandOnlyDependsOnProjectRunnerEntrypoint(t *testing.T) { + assertCommandOnlyDependsOnInternalEntrypoint(t, "./cmd/project-runner", cliModulePath+"/internal/projectrunner") } func assertCommandOnlyDependsOnInternalEntrypoint(t *testing.T, commandPath string, expectedEntrypoint string) { diff --git a/cli/internal/automation/dispatcher_minimum_version_guard.go b/cli/internal/automation/dispatcher_minimum_version_guard.go index ce28c4b2e..d3afe6404 100644 --- a/cli/internal/automation/dispatcher_minimum_version_guard.go +++ b/cli/internal/automation/dispatcher_minimum_version_guard.go @@ -16,8 +16,8 @@ const ( cliContractFile = "cli/contract.json" dispatcherContractFile = "cli/dispatcher-contract.json" dispatcherReleaseTagPrefix = "dispatcher-v" - unityPackageCliPinFile = "Packages/src/cli-pin.json" - unityProjectCliPinFile = ".uloop/cli-pin.json" + unityPackageCliPinFile = "Packages/src/project-runner-pin.json" + unityProjectCliPinFile = ".uloop/project-runner-pin.json" minimumDispatcherContractVersion = 1 minimumDispatcherVersionDescription = "minimumDispatcherVersion" ) @@ -25,18 +25,18 @@ const ( var minimumDispatcherVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_DISPATCHER_VERSION\s*=\s*"([^"]+)"`) type dispatcherMinimumVersionValues struct { - CurrentCliVersion string + CurrentProjectRunnerVersion string CurrentDispatcherVersion string CurrentDispatcherContractVersion int MinimumDispatcherVersion string - PackagePinCliVersion string + PackagePinProjectRunnerVersion string PackagePinMinimumDispatcherVersion string - ProjectPinCliVersion string + ProjectPinProjectRunnerVersion string ProjectPinMinimumDispatcherVersion string } type dispatcherMinimumVersionCliContract struct { - CliVersion string `json:"cliVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` } type dispatcherMinimumVersionContract struct { @@ -53,7 +53,7 @@ type dispatcherMinimumVersionCliPin struct { SchemaVersion int `json:"schemaVersion"` PackageName string `json:"packageName"` PackageVersion string `json:"packageVersion"` - CliVersion string `json:"cliVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` RequiredProtocolVersion int `json:"requiredProtocolVersion"` MinimumDispatcherVersion string `json:"minimumDispatcherVersion"` } @@ -159,13 +159,13 @@ func parseDispatcherMinimumVersionValues( } values := dispatcherMinimumVersionValues{ - CurrentCliVersion: cliContract.CliVersion, + CurrentProjectRunnerVersion: cliContract.ProjectRunnerVersion, CurrentDispatcherVersion: dispatcherContract.DispatcherVersion, CurrentDispatcherContractVersion: dispatcherContract.DispatcherContractVersion, MinimumDispatcherVersion: minimumDispatcherVersion, - PackagePinCliVersion: packagePin.CliVersion, + PackagePinProjectRunnerVersion: packagePin.ProjectRunnerVersion, PackagePinMinimumDispatcherVersion: packagePin.MinimumDispatcherVersion, - ProjectPinCliVersion: projectPin.CliVersion, + ProjectPinProjectRunnerVersion: projectPin.ProjectRunnerVersion, ProjectPinMinimumDispatcherVersion: projectPin.MinimumDispatcherVersion, } return values, validateDispatcherMinimumVersionValues(values) @@ -176,8 +176,8 @@ func parseDispatcherMinimumVersionCliContract(content []byte) (dispatcherMinimum if err := json.Unmarshal(content, &contract); err != nil { return dispatcherMinimumVersionCliContract{}, fmt.Errorf("%s is invalid JSON: %w", cliContractFile, err) } - if contract.CliVersion == "" { - return dispatcherMinimumVersionCliContract{}, fmt.Errorf("%s does not define cliVersion", cliContractFile) + if contract.ProjectRunnerVersion == "" { + return dispatcherMinimumVersionCliContract{}, fmt.Errorf("%s does not define projectRunnerVersion", cliContractFile) } return contract, nil } @@ -211,8 +211,8 @@ func parseDispatcherMinimumVersionPin(path string, content []byte) (dispatcherMi if err := json.Unmarshal(content, &pin); err != nil { return dispatcherMinimumVersionCliPin{}, fmt.Errorf("%s is invalid JSON: %w", path, err) } - if pin.CliVersion == "" { - return dispatcherMinimumVersionCliPin{}, fmt.Errorf("%s does not define cliVersion", path) + if pin.ProjectRunnerVersion == "" { + return dispatcherMinimumVersionCliPin{}, fmt.Errorf("%s does not define projectRunnerVersion", path) } if pin.MinimumDispatcherVersion == "" { return dispatcherMinimumVersionCliPin{}, fmt.Errorf("%s does not define %s", path, minimumDispatcherVersionDescription) @@ -221,19 +221,19 @@ func parseDispatcherMinimumVersionPin(path string, content []byte) (dispatcherMi } func validateDispatcherMinimumVersionValues(values dispatcherMinimumVersionValues) error { - if values.PackagePinCliVersion != values.CurrentCliVersion { - return fmt.Errorf("%s cliVersion %q does not match %s cliVersion %q", + if values.PackagePinProjectRunnerVersion != values.CurrentProjectRunnerVersion { + return fmt.Errorf("%s projectRunnerVersion %q does not match %s projectRunnerVersion %q", unityPackageCliPinFile, - values.PackagePinCliVersion, + values.PackagePinProjectRunnerVersion, cliContractFile, - values.CurrentCliVersion) + values.CurrentProjectRunnerVersion) } - if values.ProjectPinCliVersion != values.PackagePinCliVersion { - return fmt.Errorf("%s cliVersion %q does not match %s cliVersion %q", + if values.ProjectPinProjectRunnerVersion != values.PackagePinProjectRunnerVersion { + return fmt.Errorf("%s projectRunnerVersion %q does not match %s projectRunnerVersion %q", unityProjectCliPinFile, - values.ProjectPinCliVersion, + values.ProjectPinProjectRunnerVersion, unityPackageCliPinFile, - values.PackagePinCliVersion) + values.PackagePinProjectRunnerVersion) } if values.PackagePinMinimumDispatcherVersion != values.MinimumDispatcherVersion { return fmt.Errorf("%s %s %q does not match %s MINIMUM_REQUIRED_DISPATCHER_VERSION %q", diff --git a/cli/internal/automation/dispatcher_minimum_version_guard_test.go b/cli/internal/automation/dispatcher_minimum_version_guard_test.go index e5b335a92..47b11bc87 100644 --- a/cli/internal/automation/dispatcher_minimum_version_guard_test.go +++ b/cli/internal/automation/dispatcher_minimum_version_guard_test.go @@ -13,7 +13,7 @@ import ( // Verifies release PRs cannot point minimumDispatcherVersion at a dispatcher tag without contract metadata. func TestRunDispatcherMinimumVersionCheck_WhenMinimumReleaseLacksDispatcherContract_Fails(t *testing.T) { result := runDispatcherMinimumVersionCheckCase(t, dispatcherMinimumVersionCase{ - currentCliVersion: "3.0.0-beta.40", + currentProjectRunnerVersion: "3.0.0-beta.40", currentDispatcherVersion: "1.0.1", currentDispatcherContractVersion: 1, minimumDispatcherVersion: "1.0.0", @@ -30,7 +30,7 @@ func TestRunDispatcherMinimumVersionCheck_WhenMinimumReleaseLacksDispatcherContr // Verifies release PRs pass when the current dispatcher release itself is the minimum dispatcher version. func TestRunDispatcherMinimumVersionCheck_WhenMinimumIsCurrentRelease_Passes(t *testing.T) { result := runDispatcherMinimumVersionCheckCase(t, dispatcherMinimumVersionCase{ - currentCliVersion: "3.0.0-beta.40", + currentProjectRunnerVersion: "3.0.0-beta.40", currentDispatcherVersion: "1.0.0", currentDispatcherContractVersion: 1, minimumDispatcherVersion: "1.0.0", @@ -46,7 +46,7 @@ func TestRunDispatcherMinimumVersionCheck_WhenMinimumIsCurrentRelease_Passes(t * // Verifies committed pin files cannot drift from the C# minimum dispatcher version. func TestRunDispatcherMinimumVersionCheck_WhenProjectPinDiffersFromPackagePin_Fails(t *testing.T) { result := runDispatcherMinimumVersionCheckCase(t, dispatcherMinimumVersionCase{ - currentCliVersion: "3.0.0-beta.40", + currentProjectRunnerVersion: "3.0.0-beta.40", currentDispatcherVersion: "1.0.0", currentDispatcherContractVersion: 1, minimumDispatcherVersion: "1.0.0", @@ -56,13 +56,13 @@ func TestRunDispatcherMinimumVersionCheck_WhenProjectPinDiffersFromPackagePin_Fa if result.exitCode != 1 { t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } - assertDispatcherMinimumVersionLogContains(t, result.stderr, ".uloop/cli-pin.json minimumDispatcherVersion") + assertDispatcherMinimumVersionLogContains(t, result.stderr, ".uloop/project-runner-pin.json minimumDispatcherVersion") } // Verifies invalid dispatcher contract metadata reports the actual bad value. func TestRunDispatcherMinimumVersionCheck_WhenCurrentDispatcherContractIsInvalid_FailsWithValue(t *testing.T) { result := runDispatcherMinimumVersionCheckCase(t, dispatcherMinimumVersionCase{ - currentCliVersion: "3.0.0-beta.40", + currentProjectRunnerVersion: "3.0.0-beta.40", currentDispatcherVersion: "1.0.0", currentDispatcherContractVersion: 0, minimumDispatcherVersion: "1.0.0", @@ -75,7 +75,7 @@ func TestRunDispatcherMinimumVersionCheck_WhenCurrentDispatcherContractIsInvalid } type dispatcherMinimumVersionCase struct { - currentCliVersion string + currentProjectRunnerVersion string currentDispatcherVersion string currentDispatcherContractVersion int minimumDispatcherVersion string @@ -138,19 +138,19 @@ func prepareDispatcherMinimumVersionFiles(t *testing.T, workDir string, testCase } writeDispatcherMinimumVersionFile(t, filepath.Join(workDir, cliContractFile), buildDispatcherMinimumVersionCliContract( - testCase.currentCliVersion)) + testCase.currentProjectRunnerVersion)) writeDispatcherMinimumVersionFile(t, filepath.Join(workDir, dispatcherContractFile), buildDispatcherMinimumVersionContract( currentDispatcherVersion, testCase.currentDispatcherContractVersion)) writeDispatcherMinimumVersionFile(t, filepath.Join(workDir, protocolMinimumVersionFile), buildDispatcherMinimumVersionConstants( 2, - testCase.currentCliVersion, + testCase.currentProjectRunnerVersion, testCase.minimumDispatcherVersion)) writeDispatcherMinimumVersionFile(t, filepath.Join(workDir, unityPackageCliPinFile), buildDispatcherMinimumVersionPin( - testCase.currentCliVersion, + testCase.currentProjectRunnerVersion, testCase.minimumDispatcherVersion)) writeDispatcherMinimumVersionFile(t, filepath.Join(workDir, unityProjectCliPinFile), buildDispatcherMinimumVersionPin( - testCase.currentCliVersion, + testCase.currentProjectRunnerVersion, projectMinimumDispatcherVersion)) } @@ -163,8 +163,8 @@ func writeDispatcherMinimumVersionFile(t *testing.T, path string, content string writeFile(t, path, content) } -func buildDispatcherMinimumVersionCliContract(cliVersion string) string { - return `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"` + cliVersion + `"}` +func buildDispatcherMinimumVersionCliContract(projectRunnerVersion string) string { + return `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"` + projectRunnerVersion + `"}` } func buildDispatcherMinimumVersionContract(dispatcherVersion string, dispatcherContractVersion int) string { @@ -175,7 +175,7 @@ func buildDispatcherMinimumVersionContract(dispatcherVersion string, dispatcherC func buildDispatcherMinimumVersionConstants( requiredProtocolVersion int, - minimumCliVersion string, + minimumProjectRunnerVersion string, minimumDispatcherVersion string, ) string { return `namespace Tests { @@ -183,15 +183,15 @@ public static class CliConstants { public const int REQUIRED_CLI_PROTOCOL_VERSION = ` + strconv.Itoa(requiredProtocolVersion) + `; -public const string MINIMUM_REQUIRED_CLI_VERSION = "` + minimumCliVersion + `"; +public const string MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION = "` + minimumProjectRunnerVersion + `"; public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "` + minimumDispatcherVersion + `"; } }` } -func buildDispatcherMinimumVersionPin(cliVersion string, minimumDispatcherVersion string) string { - return `{"schemaVersion":1,"packageName":"test.package","packageVersion":"3.0.0-beta.40","cliVersion":"` + - cliVersion + +func buildDispatcherMinimumVersionPin(projectRunnerVersion string, minimumDispatcherVersion string) string { + return `{"schemaVersion":1,"packageName":"test.package","packageVersion":"3.0.0-beta.40","projectRunnerVersion":"` + + projectRunnerVersion + `","requiredProtocolVersion":2,"minimumDispatcherVersion":"` + minimumDispatcherVersion + `"}` diff --git a/cli/internal/automation/dispatcher_version_bump_guard.go b/cli/internal/automation/dispatcher_version_bump_guard.go index f8640bc8c..9634bf37b 100644 --- a/cli/internal/automation/dispatcher_version_bump_guard.go +++ b/cli/internal/automation/dispatcher_version_bump_guard.go @@ -14,7 +14,7 @@ import ( ) var dispatcherReleaseInputPatterns = []string{ - "cli/cmd/uloop/main.go", + "cli/cmd/dispatcher/main.go", "cli/contract.go", dispatcherContractFile, "cli/internal/cli/completion*.go", diff --git a/cli/internal/automation/protocol_minimum_version_guard.go b/cli/internal/automation/protocol_minimum_version_guard.go index 537bdf5e1..ae80af5bc 100644 --- a/cli/internal/automation/protocol_minimum_version_guard.go +++ b/cli/internal/automation/protocol_minimum_version_guard.go @@ -9,35 +9,29 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "strconv" "strings" ) const ( - protocolMinimumVersionFile = "Packages/src/Editor/Domain/CliConstants.cs" - protocolMinimumVersionMarker = "" - cliReleaseTagPrefix = "cli-v" + protocolMinimumVersionFile = "Packages/src/Editor/Domain/CliConstants.cs" + protocolMinimumVersionMarker = "" + projectRunnerReleaseTagPrefix = "uloop-project-runner-v" ) -var ( - requiredProtocolVersionPattern = regexp.MustCompile(`REQUIRED_CLI_PROTOCOL_VERSION\s*=\s*(\d+)`) - minimumCliVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_CLI_VERSION\s*=\s*"([^"]+)"`) - requiredMinimumCliReleaseAssets = []string{ - "uloop-cli-darwin-amd64.tar.gz", - "uloop-cli-darwin-amd64.tar.gz.sha256", - "uloop-cli-darwin-arm64.tar.gz", - "uloop-cli-darwin-arm64.tar.gz.sha256", - "uloop-cli-windows-amd64.zip", - "uloop-cli-windows-amd64.zip.sha256", - "uloop-darwin-amd64.tar.gz", - "uloop-darwin-amd64.tar.gz.sha256", - "uloop-darwin-arm64.tar.gz", - "uloop-darwin-arm64.tar.gz.sha256", - "uloop-windows-amd64.zip", - "uloop-windows-amd64.zip.sha256", - } -) +var requiredMinimumProjectRunnerAssets = []string{ + "uloop-project-runner-darwin-amd64.tar.gz", + "uloop-project-runner-darwin-amd64.tar.gz.sha256", + "uloop-project-runner-darwin-arm64.tar.gz", + "uloop-project-runner-darwin-arm64.tar.gz.sha256", + "uloop-project-runner-windows-amd64.zip", + "uloop-project-runner-windows-amd64.zip.sha256", +} + +type minimumProjectRunnerRelease struct { + Tag string + RequiredAssets []string +} type ProtocolMinimumVersionGuardConfig struct { BaseRef string @@ -45,23 +39,24 @@ type ProtocolMinimumVersionGuardConfig struct { } type ProtocolMinimumVersionValues struct { - RequiredProtocolVersion int - HasRequiredProtocol bool - MinimumCliVersion string + RequiredProtocolVersion int + HasRequiredProtocol bool + MinimumProjectRunnerVersion string + UsesPreRenameMinimumVersion bool } type ProtocolMinimumVersionGuardResult struct { - Base ProtocolMinimumVersionValues - Head ProtocolMinimumVersionValues - RequiredProtocolChanged bool - MinimumCliVersionChanged bool - NeedsMinimumVersionUpdate bool - MinimumCliReleaseProtocolError string + Base ProtocolMinimumVersionValues + Head ProtocolMinimumVersionValues + RequiredProtocolChanged bool + MinimumProjectRunnerVersionChanged bool + NeedsMinimumVersionUpdate bool + MinimumCliReleaseProtocolError string } type minimumCliReleaseContract struct { - ProtocolVersion *json.RawMessage `json:"protocolVersion"` - CliVersion string `json:"cliVersion"` + ProtocolVersion *json.RawMessage `json:"protocolVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` } type minimumCliReleaseView struct { @@ -116,7 +111,8 @@ func RunMinimumCliReleaseProtocolCheck(ctx context.Context, stdout io.Writer, st return 1 } - if err := verifyMinimumCliReleaseProtocolAtRef(ctx, repoRoot, values); err != nil { + releaseTag, err := verifyMinimumCliReleaseProtocolAtRef(ctx, repoRoot, values) + if err != nil { writeProtocolMinimumVersionLine(stderr, err) return 1 } @@ -124,9 +120,8 @@ func RunMinimumCliReleaseProtocolCheck(ctx context.Context, stdout io.Writer, st writeProtocolMinimumVersionLine( stdout, fmt.Sprintf( - "Minimum CLI release %s%s advertises protocol %d.", - cliReleaseTagPrefix, - values.MinimumCliVersion, + "Minimum project runner release %s advertises protocol %d.", + releaseTag, values.RequiredProtocolVersion)) return 0 } @@ -147,7 +142,7 @@ func AnalyzeProtocolMinimumVersionGuardForRefs( return ProtocolMinimumVersionGuardResult{}, fmt.Errorf("failed to resolve git repository root: %w", err) } - baseValues, err := protocolMinimumVersionValuesAtRef(ctx, repoRoot, config.BaseRef) + baseValues, err := protocolMinimumVersionBaseValuesAtRef(ctx, repoRoot, config.BaseRef) if err != nil { return ProtocolMinimumVersionGuardResult{}, err } @@ -158,9 +153,11 @@ func AnalyzeProtocolMinimumVersionGuardForRefs( result := AnalyzeProtocolMinimumVersionGuard(baseValues, headValues) if protocolMinimumVersionGuardNeedsReleaseCheck(result) { - err = verifyMinimumCliReleaseProtocolAtRef(ctx, repoRoot, result.Head) + _, err = verifyMinimumCliReleaseProtocolAtRef(ctx, repoRoot, result.Head) if err != nil { - result.MinimumCliReleaseProtocolError = err.Error() + if !protocolMinimumVersionBootstrapAllowsUnpublishedProjectRunner(ctx, repoRoot, config.HeadRef, result, err) { + result.MinimumCliReleaseProtocolError = err.Error() + } } } return result, nil @@ -172,40 +169,41 @@ func AnalyzeProtocolMinimumVersionGuard( ) ProtocolMinimumVersionGuardResult { requiredProtocolChanged := base.HasRequiredProtocol != head.HasRequiredProtocol || base.RequiredProtocolVersion != head.RequiredProtocolVersion - minimumCliVersionChanged := base.MinimumCliVersion != head.MinimumCliVersion + minimumProjectRunnerVersionChanged := base.MinimumProjectRunnerVersion != head.MinimumProjectRunnerVersion return ProtocolMinimumVersionGuardResult{ - Base: base, - Head: head, - RequiredProtocolChanged: requiredProtocolChanged, - MinimumCliVersionChanged: minimumCliVersionChanged, - NeedsMinimumVersionUpdate: requiredProtocolChanged && !minimumCliVersionChanged, + Base: base, + Head: head, + RequiredProtocolChanged: requiredProtocolChanged, + MinimumProjectRunnerVersionChanged: minimumProjectRunnerVersionChanged, + NeedsMinimumVersionUpdate: requiredProtocolChanged && !minimumProjectRunnerVersionChanged, } } -func ParseProtocolMinimumVersionValues(content []byte) (ProtocolMinimumVersionValues, error) { - text := string(content) - values := ProtocolMinimumVersionValues{} +func VerifyMinimumCliReleaseProtocol(values ProtocolMinimumVersionValues, contractContent []byte) error { + return verifyMinimumProjectRunnerReleaseProtocol( + minimumProjectRunnerReleaseTag(values.MinimumProjectRunnerVersion), + values, + contractContent) +} - requiredMatches := requiredProtocolVersionPattern.FindStringSubmatch(text) - if len(requiredMatches) == 2 { - requiredProtocolVersion, err := strconv.Atoi(requiredMatches[1]) - if err != nil { - return ProtocolMinimumVersionValues{}, fmt.Errorf("REQUIRED_CLI_PROTOCOL_VERSION is not an integer: %w", err) - } - values.RequiredProtocolVersion = requiredProtocolVersion - values.HasRequiredProtocol = true - } +func minimumProjectRunnerReleaseTag(version string) string { + return projectRunnerReleaseTagPrefix + normalizeProjectRunnerVersion(version) +} - minimumMatches := minimumCliVersionPattern.FindStringSubmatch(text) - if len(minimumMatches) != 2 { - return ProtocolMinimumVersionValues{}, fmt.Errorf("%s does not define MINIMUM_REQUIRED_CLI_VERSION", protocolMinimumVersionFile) +func normalizeProjectRunnerVersion(version string) string { + trimmedVersion := strings.TrimSpace(version) + if strings.HasPrefix(trimmedVersion, "v") || strings.HasPrefix(trimmedVersion, "V") { + return trimmedVersion[1:] } - values.MinimumCliVersion = minimumMatches[1] - return values, nil + return trimmedVersion } -func VerifyMinimumCliReleaseProtocol(values ProtocolMinimumVersionValues, contractContent []byte) error { +func verifyMinimumProjectRunnerReleaseProtocol( + releaseTag string, + values ProtocolMinimumVersionValues, + contractContent []byte, +) error { if !values.HasRequiredProtocol { return fmt.Errorf("%s does not define REQUIRED_CLI_PROTOCOL_VERSION", protocolMinimumVersionFile) } @@ -213,21 +211,19 @@ func VerifyMinimumCliReleaseProtocol(values ProtocolMinimumVersionValues, contra contract := minimumCliReleaseContract{} err := json.Unmarshal(contractContent, &contract) if err != nil { - return fmt.Errorf("CLI release contract is invalid JSON: %w", err) + return fmt.Errorf("project runner release contract is invalid JSON: %w", err) } protocolVersion, hasProtocolVersion := minimumCliReleaseProtocolVersion(contract.ProtocolVersion) if !hasProtocolVersion { return fmt.Errorf( - "CLI release %s%s does not define protocolVersion", - cliReleaseTagPrefix, - values.MinimumCliVersion) + "project runner release %s does not define protocolVersion", + releaseTag) } if protocolVersion != values.RequiredProtocolVersion { return fmt.Errorf( - "unity package requires protocol %d, but CLI release %s%s advertises protocol %d", + "unity package requires protocol %d, but project runner release %s advertises protocol %d", values.RequiredProtocolVersion, - cliReleaseTagPrefix, - values.MinimumCliVersion, + releaseTag, protocolVersion) } return nil @@ -249,16 +245,19 @@ func verifyMinimumCliReleaseProtocolAtRef( ctx context.Context, repoRoot string, values ProtocolMinimumVersionValues, -) error { - releaseTag := cliReleaseTagPrefix + values.MinimumCliVersion - contractContent, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, releaseTag, "cli/contract.json") +) (string, error) { + release := minimumProjectRunnerRelease{ + Tag: minimumProjectRunnerReleaseTag(values.MinimumProjectRunnerVersion), + RequiredAssets: requiredMinimumProjectRunnerAssets, + } + contractContent, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, release.Tag, "cli/contract.json") if err != nil { - return fmt.Errorf("CLI release %s does not provide cli/contract.json", releaseTag) + return "", fmt.Errorf("project runner release %s does not provide cli/contract.json", release.Tag) } - if err := VerifyMinimumCliReleaseProtocol(values, []byte(contractContent)); err != nil { - return err + if err := verifyMinimumProjectRunnerReleaseProtocol(release.Tag, values, []byte(contractContent)); err != nil { + return "", err } - return verifyMinimumCliReleaseIsPublished(ctx, repoRoot, releaseTag) + return verifyMinimumCliReleaseIsPublished(ctx, repoRoot, release) } func minimumCliReleaseProtocolFile(ctx context.Context, repoRoot string, ref string) ([]byte, error) { @@ -273,34 +272,34 @@ func minimumCliReleaseProtocolFile(ctx context.Context, repoRoot string, ref str return []byte(content), nil } -func verifyMinimumCliReleaseIsPublished(ctx context.Context, repoRoot string, releaseTag string) error { +func verifyMinimumCliReleaseIsPublished(ctx context.Context, repoRoot string, release minimumProjectRunnerRelease) (string, error) { output, err := runProtocolMinimumVersionOutput( ctx, repoRoot, "gh", "release", "view", - releaseTag, + release.Tag, "--json", "isDraft,assets") if err != nil { - return fmt.Errorf("CLI release %s is not published with complete native assets: %w", releaseTag, err) + return "", fmt.Errorf("project runner release %s is not published with complete native assets: %w", release.Tag, err) } releaseView := minimumCliReleaseView{} if err := json.Unmarshal([]byte(output), &releaseView); err != nil { - return fmt.Errorf("CLI release %s metadata is invalid JSON: %w", releaseTag, err) + return "", fmt.Errorf("project runner release %s metadata is invalid JSON: %w", release.Tag, err) } if releaseView.IsDraft { - return fmt.Errorf("CLI release %s is still draft", releaseTag) + return "", fmt.Errorf("project runner release %s is still draft", release.Tag) } - if missingAsset := missingMinimumCliReleaseAsset(releaseView.Assets); missingAsset != "" { - return fmt.Errorf("CLI release %s is missing release asset %s", releaseTag, missingAsset) + if missingAsset := missingMinimumCliReleaseAsset(releaseView.Assets, release.RequiredAssets); missingAsset != "" { + return "", fmt.Errorf("project runner release %s is missing release asset %s", release.Tag, missingAsset) } - return nil + return release.Tag, nil } -func missingMinimumCliReleaseAsset(assets []minimumCliReleaseAsset) string { +func missingMinimumCliReleaseAsset(assets []minimumCliReleaseAsset, requiredAssetNames []string) string { availableAssets := map[string]bool{} for _, asset := range assets { if asset.Size > 0 { @@ -308,7 +307,7 @@ func missingMinimumCliReleaseAsset(assets []minimumCliReleaseAsset) string { } } - for _, assetName := range requiredMinimumCliReleaseAssets { + for _, assetName := range requiredAssetNames { if !availableAssets[assetName] { return assetName } @@ -321,11 +320,11 @@ func FormatProtocolMinimumVersionWarning(result ProtocolMinimumVersionGuardResul builder.WriteString(protocolMinimumVersionMarker) builder.WriteString("\n") if result.NeedsMinimumVersionUpdate { - builder.WriteString("Protocol version changed, but `MINIMUM_REQUIRED_CLI_VERSION` did not.\n\n") + builder.WriteString("Protocol version changed, but `MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` did not.\n\n") } else if result.RequiredProtocolChanged { - builder.WriteString("Protocol version changed, but `MINIMUM_REQUIRED_CLI_VERSION` does not point to a published CLI release that advertises the required protocol.\n\n") + builder.WriteString("Protocol version changed, but `MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` does not point to a published project runner release that advertises the required protocol.\n\n") } else { - builder.WriteString("`MINIMUM_REQUIRED_CLI_VERSION` changed, but it does not point to a published CLI release that advertises the required protocol.\n\n") + builder.WriteString("`MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` changed, but it does not point to a published project runner release that advertises the required protocol.\n\n") } builder.WriteString("- Base required protocol: ") builder.WriteString(protocolMinimumVersionValueLabel(result.Base)) @@ -333,8 +332,8 @@ func FormatProtocolMinimumVersionWarning(result ProtocolMinimumVersionGuardResul builder.WriteString("- Head required protocol: ") builder.WriteString(protocolMinimumVersionValueLabel(result.Head)) builder.WriteString("\n") - builder.WriteString("- Current minimum CLI: `") - builder.WriteString(result.Head.MinimumCliVersion) + builder.WriteString("- Current minimum project runner: `") + builder.WriteString(result.Head.MinimumProjectRunnerVersion) builder.WriteString("`\n") if result.MinimumCliReleaseProtocolError != "" { builder.WriteString("- Release check: ") @@ -342,7 +341,7 @@ func FormatProtocolMinimumVersionWarning(result ProtocolMinimumVersionGuardResul builder.WriteString("\n") } builder.WriteString("\n") - builder.WriteString("Update `MINIMUM_REQUIRED_CLI_VERSION` to a published CLI release that advertises the new protocol before releasing the Unity package.") + builder.WriteString("Update `MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` to a published project runner release that advertises the new protocol before releasing the Unity package.") return builder.String() } @@ -354,7 +353,35 @@ func protocolMinimumVersionGuardNeedsReleaseCheck(result ProtocolMinimumVersionG if result.NeedsMinimumVersionUpdate { return false } - return result.RequiredProtocolChanged || result.MinimumCliVersionChanged + return result.RequiredProtocolChanged || result.MinimumProjectRunnerVersionChanged +} + +func protocolMinimumVersionBootstrapAllowsUnpublishedProjectRunner( + ctx context.Context, + repoRoot string, + headRef string, + result ProtocolMinimumVersionGuardResult, + err error, +) bool { + if !protocolMinimumVersionReleaseContractIsMissing(err) { + return false + } + if !result.Base.UsesPreRenameMinimumVersion { + return false + } + if !result.MinimumProjectRunnerVersionChanged { + return false + } + + headProjectRunnerVersion, err := protocolMinimumProjectRunnerVersionAtRef(ctx, repoRoot, headRef) + if err != nil { + return false + } + return result.Head.MinimumProjectRunnerVersion == headProjectRunnerVersion +} + +func protocolMinimumVersionReleaseContractIsMissing(err error) bool { + return strings.Contains(err.Error(), "does not provide cli/contract.json") } func protocolMinimumVersionValueLabel(values ProtocolMinimumVersionValues) string { @@ -382,6 +409,38 @@ func protocolMinimumVersionValuesAtRef( return ParseProtocolMinimumVersionValues([]byte(content)) } +func protocolMinimumVersionBaseValuesAtRef( + ctx context.Context, + repoRoot string, + ref string, +) (ProtocolMinimumVersionValues, error) { + content, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, ref, protocolMinimumVersionFile) + if err != nil { + return ProtocolMinimumVersionValues{}, err + } + return parseProtocolMinimumVersionBaseValues([]byte(content)) +} + +func protocolMinimumProjectRunnerVersionAtRef( + ctx context.Context, + repoRoot string, + ref string, +) (string, error) { + content, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, ref, cliContractFile) + if err != nil { + return "", err + } + + contract := minimumCliReleaseContract{} + if err := json.Unmarshal([]byte(content), &contract); err != nil { + return "", fmt.Errorf("%s is invalid JSON: %w", cliContractFile, err) + } + if contract.ProjectRunnerVersion == "" { + return "", fmt.Errorf("%s does not define projectRunnerVersion", cliContractFile) + } + return contract.ProjectRunnerVersion, nil +} + func protocolMinimumVersionFileAtRef( ctx context.Context, repoRoot string, diff --git a/cli/internal/automation/protocol_minimum_version_guard_test.go b/cli/internal/automation/protocol_minimum_version_guard_test.go index dd0b61392..ca5445692 100644 --- a/cli/internal/automation/protocol_minimum_version_guard_test.go +++ b/cli/internal/automation/protocol_minimum_version_guard_test.go @@ -11,17 +11,17 @@ import ( ) func TestAnalyzeProtocolMinimumVersionGuard_WhenProtocolChangesWithoutMinimumVersionChange_Warns(t *testing.T) { - // Verifies protocol bumps cannot leave the installer target on the old CLI release. + // Verifies protocol bumps cannot leave the installer target on the old project runner release. result := AnalyzeProtocolMinimumVersionGuard( ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 1, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", + RequiredProtocolVersion: 1, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", }, ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", }) if !result.NeedsMinimumVersionUpdate { @@ -33,14 +33,14 @@ func TestAnalyzeProtocolMinimumVersionGuard_WhenProtocolAndMinimumVersionChange_ // Verifies paired protocol and installer target updates clear the update-omission warning. result := AnalyzeProtocolMinimumVersionGuard( ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 1, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", + RequiredProtocolVersion: 1, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", }, ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.33", + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.33", }) if result.NeedsMinimumVersionUpdate { @@ -52,14 +52,14 @@ func TestAnalyzeProtocolMinimumVersionGuard_WhenProtocolDoesNotChange_DoesNotWar // Verifies ordinary package edits do not force a CLI installer target bump. result := AnalyzeProtocolMinimumVersionGuard( ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", }, ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", }) if result.NeedsMinimumVersionUpdate { @@ -68,18 +68,84 @@ func TestAnalyzeProtocolMinimumVersionGuard_WhenProtocolDoesNotChange_DoesNotWar } func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseMatches_Passes(t *testing.T) { - // Verifies protocol bump PRs pass only after the selected CLI release advertises the new protocol. + // Verifies protocol bump PRs pass only after the selected project runner release advertises the new protocol. result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, }) if result.exitCode != 0 { t.Fatalf("expected exit code 0, got %d\nstderr: %s", result.exitCode, result.stderr) } assertProtocolMinimumVersionLogContains(t, result.stdout, "Protocol minimum version guard passed.") - assertProtocolMinimumVersionLogContains(t, result.gitLog, "cli-v3.0.0-beta.33:cli/contract.json") + assertProtocolMinimumVersionLogContains(t, result.gitLog, "uloop-project-runner-v3.0.0-beta.33:cli/contract.json") +} + +func TestRunProtocolMinimumVersionGuard_WhenBaseUsesPreRenameMinimumConstant_Passes(t *testing.T) { + // Verifies rename PRs can compare against base branches that still use the old CLI minimum constant name. + result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ + baseContent: buildPreRenameProtocolMinimumVersionConstants(2, "3.0.0-beta.40"), + headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.40"), + }) + + if result.exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\nstderr: %s", result.exitCode, result.stderr) + } + assertProtocolMinimumVersionLogContains(t, result.stdout, "Protocol minimum version guard passed.") +} + +func TestRunProtocolMinimumVersionGuard_WhenBaseUsesPreRenameAndHeadMinimumMatchesCurrentProjectRunner_Passes(t *testing.T) { + // Verifies the project runner tag rename can bootstrap its first renamed release. + result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ + baseContent: buildPreRenameProtocolMinimumVersionConstants(2, "3.0.0-beta.40"), + headContent: buildProtocolMinimumVersionConstants(3, "3.0.0-beta.43"), + headContractContent: `{"schemaVersion":1,"protocolVersion":3,"projectRunnerVersion":"3.0.0-beta.43"}`, + }) + + if result.exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\nstderr: %s", result.exitCode, result.stderr) + } + assertProtocolMinimumVersionLogContains(t, result.stdout, "Protocol minimum version guard passed.") +} + +func TestRunProtocolMinimumVersionGuard_WhenBootstrapReleaseProtocolDiffers_Fails(t *testing.T) { + // Verifies bootstrap exemption does not hide readable release contract mismatches. + result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ + baseContent: buildPreRenameProtocolMinimumVersionConstants(2, "3.0.0-beta.40"), + headContent: buildProtocolMinimumVersionConstants(3, "3.0.0-beta.43"), + headContractContent: `{"schemaVersion":1,"protocolVersion":3,"projectRunnerVersion":"3.0.0-beta.43"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.43"}`, + }) + + if result.exitCode != 1 { + t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) + } + assertProtocolMinimumVersionLogContains(t, result.stderr, "advertises protocol 2") +} + +func TestParseProtocolMinimumVersionValues_WhenPreRenameMinimumConstantIsUsed_Fails(t *testing.T) { + // Verifies current package constants must use the project runner minimum version name. + _, err := ParseProtocolMinimumVersionValues([]byte(buildPreRenameProtocolMinimumVersionConstants(2, "3.0.0-beta.40"))) + + if err == nil { + t.Fatal("expected pre-rename minimum version constant to fail") + } + if !strings.Contains(err.Error(), "does not define MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION") { + t.Fatalf("expected missing project runner minimum version error, got %v", err) + } +} + +func TestParseProtocolMinimumVersionValues_WhenMinimumVersionIsInvalid_Fails(t *testing.T) { + // Verifies invalid minimum project runner versions fail before release tag construction. + _, err := ParseProtocolMinimumVersionValues([]byte(buildProtocolMinimumVersionConstants(2, "3.0.0-01"))) + + if err == nil { + t.Fatal("expected invalid minimum project runner version to fail") + } + if !strings.Contains(err.Error(), "MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION must be semver") { + t.Fatalf("expected semver error, got %v", err) + } } func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseProtocolDiffers_Fails(t *testing.T) { @@ -87,45 +153,45 @@ func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseProtocolDiffers_Fails( result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":1,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":1,"projectRunnerVersion":"3.0.0-beta.33"}`, }) if result.exitCode != 1 { t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } - assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published CLI release") + assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published project runner release") assertProtocolMinimumVersionLogContains(t, result.stderr, "advertises protocol 1") } func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseIsDraft_Fails(t *testing.T) { - // Verifies protocol bump PRs wait for a published CLI release, not only a git tag. + // Verifies protocol bump PRs wait for a published project runner release, not only a git tag. result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, releaseView: `{"isDraft":true,"assets":[]}`, }) if result.exitCode != 1 { t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } - assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published CLI release") + assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published project runner release") assertProtocolMinimumVersionLogContains(t, result.stderr, "is still draft") } func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseAssetsAreMissing_Fails(t *testing.T) { - // Verifies protocol bump PRs wait for installable native CLI release assets. + // Verifies protocol bump PRs wait for installable native project runner release assets. result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, releaseView: `{"isDraft":false,"assets":[]}`, }) if result.exitCode != 1 { t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } - assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published CLI release") + assertProtocolMinimumVersionLogContains(t, result.stderr, "does not point to a published project runner release") assertProtocolMinimumVersionLogContains(t, result.stderr, "is missing release asset") } @@ -134,35 +200,35 @@ func TestRunProtocolMinimumVersionGuard_WhenOnlyMinimumReleaseProtocolDiffers_Fa result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":1,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":1,"projectRunnerVersion":"3.0.0-beta.33"}`, }) if result.exitCode != 1 { t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } - assertProtocolMinimumVersionLogContains(t, result.stderr, "`MINIMUM_REQUIRED_CLI_VERSION` changed") + assertProtocolMinimumVersionLogContains(t, result.stderr, "`MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` changed") assertProtocolMinimumVersionLogContains(t, result.stderr, "advertises protocol 1") } func TestVerifyMinimumCliReleaseProtocol_WhenTagProtocolMatches_Passes(t *testing.T) { - // Verifies release validation accepts a CLI tag that advertises the required protocol. + // Verifies release validation accepts a project runner tag that advertises the required protocol. err := VerifyMinimumCliReleaseProtocol(ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.33", - }, []byte(`{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`)) + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.33", + }, []byte(`{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`)) if err != nil { t.Fatalf("expected matching release protocol, got %v", err) } } func TestVerifyMinimumCliReleaseProtocol_WhenTagProtocolIsMissing_Fails(t *testing.T) { - // Verifies release validation rejects CLI tags that predate protocol metadata. + // Verifies release validation rejects project runner tags that predate protocol metadata. err := VerifyMinimumCliReleaseProtocol(ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.32", - }, []byte(`{"schemaVersion":1,"cliVersion":"3.0.0-beta.32"}`)) + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.32", + }, []byte(`{"schemaVersion":1,"projectRunnerVersion":"3.0.0-beta.32"}`)) if err == nil { t.Fatal("expected missing protocolVersion to fail") @@ -175,10 +241,10 @@ func TestVerifyMinimumCliReleaseProtocol_WhenTagProtocolIsMissing_Fails(t *testi func TestVerifyMinimumCliReleaseProtocol_WhenProtocolVersionIsOutOfRange_FailsAsMissingMetadata(t *testing.T) { // Verifies malformed protocol metadata is reported consistently with missing metadata. err := VerifyMinimumCliReleaseProtocol(ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 2, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.33", - }, []byte(`{"schemaVersion":1,"protocolVersion":999999999999999999999999999999,"cliVersion":"3.0.0-beta.33"}`)) + RequiredProtocolVersion: 2, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.33", + }, []byte(`{"schemaVersion":1,"protocolVersion":999999999999999999999999999999,"projectRunnerVersion":"3.0.0-beta.33"}`)) if err == nil { t.Fatal("expected out-of-range protocolVersion to fail") @@ -192,12 +258,12 @@ func TestVerifyMinimumCliReleaseProtocol_WhenProtocolVersionIsOutOfRange_FailsAs } func TestVerifyMinimumCliReleaseProtocol_WhenTagProtocolDiffers_Fails(t *testing.T) { - // Verifies release validation rejects published CLIs from a different protocol generation. + // Verifies release validation rejects published project runners from a different protocol generation. err := VerifyMinimumCliReleaseProtocol(ProtocolMinimumVersionValues{ - RequiredProtocolVersion: 3, - HasRequiredProtocol: true, - MinimumCliVersion: "3.0.0-beta.33", - }, []byte(`{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`)) + RequiredProtocolVersion: 3, + HasRequiredProtocol: true, + MinimumProjectRunnerVersion: "3.0.0-beta.33", + }, []byte(`{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`)) if err == nil { t.Fatal("expected mismatched protocolVersion to fail") @@ -223,7 +289,7 @@ func TestRunMinimumCliReleaseProtocolCheck_WhenRefIsProvided_ReadsValuesAtRef(t prepareProtocolMinimumVersionGitContents(t, workDir, protocolMinimumVersionRefCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, }) t.Setenv("PATH", mockBin+string(os.PathListSeparator)+os.Getenv("PATH")) @@ -242,9 +308,49 @@ func TestRunMinimumCliReleaseProtocolCheck_WhenRefIsProvided_ReadsValuesAtRef(t if exitCode != 0 { t.Fatalf("expected exit code 0, got %d\nstderr: %s", exitCode, stderr.String()) } - assertProtocolMinimumVersionLogContains(t, stdout.String(), "Minimum CLI release cli-v3.0.0-beta.33 advertises protocol 2.") + assertProtocolMinimumVersionLogContains(t, stdout.String(), "Minimum project runner release uloop-project-runner-v3.0.0-beta.33 advertises protocol 2.") assertProtocolMinimumVersionLogContains(t, readFile(t, gitLogPath), "protocol-release:"+protocolMinimumVersionFile) - assertProtocolMinimumVersionLogContains(t, readFile(t, ghLogPath), "release view cli-v3.0.0-beta.33") + assertProtocolMinimumVersionLogContains(t, readFile(t, ghLogPath), "release view uloop-project-runner-v3.0.0-beta.33") +} + +func TestRunMinimumCliReleaseProtocolCheck_WhenMinimumVersionHasPrefix_NormalizesReleaseTag(t *testing.T) { + // Verifies v-prefixed package constants map to a project runner tag with one prefix. + workDir := t.TempDir() + mockBin := filepath.Join(workDir, "bin") + err := os.MkdirAll(mockBin, 0o755) + if err != nil { + t.Fatalf("failed to create mock bin: %v", err) + } + + gitLogPath := filepath.Join(workDir, "git.log") + ghLogPath := filepath.Join(workDir, "gh.log") + writeProtocolMinimumVersionMockGit(t, filepath.Join(mockBin, "git")) + writeProtocolMinimumVersionMockGH(t, filepath.Join(mockBin, "gh")) + prepareProtocolMinimumVersionGitContents(t, workDir, protocolMinimumVersionRefCase{ + baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), + headContent: buildProtocolMinimumVersionConstants(2, "v3.0.0-beta.33"), + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, + }) + + t.Setenv("PATH", mockBin+string(os.PathListSeparator)+os.Getenv("PATH")) + t.Setenv("ULOOP_REPOSITORY_ROOT", workDir) + t.Setenv("GIT_LOG", gitLogPath) + t.Setenv("GH_LOG", ghLogPath) + + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} + exitCode := RunMinimumCliReleaseProtocolCheck( + context.Background(), + &stdout, + &stderr, + "protocol-release") + + if exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\nstderr: %s", exitCode, stderr.String()) + } + assertProtocolMinimumVersionLogContains(t, stdout.String(), "Minimum project runner release uloop-project-runner-v3.0.0-beta.33 advertises protocol 2.") + assertProtocolMinimumVersionLogContains(t, readFile(t, gitLogPath), "uloop-project-runner-v3.0.0-beta.33:cli/contract.json") + assertProtocolMinimumVersionLogContains(t, readFile(t, ghLogPath), "release view uloop-project-runner-v3.0.0-beta.33") } func TestRunProtocolMinimumVersionComment_WhenWarningExists_UpsertsComment(t *testing.T) { @@ -268,7 +374,7 @@ func TestRunProtocolMinimumVersionComment_WhenWarningIsResolved_DeletesComment(t result := runProtocolMinimumVersionCommentCase(t, protocolMinimumVersionCommentCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":2,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":2,"projectRunnerVersion":"3.0.0-beta.33"}`, commentIDs: "123", }) @@ -284,7 +390,7 @@ func TestRunProtocolMinimumVersionComment_WhenMinimumReleaseProtocolDiffers_Upse result := runProtocolMinimumVersionCommentCase(t, protocolMinimumVersionCommentCase{ baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), - releaseContent: `{"schemaVersion":1,"protocolVersion":1,"cliVersion":"3.0.0-beta.33"}`, + releaseContent: `{"schemaVersion":1,"protocolVersion":1,"projectRunnerVersion":"3.0.0-beta.33"}`, commentIDs: "123", }) @@ -296,10 +402,11 @@ func TestRunProtocolMinimumVersionComment_WhenMinimumReleaseProtocolDiffers_Upse } type protocolMinimumVersionRefCase struct { - baseContent string - headContent string - releaseContent string - releaseView string + baseContent string + headContent string + headContractContent string + releaseContent string + releaseView string } type protocolMinimumVersionGuardRunResult struct { @@ -417,6 +524,11 @@ func prepareProtocolMinimumVersionGitContents(t *testing.T, workDir string, test releaseContentPath := filepath.Join(workDir, "release-contract.json") writeFile(t, baseContentPath, testCase.baseContent) writeFile(t, headContentPath, testCase.headContent) + if testCase.headContractContent != "" { + headContractContentPath := filepath.Join(workDir, "head-contract.json") + writeFile(t, headContractContentPath, testCase.headContractContent) + t.Setenv("GIT_HEAD_CONTRACT_CONTENT", headContractContentPath) + } if testCase.releaseContent != "" { writeFile(t, releaseContentPath, testCase.releaseContent) t.Setenv("GIT_RELEASE_CONTENT", releaseContentPath) @@ -425,7 +537,18 @@ func prepareProtocolMinimumVersionGitContents(t *testing.T, workDir string, test t.Setenv("GIT_HEAD_CONTENT", headContentPath) } -func buildProtocolMinimumVersionConstants(requiredProtocolVersion int, minimumCliVersion string) string { +func buildProtocolMinimumVersionConstants(requiredProtocolVersion int, minimumProjectRunnerVersion string) string { + return `namespace Tests { +public static class CliConstants { +public const int REQUIRED_CLI_PROTOCOL_VERSION = ` + + strconv.Itoa(requiredProtocolVersion) + + `; +public const string MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION = "` + minimumProjectRunnerVersion + `"; +} + }` +} + +func buildPreRenameProtocolMinimumVersionConstants(requiredProtocolVersion int, minimumCliVersion string) string { return `namespace Tests { public static class CliConstants { public const int REQUIRED_CLI_PROTOCOL_VERSION = ` + @@ -453,21 +576,28 @@ if [ "$1" = "-C" ]; then shift 2 fi -if [ "$1" = "show" ]; then - case "$2" in - origin/v3-beta:*) cat "$GIT_BASE_CONTENT" ;; - protocol-pr-head:*) cat "$GIT_HEAD_CONTENT" ;; - protocol-release:*) cat "$GIT_HEAD_CONTENT" ;; - cli-v*:cli/contract.json) - if [ -n "${GIT_RELEASE_CONTENT:-}" ]; then - cat "$GIT_RELEASE_CONTENT" - else - echo "release not found" >&2 - exit 1 - fi - ;; - *) echo "unexpected git show ref: $2" >&2; exit 1 ;; - esac + if [ "$1" = "show" ]; then + case "$2" in + origin/v3-beta:*) cat "$GIT_BASE_CONTENT" ;; + protocol-pr-head:cli/contract.json) + if [ -n "${GIT_HEAD_CONTRACT_CONTENT:-}" ]; then + cat "$GIT_HEAD_CONTRACT_CONTENT" + else + cat "$GIT_HEAD_CONTENT" + fi + ;; + protocol-pr-head:*) cat "$GIT_HEAD_CONTENT" ;; + protocol-release:*) cat "$GIT_HEAD_CONTENT" ;; + uloop-project-runner-v*:cli/contract.json) + if [ -n "${GIT_RELEASE_CONTENT:-}" ]; then + cat "$GIT_RELEASE_CONTENT" + else + echo "release not found" >&2 + exit 1 + fi + ;; + *) echo "unexpected git show ref: $2" >&2; exit 1 ;; + esac exit 0 fi @@ -493,7 +623,7 @@ if [ "$1" = "release" ] && [ "$2" = "view" ]; then if [ -n "${GH_RELEASE_VIEW:-}" ]; then printf '%s\n' "$GH_RELEASE_VIEW" else - printf '%s\n' '{"isDraft":false,"assets":[{"name":"uloop-cli-darwin-amd64.tar.gz","size":1},{"name":"uloop-cli-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-cli-windows-amd64.zip","size":1},{"name":"uloop-cli-windows-amd64.zip.sha256","size":1},{"name":"uloop-darwin-amd64.tar.gz","size":1},{"name":"uloop-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-darwin-arm64.tar.gz","size":1},{"name":"uloop-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-windows-amd64.zip","size":1},{"name":"uloop-windows-amd64.zip.sha256","size":1}]}' + printf '%s\n' '{"isDraft":false,"assets":[{"name":"uloop-project-runner-darwin-amd64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-windows-amd64.zip","size":1},{"name":"uloop-project-runner-windows-amd64.zip.sha256","size":1}]}' fi exit 0 fi diff --git a/cli/internal/automation/protocol_minimum_version_parse.go b/cli/internal/automation/protocol_minimum_version_parse.go new file mode 100644 index 000000000..b9eeaa5c9 --- /dev/null +++ b/cli/internal/automation/protocol_minimum_version_parse.go @@ -0,0 +1,82 @@ +package automation + +import ( + "fmt" + "regexp" + "strconv" + + sharedversion "github.com/hatayama/unity-cli-loop/cli/internal/version" +) + +var ( + requiredProtocolVersionPattern = regexp.MustCompile(`REQUIRED_CLI_PROTOCOL_VERSION\s*=\s*(\d+)`) + minimumProjectRunnerVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION\s*=\s*"([^"]+)"`) + preRenameMinimumProjectRunnerVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_CLI_VERSION\s*=\s*"([^"]+)"`) +) + +func ParseProtocolMinimumVersionValues(content []byte) (ProtocolMinimumVersionValues, error) { + text := string(content) + values, err := parseProtocolMinimumVersionValuesWithMinimumVersion(text) + if err != nil { + return ProtocolMinimumVersionValues{}, err + } + return values, nil +} + +func parseProtocolMinimumVersionBaseValues(content []byte) (ProtocolMinimumVersionValues, error) { + text := string(content) + if _, ok := parseMinimumProjectRunnerVersion(text); ok { + return ParseProtocolMinimumVersionValues(content) + } + + minimumMatches := preRenameMinimumProjectRunnerVersionPattern.FindStringSubmatch(text) + if len(minimumMatches) != 2 { + return ParseProtocolMinimumVersionValues(content) + } + values, err := parseProtocolMinimumVersionValues(text, normalizeProjectRunnerVersion(minimumMatches[1]), "MINIMUM_REQUIRED_CLI_VERSION") + if err != nil { + return ProtocolMinimumVersionValues{}, err + } + values.UsesPreRenameMinimumVersion = true + return values, nil +} + +func parseProtocolMinimumVersionValuesWithMinimumVersion(text string) (ProtocolMinimumVersionValues, error) { + minimumProjectRunnerVersion, ok := parseMinimumProjectRunnerVersion(text) + if !ok { + return ProtocolMinimumVersionValues{}, fmt.Errorf("%s does not define MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION", protocolMinimumVersionFile) + } + return parseProtocolMinimumVersionValues(text, minimumProjectRunnerVersion, "MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION") +} + +func parseMinimumProjectRunnerVersion(text string) (string, bool) { + minimumMatches := minimumProjectRunnerVersionPattern.FindStringSubmatch(text) + if len(minimumMatches) == 2 { + return normalizeProjectRunnerVersion(minimumMatches[1]), true + } + return "", false +} + +func parseProtocolMinimumVersionValues( + text string, + minimumProjectRunnerVersion string, + minimumVersionConstantName string, +) (ProtocolMinimumVersionValues, error) { + values := ProtocolMinimumVersionValues{ + MinimumProjectRunnerVersion: minimumProjectRunnerVersion, + } + + requiredMatches := requiredProtocolVersionPattern.FindStringSubmatch(text) + if len(requiredMatches) == 2 { + requiredProtocolVersion, err := strconv.Atoi(requiredMatches[1]) + if err != nil { + return ProtocolMinimumVersionValues{}, fmt.Errorf("REQUIRED_CLI_PROTOCOL_VERSION is not an integer: %w", err) + } + values.RequiredProtocolVersion = requiredProtocolVersion + values.HasRequiredProtocol = true + } + if _, ok := sharedversion.Compare(minimumProjectRunnerVersion, minimumProjectRunnerVersion); !ok { + return ProtocolMinimumVersionValues{}, fmt.Errorf("%s %s must be semver, got %q", protocolMinimumVersionFile, minimumVersionConstantName, minimumProjectRunnerVersion) + } + return values, nil +} diff --git a/cli/internal/automation/release_pr_checks_test.go b/cli/internal/automation/release_pr_checks_test.go index f4f7eed8d..939f4e494 100644 --- a/cli/internal/automation/release_pr_checks_test.go +++ b/cli/internal/automation/release_pr_checks_test.go @@ -312,7 +312,7 @@ fi if [ "$1" = "show" ]; then case "$2" in - cli-v*:cli/contract.json) + uloop-project-runner-v*:cli/contract.json) if [ -n "${GIT_RELEASE_CONTRACT:-}" ]; then cat "$GIT_RELEASE_CONTRACT" else diff --git a/cli/internal/cli/dispatcher.go b/cli/internal/cli/dispatcher.go index 50ee42b47..d92a507a9 100644 --- a/cli/internal/cli/dispatcher.go +++ b/cli/internal/cli/dispatcher.go @@ -23,11 +23,11 @@ const ( dispatcherCacheDirectoryName = "uloop" dispatcherVersionsDirectoryName = "versions" dispatcherUpdateStateFileName = "dispatcher-update.json" - dispatcherProjectPinRelativePath = ".uloop/cli-pin.json" - dispatcherPackagePinFileName = "cli-pin.json" + dispatcherProjectPinRelativePath = ".uloop/project-runner-pin.json" + dispatcherPackagePinFileName = "project-runner-pin.json" dispatcherUnityPackageName = "io.github.hatayama.uloopmcp" - dispatcherRealCLIUnixFileName = "uloop-cli" - dispatcherRealCLIWindowsFileName = "uloop-cli.exe" + dispatcherRealCLIUnixFileName = "uloop-project-runner" + dispatcherRealCLIWindowsFileName = "uloop-project-runner.exe" dispatcherReleaseRepository = "hatayama/unity-cli-loop" dispatcherReleaseBaseURL = "https://github.com/" + dispatcherReleaseRepository + "/releases/download" dispatcherSelfUpdateInterval = 24 * time.Hour @@ -285,12 +285,12 @@ func dispatcherPinResolutionError(projectRoot string, cause error) cliError { return cliError{ ErrorCode: errorCodeInternalError, Phase: errorPhaseProjectResolve, - Message: "Could not resolve the required uloop CLI for this Unity project.", + Message: "Could not resolve the required uloop project runner for this Unity project.", Retryable: true, SafeToRetry: true, ProjectRoot: projectRoot, NextActions: []string{ - "Open the Unity project once so Unity CLI Loop can write `.uloop/cli-pin.json`.", + "Open the Unity project once so Unity CLI Loop can write `.uloop/project-runner-pin.json`.", "Run the CLI setup from Unity CLI Loop Settings if the pin file is still missing.", }, Details: map[string]any{ @@ -303,15 +303,15 @@ func dispatcherRealCLIResolutionError(projectRoot string, pin dispatcherPin, cau return cliError{ ErrorCode: errorCodeInternalError, Phase: errorPhaseExecution, - Message: "Could not prepare the pinned uloop CLI version.", + Message: "Could not prepare the pinned uloop project runner version.", Retryable: true, SafeToRetry: true, ProjectRoot: projectRoot, NextActions: []string{"Check network access to GitHub releases, then retry the command."}, Details: map[string]any{ - "Cause": cause.Error(), - "CliVersion": pin.CLIVersion, - "PinSource": pin.SourcePath, + "Cause": cause.Error(), + "ProjectRunnerVersion": pin.ProjectRunnerVersion, + "PinSource": pin.SourcePath, }, } } diff --git a/cli/internal/cli/dispatcher_download.go b/cli/internal/cli/dispatcher_download.go index de7da8126..ccdd66451 100644 --- a/cli/internal/cli/dispatcher_download.go +++ b/cli/internal/cli/dispatcher_download.go @@ -24,8 +24,8 @@ import ( var dispatcherHTTPClient = &http.Client{Timeout: 2 * time.Minute} func resolveDispatcherRealCLI(ctx context.Context, pin dispatcherPin, stderr io.Writer) (string, error) { - pin.CLIVersion = strings.TrimSpace(pin.CLIVersion) - if err := validateDispatcherCLIVersion(pin.CLIVersion); err != nil { + pin.ProjectRunnerVersion = strings.TrimSpace(pin.ProjectRunnerVersion) + if err := validateDispatcherProjectRunnerVersion(pin.ProjectRunnerVersion); err != nil { return "", err } if siblingPath, ok := dispatcherSiblingRealCLIPath(pin); ok { @@ -36,16 +36,16 @@ func resolveDispatcherRealCLI(ctx context.Context, pin dispatcherPin, stderr io. if err != nil { return "", err } - realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, pin.CLIVersion, runtime.GOOS, runtime.GOARCH) + realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, pin.ProjectRunnerVersion, runtime.GOOS, runtime.GOARCH) if isExecutableFile(realCLIPath) { return realCLIPath, nil } - return downloadDispatcherRealCLI(ctx, cacheRoot, pin.CLIVersion, runtime.GOOS, runtime.GOARCH, stderr) + return downloadDispatcherRealCLIForPin(ctx, cacheRoot, pin, runtime.GOOS, runtime.GOARCH, stderr) } func dispatcherSiblingRealCLIPath(pin dispatcherPin) (string, bool) { - if pin.CLIVersion != version { + if pin.ProjectRunnerVersion != version { return "", false } executablePath, err := os.Executable() @@ -91,11 +91,11 @@ func dispatcherCacheRoot(goos string) (string, error) { } } -func dispatcherCachedRealCLIPath(cacheRoot string, cliVersion string, goos string, goarch string) string { +func dispatcherCachedRealCLIPath(cacheRoot string, projectRunnerVersion string, goos string, goarch string) string { return filepath.Join( cacheRoot, dispatcherVersionsDirectoryName, - cliVersion, + projectRunnerVersion, dispatcherPlatformName(goos, goarch), dispatcherRealCLIFileName(goos)) } @@ -122,12 +122,22 @@ func isExecutableFile(filePath string) bool { return info.Mode()&0o111 != 0 } -func downloadDispatcherRealCLI(ctx context.Context, cacheRoot string, cliVersion string, goos string, goarch string, stderr io.Writer) (string, error) { +func downloadDispatcherRealCLI(ctx context.Context, cacheRoot string, projectRunnerVersion string, goos string, goarch string, stderr io.Writer) (string, error) { + return downloadDispatcherRealCLIForPin( + ctx, + cacheRoot, + dispatcherPin{ProjectRunnerVersion: projectRunnerVersion}, + goos, + goarch, + stderr) +} + +func downloadDispatcherRealCLIForPin(ctx context.Context, cacheRoot string, pin dispatcherPin, goos string, goarch string, stderr io.Writer) (string, error) { assetName, err := dispatcherReleaseAssetName(goos, goarch) if err != nil { return "", err } - realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, cliVersion, goos, goarch) + realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, pin.ProjectRunnerVersion, goos, goarch) if err := os.MkdirAll(filepath.Dir(realCLIPath), 0o755); err != nil { return "", err } @@ -142,8 +152,8 @@ func downloadDispatcherRealCLI(ctx context.Context, cacheRoot string, cliVersion archivePath := filepath.Join(tempDir, assetName) checksumPath := archivePath + ".sha256" - assetURL := dispatcherReleaseAssetURL(cliVersion, assetName) - writeFormat(stderr, "uloop: downloading pinned CLI %s for %s...\n", cliVersion, dispatcherPlatformName(goos, goarch)) + assetURL := dispatcherReleaseAssetURL(pin.ProjectRunnerVersion, assetName) + writeFormat(stderr, "uloop: downloading pinned project runner %s for %s...\n", pin.ProjectRunnerVersion, dispatcherPlatformName(goos, goarch)) if err := downloadDispatcherFile(ctx, assetURL, archivePath); err != nil { return "", err } @@ -184,24 +194,25 @@ func installDownloadedDispatcherRealCLI(tempRealCLIPath string, realCLIPath stri } func dispatcherReleaseAssetName(goos string, goarch string) (string, error) { + assetPrefix := "uloop-project-runner" switch goos { case "darwin": if goarch != "arm64" && goarch != "amd64" { return "", fmt.Errorf("unsupported darwin architecture: %s", goarch) } - return "uloop-cli-darwin-" + goarch + ".tar.gz", nil + return assetPrefix + "-darwin-" + goarch + ".tar.gz", nil case "windows": if goarch != "amd64" { return "", fmt.Errorf("unsupported windows architecture: %s", goarch) } - return "uloop-cli-windows-amd64.zip", nil + return assetPrefix + "-windows-amd64.zip", nil default: return "", fmt.Errorf("unsupported platform: %s-%s", goos, goarch) } } -func dispatcherReleaseAssetURL(cliVersion string, assetName string) string { - return dispatcherReleaseBaseURL + "/" + sharedupdate.CLIReleaseTag(cliVersion) + "/" + assetName +func dispatcherReleaseAssetURL(projectRunnerVersion string, assetName string) string { + return dispatcherReleaseBaseURL + "/" + sharedupdate.ProjectRunnerReleaseTag(projectRunnerVersion) + "/" + assetName } func downloadDispatcherFile(ctx context.Context, url string, destinationPath string) error { @@ -269,11 +280,12 @@ func extractDispatcherRealCLI(archivePath string, assetName string, destinationP } func extractDispatcherRealCLIFromTarGz(archivePath string, destinationPath string, goos string) error { - found, err := extractDispatcherCLIFromTarGzEntry(archivePath, destinationPath, dispatcherRealCLIFileName(goos)) + entryFileName := dispatcherRealCLIFileName(goos) + found, err := extractDispatcherCLIFromTarGzEntry(archivePath, destinationPath, entryFileName) if err != nil || found { return err } - return fmt.Errorf("archive does not contain %s", dispatcherRealCLIFileName(goos)) + return fmt.Errorf("archive does not contain %s", entryFileName) } func extractDispatcherCLIFromTarGzEntry(archivePath string, destinationPath string, entryFileName string) (bool, error) { @@ -319,11 +331,12 @@ func extractDispatcherRealCLIFromZip(archivePath string, destinationPath string, defer func() { _ = reader.Close() }() - found, err := extractDispatcherCLIFromZipEntry(reader, destinationPath, dispatcherRealCLIFileName(goos)) + entryFileName := dispatcherRealCLIFileName(goos) + found, err := extractDispatcherCLIFromZipEntry(reader, destinationPath, entryFileName) if err != nil || found { return err } - return fmt.Errorf("archive does not contain %s", dispatcherRealCLIFileName(goos)) + return fmt.Errorf("archive does not contain %s", entryFileName) } func extractDispatcherCLIFromZipEntry(reader *zip.ReadCloser, destinationPath string, entryFileName string) (bool, error) { diff --git a/cli/internal/cli/dispatcher_pin.go b/cli/internal/cli/dispatcher_pin.go index 9c00a5aad..e0a894a93 100644 --- a/cli/internal/cli/dispatcher_pin.go +++ b/cli/internal/cli/dispatcher_pin.go @@ -11,17 +11,17 @@ import ( ) var ( - dispatcherMinimumCliVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_CLI_VERSION\s*=\s*"([^"]+)"`) - dispatcherMinimumVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_DISPATCHER_VERSION\s*=\s*"([^"]+)"`) - dispatcherRequiredProtocolVersionPattern = regexp.MustCompile(`REQUIRED_CLI_PROTOCOL_VERSION\s*=\s*(\d+)`) - dispatcherCLIVersionPattern = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$`) + dispatcherMinimumProjectRunnerVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION\s*=\s*"([^"]+)"`) + dispatcherMinimumVersionPattern = regexp.MustCompile(`MINIMUM_REQUIRED_DISPATCHER_VERSION\s*=\s*"([^"]+)"`) + dispatcherRequiredProtocolVersionPattern = regexp.MustCompile(`REQUIRED_CLI_PROTOCOL_VERSION\s*=\s*(\d+)`) + dispatcherProjectRunnerVersionPattern = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z][0-9A-Za-z.-]*)?(?:\+[0-9A-Za-z][0-9A-Za-z.-]*)?$`) ) type dispatcherPin struct { SchemaVersion int `json:"schemaVersion"` PackageName string `json:"packageName"` PackageVersion string `json:"packageVersion"` - CLIVersion string `json:"cliVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` RequiredProtocolVersion int `json:"requiredProtocolVersion"` MinimumDispatcherVersion string `json:"minimumDispatcherVersion"` SourcePath string `json:"-"` @@ -64,7 +64,7 @@ func loadDispatcherPin(projectRoot string) (dispatcherPin, error) { if invalidPackagePinError != nil { return dispatcherPin{}, invalidPackagePinError } - return dispatcherPin{}, fmt.Errorf("cli pin not found under %s", projectRoot) + return dispatcherPin{}, fmt.Errorf("project runner pin not found under %s", projectRoot) } func dispatcherPinCandidatePaths(projectRoot string) []dispatcherPinCandidatePath { @@ -118,16 +118,16 @@ func readDispatcherPin(pinPath string) (dispatcherPin, error) { if err := json.Unmarshal(content, &pin); err != nil { return dispatcherPin{}, fmt.Errorf("failed to parse %s: %w", pinPath, err) } - pin.CLIVersion = normalizeDispatcherVersion(pin.CLIVersion) - if pin.CLIVersion == "" { - return dispatcherPin{}, fmt.Errorf("%s does not define cliVersion", pinPath) + pin.ProjectRunnerVersion = normalizeDispatcherVersion(pin.ProjectRunnerVersion) + if pin.ProjectRunnerVersion == "" { + return dispatcherPin{}, fmt.Errorf("%s does not define projectRunnerVersion", pinPath) } - if err := validateDispatcherCLIVersion(pin.CLIVersion); err != nil { - return dispatcherPin{}, fmt.Errorf("%s defines invalid cliVersion: %w", pinPath, err) + if err := validateDispatcherProjectRunnerVersion(pin.ProjectRunnerVersion); err != nil { + return dispatcherPin{}, fmt.Errorf("%s defines invalid projectRunnerVersion: %w", pinPath, err) } pin.MinimumDispatcherVersion = normalizeDispatcherVersion(pin.MinimumDispatcherVersion) if pin.MinimumDispatcherVersion != "" { - if err := validateDispatcherCLIVersion(pin.MinimumDispatcherVersion); err != nil { + if err := validateDispatcherProjectRunnerVersion(pin.MinimumDispatcherVersion); err != nil { return dispatcherPin{}, fmt.Errorf("%s defines invalid minimumDispatcherVersion: %w", pinPath, err) } } @@ -144,20 +144,20 @@ func readDispatcherPinFromCliConstants(constantsPath string) (dispatcherPin, err return dispatcherPin{}, err } text := string(content) - versionMatch := dispatcherMinimumCliVersionPattern.FindStringSubmatch(text) + versionMatch := dispatcherMinimumProjectRunnerVersionPattern.FindStringSubmatch(text) if len(versionMatch) != 2 { - return dispatcherPin{}, fmt.Errorf("%s does not define MINIMUM_REQUIRED_CLI_VERSION", constantsPath) + return dispatcherPin{}, fmt.Errorf("%s does not define MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION", constantsPath) } - cliVersion := normalizeDispatcherVersion(versionMatch[1]) - if err := validateDispatcherCLIVersion(cliVersion); err != nil { - return dispatcherPin{}, fmt.Errorf("%s defines invalid MINIMUM_REQUIRED_CLI_VERSION: %w", constantsPath, err) + projectRunnerVersion := normalizeDispatcherVersion(versionMatch[1]) + if err := validateDispatcherProjectRunnerVersion(projectRunnerVersion); err != nil { + return dispatcherPin{}, fmt.Errorf("%s defines invalid MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION: %w", constantsPath, err) } dispatcherVersionMatch := dispatcherMinimumVersionPattern.FindStringSubmatch(text) if len(dispatcherVersionMatch) != 2 { return dispatcherPin{}, fmt.Errorf("%s does not define MINIMUM_REQUIRED_DISPATCHER_VERSION", constantsPath) } minimumDispatcherVersion := normalizeDispatcherVersion(dispatcherVersionMatch[1]) - if err := validateDispatcherCLIVersion(minimumDispatcherVersion); err != nil { + if err := validateDispatcherProjectRunnerVersion(minimumDispatcherVersion); err != nil { return dispatcherPin{}, fmt.Errorf("%s defines invalid MINIMUM_REQUIRED_DISPATCHER_VERSION: %w", constantsPath, err) } protocolVersion := 0 @@ -169,7 +169,7 @@ func readDispatcherPinFromCliConstants(constantsPath string) (dispatcherPin, err return dispatcherPin{ SchemaVersion: 1, PackageName: dispatcherUnityPackageName, - CLIVersion: cliVersion, + ProjectRunnerVersion: projectRunnerVersion, RequiredProtocolVersion: protocolVersion, MinimumDispatcherVersion: minimumDispatcherVersion, SourcePath: constantsPath, @@ -184,9 +184,9 @@ func normalizeDispatcherVersion(value string) string { return trimmed } -func validateDispatcherCLIVersion(cliVersion string) error { - if !dispatcherCLIVersionPattern.MatchString(cliVersion) { - return fmt.Errorf("expected semantic version, got %q", cliVersion) +func validateDispatcherProjectRunnerVersion(projectRunnerVersion string) error { + if !dispatcherProjectRunnerVersionPattern.MatchString(projectRunnerVersion) { + return fmt.Errorf("expected semantic version, got %q", projectRunnerVersion) } return nil } diff --git a/cli/internal/cli/dispatcher_test.go b/cli/internal/cli/dispatcher_test.go index 9407a1513..ec96b817b 100644 --- a/cli/internal/cli/dispatcher_test.go +++ b/cli/internal/cli/dispatcher_test.go @@ -200,14 +200,14 @@ func TestRunDispatcherVersionUsesDispatcherVersion(t *testing.T) { } } -func TestResolveDispatcherRealCLIRejectsInvalidCLIVersion(t *testing.T) { - // Verifies project pins cannot escape the dispatcher cache through cliVersion path segments. +func TestResolveDispatcherRealCLIRejectsInvalidProjectRunnerVersion(t *testing.T) { + // Verifies project pins cannot escape the dispatcher cache through projectRunnerVersion path segments. t.Setenv(dispatcherCacheDirEnvName, t.TempDir()) - _, err := resolveDispatcherRealCLI(context.Background(), dispatcherPin{CLIVersion: "../../../../payload"}, io.Discard) + _, err := resolveDispatcherRealCLI(context.Background(), dispatcherPin{ProjectRunnerVersion: "../../../../payload"}, io.Discard) if err == nil { - t.Fatal("expected invalid cliVersion error") + t.Fatal("expected invalid projectRunnerVersion error") } } @@ -341,15 +341,14 @@ func stubDispatcherUpdateHooks(t *testing.T, updatedVersion string) func() { } } -func TestExtractDispatcherRealCLIFromTarPrefersRealCLI(t *testing.T) { - // Verifies legacy bridge archives that contain dispatcher first still extract the real CLI binary. +func TestExtractDispatcherRealCLIFromTarRequiresProjectRunnerAsset(t *testing.T) { + // Verifies project runner release archives extract the project runner binary. tempDir := t.TempDir() - archivePath := filepath.Join(tempDir, "uloop-darwin-arm64.tar.gz") + archivePath := filepath.Join(tempDir, "uloop-project-runner-darwin-arm64.tar.gz") writeDispatcherTarGzArchive(t, archivePath, []dispatcherArchiveTestEntry{ - {Name: "uloop", Content: "dispatcher"}, - {Name: "uloop-cli", Content: "real"}, + {Name: "uloop-project-runner", Content: "real"}, }) - destinationPath := filepath.Join(tempDir, "uloop-cli") + destinationPath := filepath.Join(tempDir, "uloop-project-runner") err := extractDispatcherRealCLI(archivePath, filepath.Base(archivePath), destinationPath, "darwin") if err != nil { @@ -358,47 +357,14 @@ func TestExtractDispatcherRealCLIFromTarPrefersRealCLI(t *testing.T) { assertFileContent(t, destinationPath, "real") } -func TestExtractDispatcherRealCLIFromZipPrefersRealCLI(t *testing.T) { - // Verifies Windows legacy bridge archives that contain dispatcher first still extract the real CLI binary. +func TestExtractDispatcherRealCLIFromZipRequiresProjectRunnerAsset(t *testing.T) { + // Verifies Windows project runner release archives extract the project runner binary. tempDir := t.TempDir() - archivePath := filepath.Join(tempDir, "uloop-windows-amd64.zip") + archivePath := filepath.Join(tempDir, "uloop-project-runner-windows-amd64.zip") writeDispatcherZipArchive(t, archivePath, []dispatcherArchiveTestEntry{ - {Name: "uloop.exe", Content: "dispatcher"}, - {Name: "uloop-cli.exe", Content: "real"}, + {Name: "uloop-project-runner.exe", Content: "real"}, }) - destinationPath := filepath.Join(tempDir, "uloop-cli.exe") - - err := extractDispatcherRealCLI(archivePath, filepath.Base(archivePath), destinationPath, "windows") - if err != nil { - t.Fatalf("extractDispatcherRealCLI failed: %v", err) - } - assertFileContent(t, destinationPath, "real") -} - -func TestExtractDispatcherRealCLIFromTarRequiresRealCLIAsset(t *testing.T) { - // Verifies CLI release archives extract the real CLI binary without dispatcher payloads. - tempDir := t.TempDir() - archivePath := filepath.Join(tempDir, "uloop-cli-darwin-arm64.tar.gz") - writeDispatcherTarGzArchive(t, archivePath, []dispatcherArchiveTestEntry{ - {Name: "uloop-cli", Content: "real"}, - }) - destinationPath := filepath.Join(tempDir, "uloop-cli") - - err := extractDispatcherRealCLI(archivePath, filepath.Base(archivePath), destinationPath, "darwin") - if err != nil { - t.Fatalf("extractDispatcherRealCLI failed: %v", err) - } - assertFileContent(t, destinationPath, "real") -} - -func TestExtractDispatcherRealCLIFromZipRequiresRealCLIAsset(t *testing.T) { - // Verifies Windows CLI release archives extract the real CLI binary without dispatcher payloads. - tempDir := t.TempDir() - archivePath := filepath.Join(tempDir, "uloop-cli-windows-amd64.zip") - writeDispatcherZipArchive(t, archivePath, []dispatcherArchiveTestEntry{ - {Name: "uloop-cli.exe", Content: "real"}, - }) - destinationPath := filepath.Join(tempDir, "uloop-cli.exe") + destinationPath := filepath.Join(tempDir, "uloop-project-runner.exe") err := extractDispatcherRealCLI(archivePath, filepath.Base(archivePath), destinationPath, "windows") if err != nil { @@ -415,11 +381,11 @@ func TestDispatcherHTTPClientHasDownloadTimeout(t *testing.T) { } func TestDownloadDispatcherRealCLIWritesDownloadStatus(t *testing.T) { - // Verifies cache misses tell callers that dispatcher is downloading the pinned CLI. + // Verifies cache misses tell callers that dispatcher is downloading the pinned project runner. tempDir := t.TempDir() - archivePath := filepath.Join(tempDir, "uloop-cli-darwin-arm64.tar.gz") + archivePath := filepath.Join(tempDir, "uloop-project-runner-darwin-arm64.tar.gz") writeDispatcherTarGzArchive(t, archivePath, []dispatcherArchiveTestEntry{ - {Name: "uloop-cli", Content: "real"}, + {Name: "uloop-project-runner", Content: "real"}, }) archiveContent, err := os.ReadFile(archivePath) if err != nil { @@ -436,11 +402,11 @@ func TestDownloadDispatcherRealCLIWritesDownloadStatus(t *testing.T) { Transport: dispatcherRoundTripFunc(func(request *http.Request) (*http.Response, error) { content := []byte{} statusCode := http.StatusNotFound - if strings.HasSuffix(request.URL.Path, "/uloop-cli-darwin-arm64.tar.gz") { + if strings.HasSuffix(request.URL.Path, "/uloop-project-runner-darwin-arm64.tar.gz") { content = archiveContent statusCode = http.StatusOK } - if strings.HasSuffix(request.URL.Path, "/uloop-cli-darwin-arm64.tar.gz.sha256") { + if strings.HasSuffix(request.URL.Path, "/uloop-project-runner-darwin-arm64.tar.gz.sha256") { content = checksumContent statusCode = http.StatusOK } @@ -463,7 +429,7 @@ func TestDownloadDispatcherRealCLIWritesDownloadStatus(t *testing.T) { if err != nil { t.Fatalf("downloadDispatcherRealCLI failed: %v", err) } - expectedStatus := "uloop: downloading pinned CLI 3.0.0-beta.88 for darwin-arm64...\n" + expectedStatus := "uloop: downloading pinned project runner 3.0.0-beta.88 for darwin-arm64...\n" if stderr.String() != expectedStatus { t.Fatalf("download status mismatch: %q", stderr.String()) } @@ -505,8 +471,8 @@ func TestLoadDispatcherPinFallsBackToPackagePin(t *testing.T) { if err != nil { t.Fatalf("loadDispatcherPin failed: %v", err) } - if pin.CLIVersion != "3.0.0-beta.55" { - t.Fatalf("cliVersion mismatch: %s", pin.CLIVersion) + if pin.ProjectRunnerVersion != "3.0.0-beta.55" { + t.Fatalf("projectRunnerVersion mismatch: %s", pin.ProjectRunnerVersion) } } @@ -527,8 +493,8 @@ func TestLoadDispatcherPinSkipsInvalidPackageCandidate(t *testing.T) { if err != nil { t.Fatalf("loadDispatcherPin failed: %v", err) } - if pin.CLIVersion != "3.0.0-beta.57" { - t.Fatalf("cliVersion mismatch: %s", pin.CLIVersion) + if pin.ProjectRunnerVersion != "3.0.0-beta.57" { + t.Fatalf("projectRunnerVersion mismatch: %s", pin.ProjectRunnerVersion) } } @@ -542,22 +508,22 @@ func TestLoadDispatcherPinNormalizesVersionPrefixes(t *testing.T) { if err != nil { t.Fatalf("loadDispatcherPin failed: %v", err) } - if pin.CLIVersion != "3.0.0-beta.58" { - t.Fatalf("cliVersion mismatch: %s", pin.CLIVersion) + if pin.ProjectRunnerVersion != "3.0.0-beta.58" { + t.Fatalf("projectRunnerVersion mismatch: %s", pin.ProjectRunnerVersion) } if pin.MinimumDispatcherVersion != "3.0.0-beta.39" { t.Fatalf("minimumDispatcherVersion mismatch: %s", pin.MinimumDispatcherVersion) } } -func TestLoadDispatcherPinRejectsInvalidCLIVersion(t *testing.T) { - // Verifies project pin cliVersion must be a release version, not a filesystem path. +func TestLoadDispatcherPinRejectsInvalidProjectRunnerVersion(t *testing.T) { + // Verifies project pin projectRunnerVersion must be a release version, not a filesystem path. projectRoot := createDispatcherUnityProject(t) pinPath := filepath.Join(projectRoot, dispatcherProjectPinRelativePath) if err := os.MkdirAll(filepath.Dir(pinPath), 0o755); err != nil { t.Fatalf("failed to create pin directory: %v", err) } - content := `{"schemaVersion":1,"packageName":"io.github.hatayama.uloopmcp","packageVersion":"3.0.0-beta.1","cliVersion":"../../payload","requiredProtocolVersion":2,"minimumDispatcherVersion":"3.0.0-beta.39"}` + content := `{"schemaVersion":1,"packageName":"io.github.hatayama.uloopmcp","packageVersion":"3.0.0-beta.1","projectRunnerVersion":"../../payload","requiredProtocolVersion":2,"minimumDispatcherVersion":"3.0.0-beta.39"}` if err := os.WriteFile(pinPath, []byte(content), 0o644); err != nil { t.Fatalf("failed to write pin: %v", err) } @@ -565,7 +531,7 @@ func TestLoadDispatcherPinRejectsInvalidCLIVersion(t *testing.T) { _, err := loadDispatcherPin(projectRoot) if err == nil { - t.Fatal("expected invalid cliVersion error") + t.Fatal("expected invalid projectRunnerVersion error") } } @@ -590,7 +556,7 @@ func TestLoadDispatcherPinFallsBackToCliConstants(t *testing.T) { t.Fatalf("failed to create constants directory: %v", err) } content := `public const int REQUIRED_CLI_PROTOCOL_VERSION = 3; -public const string MINIMUM_REQUIRED_CLI_VERSION = "3.0.0-beta.56"; +public const string MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION = "3.0.0-beta.56"; public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "1.0.0";` if err := os.WriteFile(constantsPath, []byte(content), 0o644); err != nil { t.Fatalf("failed to write constants: %v", err) @@ -600,8 +566,8 @@ public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "1.0.0";` if err != nil { t.Fatalf("loadDispatcherPin failed: %v", err) } - if pin.CLIVersion != "3.0.0-beta.56" { - t.Fatalf("cliVersion mismatch: %s", pin.CLIVersion) + if pin.ProjectRunnerVersion != "3.0.0-beta.56" { + t.Fatalf("projectRunnerVersion mismatch: %s", pin.ProjectRunnerVersion) } if pin.RequiredProtocolVersion != 3 { t.Fatalf("protocol mismatch: %d", pin.RequiredProtocolVersion) @@ -619,7 +585,7 @@ func TestLoadDispatcherPinFromCliConstantsNormalizesVersionPrefix(t *testing.T) t.Fatalf("failed to create constants directory: %v", err) } content := `public const int REQUIRED_CLI_PROTOCOL_VERSION = 3; -public const string MINIMUM_REQUIRED_CLI_VERSION = "v3.0.0-beta.59"; +public const string MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION = "v3.0.0-beta.59"; public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "v1.0.0";` if err := os.WriteFile(constantsPath, []byte(content), 0o644); err != nil { t.Fatalf("failed to write constants: %v", err) @@ -629,8 +595,8 @@ public const string MINIMUM_REQUIRED_DISPATCHER_VERSION = "v1.0.0";` if err != nil { t.Fatalf("loadDispatcherPin failed: %v", err) } - if pin.CLIVersion != "3.0.0-beta.59" { - t.Fatalf("cliVersion mismatch: %s", pin.CLIVersion) + if pin.ProjectRunnerVersion != "3.0.0-beta.59" { + t.Fatalf("projectRunnerVersion mismatch: %s", pin.ProjectRunnerVersion) } if pin.MinimumDispatcherVersion != "1.0.0" { t.Fatalf("minimumDispatcherVersion mismatch: %s", pin.MinimumDispatcherVersion) @@ -657,24 +623,24 @@ func createDispatcherUnityProject(t *testing.T) string { return projectRoot } -func writeDispatcherProjectPin(t *testing.T, projectRoot string, cliVersion string) { +func writeDispatcherProjectPin(t *testing.T, projectRoot string, projectRunnerVersion string) { t.Helper() pinPath := filepath.Join(projectRoot, dispatcherProjectPinRelativePath) - writeDispatcherPinFile(t, pinPath, cliVersion) + writeDispatcherPinFile(t, pinPath, projectRunnerVersion) } -func writeDispatcherPinFile(t *testing.T, pinPath string, cliVersion string) { +func writeDispatcherPinFile(t *testing.T, pinPath string, projectRunnerVersion string) { t.Helper() - writeDispatcherPinFileWithMinimum(t, pinPath, cliVersion, dispatcherVersion) + writeDispatcherPinFileWithMinimum(t, pinPath, projectRunnerVersion, dispatcherVersion) } -func writeDispatcherPinFileWithMinimum(t *testing.T, pinPath string, cliVersion string, minimumDispatcherVersion string) { +func writeDispatcherPinFileWithMinimum(t *testing.T, pinPath string, projectRunnerVersion string, minimumDispatcherVersion string) { t.Helper() if err := os.MkdirAll(filepath.Dir(pinPath), 0o755); err != nil { t.Fatalf("failed to create pin directory: %v", err) } - content := `{"schemaVersion":1,"packageName":"io.github.hatayama.uloopmcp","packageVersion":"3.0.0-beta.1","cliVersion":"` + - cliVersion + + content := `{"schemaVersion":1,"packageName":"io.github.hatayama.uloopmcp","packageVersion":"3.0.0-beta.1","projectRunnerVersion":"` + + projectRunnerVersion + `","requiredProtocolVersion":2,"minimumDispatcherVersion":"` + minimumDispatcherVersion + `"}` @@ -683,9 +649,9 @@ func writeDispatcherPinFileWithMinimum(t *testing.T, pinPath string, cliVersion } } -func writeCachedDispatcherRealCLI(t *testing.T, cacheRoot string, cliVersion string) string { +func writeCachedDispatcherRealCLI(t *testing.T, cacheRoot string, projectRunnerVersion string) string { t.Helper() - realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, cliVersion, runtime.GOOS, runtime.GOARCH) + realCLIPath := dispatcherCachedRealCLIPath(cacheRoot, projectRunnerVersion, runtime.GOOS, runtime.GOARCH) if err := os.MkdirAll(filepath.Dir(realCLIPath), 0o755); err != nil { t.Fatalf("failed to create cached CLI directory: %v", err) } diff --git a/cli/internal/cli/dispatcher_update_version.go b/cli/internal/cli/dispatcher_update_version.go index d899b277f..81ad7c53d 100644 --- a/cli/internal/cli/dispatcher_update_version.go +++ b/cli/internal/cli/dispatcher_update_version.go @@ -36,7 +36,7 @@ func readInstalledDispatcherVersion(ctx context.Context) (string, error) { if updatedVersion == "" { return "", errors.New("updated dispatcher version is empty") } - if err := validateDispatcherCLIVersion(updatedVersion); err != nil { + if err := validateDispatcherProjectRunnerVersion(updatedVersion); err != nil { return "", err } return updatedVersion, nil diff --git a/cli/internal/cli/help_test.go b/cli/internal/cli/help_test.go index eb3ef7d6c..b81f2d7a6 100644 --- a/cli/internal/cli/help_test.go +++ b/cli/internal/cli/help_test.go @@ -62,8 +62,8 @@ func TestRunProjectLocalVersionJSONIncludesProtocolVersion(t *testing.T) { if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil { t.Fatalf("version json output is not JSON: %v\n%s", err, stdout.String()) } - if payload["CliVersion"] != version { - t.Fatalf("cliVersion mismatch: %#v", payload) + if payload["ProjectRunnerVersion"] != version { + t.Fatalf("projectRunnerVersion mismatch: %#v", payload) } if payload["ProtocolVersion"] != float64(protocolVersion) { t.Fatalf("protocolVersion mismatch: %#v", payload) diff --git a/cli/internal/cli/run.go b/cli/internal/cli/run.go index d800f8685..3b4cedcf0 100644 --- a/cli/internal/cli/run.go +++ b/cli/internal/cli/run.go @@ -56,8 +56,8 @@ func RunProjectLocal(ctx context.Context, args []string, stdout io.Writer, stder func writeVersionJSON(stdout io.Writer) { content, err := json.Marshal(map[string]any{ - "CliVersion": version, - "ProtocolVersion": protocolVersion, + "ProjectRunnerVersion": version, + "ProtocolVersion": protocolVersion, }) if err != nil { panic(err) diff --git a/cli/internal/cli/tools.go b/cli/internal/cli/tools.go index 53bc59a50..70ac05e6e 100644 --- a/cli/internal/cli/tools.go +++ b/cli/internal/cli/tools.go @@ -11,7 +11,7 @@ import ( ) var ( - version = clicontract.Current.CliVersion + version = clicontract.Current.ProjectRunnerVersion protocolVersion = clicontract.Current.ProtocolVersion dispatcherVersion = clicontract.DispatcherCurrent.DispatcherVersion dispatcherContractVersion = clicontract.DispatcherCurrent.DispatcherContractVersion diff --git a/cli/internal/cli/update_test.go b/cli/internal/cli/update_test.go index bbacb7414..a6ac48177 100644 --- a/cli/internal/cli/update_test.go +++ b/cli/internal/cli/update_test.go @@ -105,10 +105,10 @@ func TestUpdateCommandForDarwinNormalizesRequestedVersionPrefix(t *testing.T) { } } -func TestUpdateCommandForDarwinNormalizesLegacyCliReleaseTag(t *testing.T) { - // Verifies migrated CLI release tags resolve to the matching dispatcher release. +func TestUpdateCommandForDarwinNormalizesProjectRunnerReleaseTag(t *testing.T) { + // Verifies project runner release tags resolve to the matching dispatcher release. commandName, args, err := updateCommandForOSWithOptions("darwin", updateOptions{ - targetVersion: "cli-v3.0.0-beta.6", + targetVersion: "uloop-project-runner-v3.0.0-beta.6", }) if err != nil { t.Fatalf("updateCommandForOSWithOptions failed: %v", err) @@ -121,8 +121,8 @@ func TestUpdateCommandForDarwinNormalizesLegacyCliReleaseTag(t *testing.T) { if !strings.Contains(joinedArgs, "dispatcher-v3.0.0-beta.6/scripts/install.sh") { t.Fatalf("installer URL mismatch: %s", joinedArgs) } - if strings.Contains(joinedArgs, "dispatcher-vcli-v3.0.0-beta.6") { - t.Fatalf("installer version contains legacy CLI prefix: %s", joinedArgs) + if strings.Contains(joinedArgs, "dispatcher-vuloop-project-runner-v3.0.0-beta.6") { + t.Fatalf("installer version contains project runner prefix: %s", joinedArgs) } } @@ -159,9 +159,9 @@ func TestParseUpdateOptionsNormalizesVersionPrefix(t *testing.T) { } } -func TestParseUpdateOptionsNormalizesLegacyCliReleaseTag(t *testing.T) { - // Verifies parsed legacy CLI release tags are normalized before dispatcher tag selection. - options, err := parseUpdateOptions([]string{"--to-version", "cli-v3.0.0-beta.6"}) +func TestParseUpdateOptionsNormalizesProjectRunnerReleaseTag(t *testing.T) { + // Verifies parsed project runner release tags are normalized before dispatcher tag selection. + options, err := parseUpdateOptions([]string{"--to-version", "uloop-project-runner-v3.0.0-beta.6"}) if err != nil { t.Fatalf("parseUpdateOptions failed: %v", err) } diff --git a/cli/internal/projectcli/projectcli.go b/cli/internal/projectrunner/projectrunner.go similarity index 91% rename from cli/internal/projectcli/projectcli.go rename to cli/internal/projectrunner/projectrunner.go index 9b25a6338..8a47b72f2 100644 --- a/cli/internal/projectcli/projectcli.go +++ b/cli/internal/projectrunner/projectrunner.go @@ -1,4 +1,4 @@ -package projectcli +package projectrunner import ( "context" diff --git a/cli/internal/unityipc/client.go b/cli/internal/unityipc/client.go index 1985e993e..d17c51ef1 100644 --- a/cli/internal/unityipc/client.go +++ b/cli/internal/unityipc/client.go @@ -66,10 +66,10 @@ type rpcRequest struct { } type rpcClientMetadata struct { - CLIVersion string `json:"cliVersion"` - ProtocolVersion int `json:"protocolVersion"` - AcceptsDispatchAck bool `json:"acceptsDispatchAck"` - AcceptsHeartbeat bool `json:"acceptsHeartbeat"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` + ProtocolVersion int `json:"protocolVersion"` + AcceptsDispatchAck bool `json:"acceptsDispatchAck"` + AcceptsHeartbeat bool `json:"acceptsHeartbeat"` } type rpcResponse struct { @@ -189,10 +189,10 @@ func (client *Client) SendWithProgressOutcomeAcceptContext( Method: method, Params: params, ULoop: rpcClientMetadata{ - CLIVersion: client.clientVersion, - ProtocolVersion: clicontract.Current.ProtocolVersion, - AcceptsDispatchAck: true, - AcceptsHeartbeat: true, + ProjectRunnerVersion: client.clientVersion, + ProtocolVersion: clicontract.Current.ProtocolVersion, + AcceptsDispatchAck: true, + AcceptsHeartbeat: true, }, ID: client.requestID, } diff --git a/cli/internal/unityipc/client_test.go b/cli/internal/unityipc/client_test.go index e21de1349..7953b2109 100644 --- a/cli/internal/unityipc/client_test.go +++ b/cli/internal/unityipc/client_test.go @@ -40,8 +40,8 @@ func TestFormatConnectionAttemptErrorExplainsDialFailureWithoutDisconnectClaim(t } } -func TestSendIncludesCliVersionWithoutProjectIdentityMetadata(t *testing.T) { - // Verifies that requests carry CLI compatibility metadata without reviving legacy project identity metadata. +func TestSendIncludesProjectRunnerVersionWithoutProjectIdentityMetadata(t *testing.T) { + // Verifies that requests carry project runner compatibility metadata without reviving legacy project identity metadata. if runtime.GOOS == "windows" { t.Skip("TCP endpoint injection is only used by this non-Windows client test") } @@ -122,8 +122,8 @@ func assertClientMetadataRequest(t *testing.T, request map[string]any) { if !ok { t.Fatalf("request should include uloop metadata: %#v", request) } - if metadata["cliVersion"] != "3.0.0-beta.6" { - t.Fatalf("cli version metadata mismatch: %#v", metadata) + if metadata["projectRunnerVersion"] != "3.0.0-beta.6" { + t.Fatalf("project runner version metadata mismatch: %#v", metadata) } if metadata["protocolVersion"] != float64(clicontract.Current.ProtocolVersion) { t.Fatalf("protocol version metadata mismatch: %#v", metadata) diff --git a/cli/internal/update/command.go b/cli/internal/update/command.go index 83ab045b6..3be110930 100644 --- a/cli/internal/update/command.go +++ b/cli/internal/update/command.go @@ -55,8 +55,8 @@ func NormalizeTargetVersion(value string) string { if strings.HasPrefix(lower, dispatcherTagPrefix) { return trimmed[len(dispatcherTagPrefix):] } - if strings.HasPrefix(lower, cliReleaseTagPrefix) { - return trimmed[len(cliReleaseTagPrefix):] + if strings.HasPrefix(lower, projectRunnerReleaseTagPrefix) { + return trimmed[len(projectRunnerReleaseTagPrefix):] } if strings.HasPrefix(lower, "v") { return trimmed[1:] diff --git a/cli/internal/update/installer.go b/cli/internal/update/installer.go index d86acd477..212e6f9ad 100644 --- a/cli/internal/update/installer.go +++ b/cli/internal/update/installer.go @@ -8,22 +8,22 @@ const ( LatestStable = "latest" LatestBeta = "latest-beta" - repositoryRawBaseURL = "https://raw.githubusercontent.com/hatayama/unity-cli-loop" - cliReleaseTagPrefix = "cli-v" - dispatcherTagPrefix = "dispatcher-v" - betaVersionMarker = "-beta." + repositoryRawBaseURL = "https://raw.githubusercontent.com/hatayama/unity-cli-loop" + projectRunnerReleaseTagPrefix = "uloop-project-runner-v" + dispatcherTagPrefix = "dispatcher-v" + betaVersionMarker = "-beta." ) func ScriptURL(version string, scriptName string) string { return repositoryRawBaseURL + "/" + DispatcherReleaseTag(version) + "/scripts/" + scriptName } -func CLIReleaseTag(version string) string { - if strings.HasPrefix(version, cliReleaseTagPrefix) || strings.HasPrefix(version, strings.ToUpper(cliReleaseTagPrefix)) { +func ProjectRunnerReleaseTag(version string) string { + if strings.HasPrefix(version, projectRunnerReleaseTagPrefix) || strings.HasPrefix(version, strings.ToUpper(projectRunnerReleaseTagPrefix)) { return version } - return cliReleaseTagPrefix + version + return projectRunnerReleaseTagPrefix + version } func DispatcherReleaseTag(version string) string { diff --git a/cli/internal/update/installer_test.go b/cli/internal/update/installer_test.go index c5f0ddd29..f9219130c 100644 --- a/cli/internal/update/installer_test.go +++ b/cli/internal/update/installer_test.go @@ -22,20 +22,20 @@ func TestScriptURLForStableVersionUsesReleaseInstaller(t *testing.T) { } } -func TestCLIReleaseTagAddsMissingPrefix(t *testing.T) { - // Verifies CLI downloads use the GitHub release tag format. - tag := CLIReleaseTag("3.0.0-beta.3") +func TestProjectRunnerReleaseTagAddsMissingPrefix(t *testing.T) { + // Verifies project runner downloads use the GitHub release tag format. + tag := ProjectRunnerReleaseTag("3.0.0-beta.3") - if tag != "cli-v3.0.0-beta.3" { + if tag != "uloop-project-runner-v3.0.0-beta.3" { t.Fatalf("release tag mismatch: %s", tag) } } -func TestCLIReleaseTagKeepsCliPrefix(t *testing.T) { - // Verifies exact CLI release tags are not rewritten. - tag := CLIReleaseTag("cli-v3.0.0-beta.3") +func TestProjectRunnerReleaseTagKeepsProjectRunnerPrefix(t *testing.T) { + // Verifies exact project runner release tags are not rewritten. + tag := ProjectRunnerReleaseTag("uloop-project-runner-v3.0.0-beta.3") - if tag != "cli-v3.0.0-beta.3" { + if tag != "uloop-project-runner-v3.0.0-beta.3" { t.Fatalf("release tag mismatch: %s", tag) } } diff --git a/cli/protocol_version_consistency_test.go b/cli/protocol_version_consistency_test.go index 807be3998..b27672bb7 100644 --- a/cli/protocol_version_consistency_test.go +++ b/cli/protocol_version_consistency_test.go @@ -14,8 +14,8 @@ import ( const ( unityProtocolConstantPath = "../Packages/src/Editor/Domain/CliConstants.cs" unityPackageManifestPath = "../Packages/src/package.json" - unityPackageCliPinPath = "../Packages/src/cli-pin.json" - unityProjectCliPinPath = "../.uloop/cli-pin.json" + unityPackageCliPinPath = "../Packages/src/project-runner-pin.json" + unityProjectCliPinPath = "../.uloop/project-runner-pin.json" ) var ( @@ -32,7 +32,7 @@ type unityPackageCliPin struct { SchemaVersion int `json:"schemaVersion"` PackageName string `json:"packageName"` PackageVersion string `json:"packageVersion"` - CLIVersion string `json:"cliVersion"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` RequiredProtocolVersion int `json:"requiredProtocolVersion"` MinimumDispatcherVersion string `json:"minimumDispatcherVersion"` } @@ -54,7 +54,7 @@ func TestProtocolVersionMatchesUnityPackage(t *testing.T) { } // TestUnityPackageCliPinMatchesReleaseContracts verifies the dispatcher pin copied into -// projects points at the package release, CLI release, and protocol generation from their +// projects points at the package release, project runner release, and protocol generation from their // canonical declarations. func TestUnityPackageCliPinMatchesReleaseContracts(t *testing.T) { manifest := readJSONFile[unityPackageManifest](t, unityPackageManifestPath) @@ -69,8 +69,8 @@ func TestUnityPackageCliPinMatchesReleaseContracts(t *testing.T) { if pin.PackageVersion != manifest.Version { t.Fatalf("expected %s packageVersion to match %s version: %q != %q", unityPackageCliPinPath, unityPackageManifestPath, pin.PackageVersion, manifest.Version) } - if pin.CLIVersion != Current.CliVersion { - t.Fatalf("expected %s cliVersion to match cli/contract.json cliVersion: %q != %q", unityPackageCliPinPath, pin.CLIVersion, Current.CliVersion) + if pin.ProjectRunnerVersion != Current.ProjectRunnerVersion { + t.Fatalf("expected %s projectRunnerVersion to match cli/contract.json projectRunnerVersion: %q != %q", unityPackageCliPinPath, pin.ProjectRunnerVersion, Current.ProjectRunnerVersion) } if pin.RequiredProtocolVersion != Current.ProtocolVersion { t.Fatalf("expected %s requiredProtocolVersion to match cli/contract.json protocolVersion: %d != %d", unityPackageCliPinPath, pin.RequiredProtocolVersion, Current.ProtocolVersion) diff --git a/release-please-config.json b/release-please-config.json index dad325ad5..c68ada0ec 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -19,18 +19,18 @@ }, { "type": "json", - "path": "Packages/src/cli-pin.json", + "path": "Packages/src/project-runner-pin.json", "jsonpath": "$.packageVersion" }, { "type": "json", - "path": ".uloop/cli-pin.json", + "path": ".uloop/project-runner-pin.json", "jsonpath": "$.packageVersion" } ] }, "cli": { - "component": "cli", + "component": "uloop-project-runner", "release-type": "go", "versioning": "prerelease", "prerelease": true, @@ -47,17 +47,17 @@ { "type": "json", "path": "contract.json", - "jsonpath": "$.cliVersion" + "jsonpath": "$.projectRunnerVersion" }, { "type": "json", - "path": "/Packages/src/cli-pin.json", - "jsonpath": "$.cliVersion" + "path": "/Packages/src/project-runner-pin.json", + "jsonpath": "$.projectRunnerVersion" }, { "type": "json", - "path": "/.uloop/cli-pin.json", - "jsonpath": "$.cliVersion" + "path": "/.uloop/project-runner-pin.json", + "jsonpath": "$.projectRunnerVersion" } ] } diff --git a/scripts/build-go-cli.sh b/scripts/build-go-cli.sh index 21248f4c3..b4530e929 100755 --- a/scripts/build-go-cli.sh +++ b/scripts/build-go-cli.sh @@ -28,9 +28,9 @@ build_binary() { ) } -build_binary darwin arm64 uloop "$CLI_DIR" ./cmd/uloop -build_binary darwin arm64 uloop-cli "$CLI_DIR" ./cmd/uloop-cli -build_binary darwin amd64 uloop "$CLI_DIR" ./cmd/uloop -build_binary darwin amd64 uloop-cli "$CLI_DIR" ./cmd/uloop-cli -build_binary windows amd64 uloop "$CLI_DIR" ./cmd/uloop -build_binary windows amd64 uloop-cli "$CLI_DIR" ./cmd/uloop-cli +build_binary darwin arm64 uloop "$CLI_DIR" ./cmd/dispatcher +build_binary darwin arm64 uloop-project-runner "$CLI_DIR" ./cmd/project-runner +build_binary darwin amd64 uloop "$CLI_DIR" ./cmd/dispatcher +build_binary darwin amd64 uloop-project-runner "$CLI_DIR" ./cmd/project-runner +build_binary windows amd64 uloop "$CLI_DIR" ./cmd/dispatcher +build_binary windows amd64 uloop-project-runner "$CLI_DIR" ./cmd/project-runner diff --git a/scripts/package-go-cli.sh b/scripts/package-go-cli.sh index 12f8e8a09..d9af1d411 100755 --- a/scripts/package-go-cli.sh +++ b/scripts/package-go-cli.sh @@ -13,27 +13,11 @@ package_unix() { platform="$1" tmp_dir="$RELEASE_DIR/tmp-$platform" mkdir -p "$tmp_dir" - cp "$DIST_DIR/$platform/uloop-cli" "$tmp_dir/uloop-cli" - chmod +x "$tmp_dir/uloop-cli" + cp "$DIST_DIR/$platform/uloop-project-runner" "$tmp_dir/uloop-project-runner" + chmod +x "$tmp_dir/uloop-project-runner" ( cd "$tmp_dir" - tar -czf "$RELEASE_DIR/uloop-cli-$platform.tar.gz" uloop-cli - ) - rm -rf "$tmp_dir" -} - -# Legacy beta launchers still self-update from cli-v* releases and look for uloop-* assets. -package_legacy_dispatcher_unix() { - platform="$1" - tmp_dir="$RELEASE_DIR/tmp-legacy-$platform" - mkdir -p "$tmp_dir" - cp "$DIST_DIR/$platform/uloop" "$tmp_dir/uloop" - cp "$DIST_DIR/$platform/uloop-cli" "$tmp_dir/uloop-cli" - chmod +x "$tmp_dir/uloop" - chmod +x "$tmp_dir/uloop-cli" - ( - cd "$tmp_dir" - tar -czf "$RELEASE_DIR/uloop-$platform.tar.gz" uloop uloop-cli + tar -czf "$RELEASE_DIR/uloop-project-runner-$platform.tar.gz" uloop-project-runner ) rm -rf "$tmp_dir" } @@ -42,24 +26,10 @@ package_windows() { platform="windows-amd64" tmp_dir="$RELEASE_DIR/tmp-$platform" mkdir -p "$tmp_dir" - cp "$DIST_DIR/$platform/uloop-cli.exe" "$tmp_dir/uloop-cli.exe" - ( - cd "$tmp_dir" - zip -q "$RELEASE_DIR/uloop-cli-$platform.zip" uloop-cli.exe - ) - rm -rf "$tmp_dir" -} - -# Legacy beta launchers install uloop.exe globally, while their dispatcher cache reads uloop-cli.exe. -package_legacy_dispatcher_windows() { - platform="windows-amd64" - tmp_dir="$RELEASE_DIR/tmp-legacy-$platform" - mkdir -p "$tmp_dir" - cp "$DIST_DIR/$platform/uloop.exe" "$tmp_dir/uloop.exe" - cp "$DIST_DIR/$platform/uloop-cli.exe" "$tmp_dir/uloop-cli.exe" + cp "$DIST_DIR/$platform/uloop-project-runner.exe" "$tmp_dir/uloop-project-runner.exe" ( cd "$tmp_dir" - zip -q "$RELEASE_DIR/uloop-$platform.zip" uloop.exe uloop-cli.exe + zip -q "$RELEASE_DIR/uloop-project-runner-$platform.zip" uloop-project-runner.exe ) rm -rf "$tmp_dir" } @@ -83,9 +53,6 @@ create_checksum() { package_unix darwin-arm64 package_unix darwin-amd64 package_windows -package_legacy_dispatcher_unix darwin-arm64 -package_legacy_dispatcher_unix darwin-amd64 -package_legacy_dispatcher_windows for asset_path in "$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.zip; do create_checksum "$asset_path" diff --git a/scripts/resolve-native-cli-release-target.sh b/scripts/resolve-native-cli-release-target.sh index a97a1e755..98b3a326d 100755 --- a/scripts/resolve-native-cli-release-target.sh +++ b/scripts/resolve-native-cli-release-target.sh @@ -2,6 +2,7 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) +LEGACY_CLI_RELEASE_TAG_PREFIX="cli-v" : "${EVENT_NAME:?EVENT_NAME is required}" @@ -25,6 +26,11 @@ scripts/package-go-cli.sh scripts/verify-native-cli-release-assets.sh " +is_semver_version() { + version=$1 + printf '%s\n' "$version" | grep -Eq '^(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-(0|[1-9][0-9]*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)([.](0|[1-9][0-9]*|[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))*)?([+][0-9A-Za-z-]+([.][0-9A-Za-z-]+)*)?$' +} + release_json() { release_tag=$1 release_error_file=$(mktemp) @@ -247,13 +253,13 @@ release_commit_sha_for_version() { VERSION=$(jq -r '.["cli"]' .release-please-manifest.json) if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then - echo "Could not resolve CLI release version from .release-please-manifest.json." >&2 + echo "Could not resolve project runner release version from .release-please-manifest.json." >&2 exit 1 fi -RELEASE_TAG="${INPUT_RELEASE_TAG:-cli-v$VERSION}" +RELEASE_TAG="${INPUT_RELEASE_TAG:-uloop-project-runner-v$VERSION}" case "$RELEASE_TAG" in - cli-v[0-9]*) + uloop-project-runner-v*) ;; *) echo "Invalid release tag: $RELEASE_TAG" >&2 @@ -261,8 +267,14 @@ case "$RELEASE_TAG" in ;; esac +RELEASE_TAG_VERSION=${RELEASE_TAG#uloop-project-runner-v} +if ! is_semver_version "$RELEASE_TAG_VERSION"; then + echo "Invalid release tag: $RELEASE_TAG" >&2 + exit 1 +fi + case "$RELEASE_TAG" in - *[!A-Za-z0-9._-]*) + *[!A-Za-z0-9._+-]*) echo "Invalid release tag: $RELEASE_TAG" >&2 exit 1 ;; @@ -305,6 +317,13 @@ RELEASE_TARGET_SHA=$(release_commit_sha_for_version "$VERSION" "$BUILD_SHA") if [ -z "$RELEASE_TARGET_SHA" ]; then RELEASE_TARGET_SHA=$BUILD_SHA fi +LEGACY_CLI_RELEASE_TAG="$LEGACY_CLI_RELEASE_TAG_PREFIX$VERSION" +if [ "$CAN_EVALUATE_CLI_RELEASE" = "true" ] && + [ "$RELEASE_TARGET_SHA" != "$BUILD_SHA" ] && + release_is_published "$LEGACY_CLI_RELEASE_TAG"; then + echo "Project runner release tag namespace changed for $VERSION; targeting build commit $BUILD_SHA instead of legacy release commit $RELEASE_TARGET_SHA." >&2 + RELEASE_TARGET_SHA=$BUILD_SHA +fi if [ "$CAN_EVALUATE_CLI_RELEASE" != "true" ]; then SHOULD_PUBLISH=false @@ -322,16 +341,16 @@ elif release_is_published_with_cli_assets "$RELEASE_TAG"; then else PREVIOUS_CLI_RELEASE_TAG=$(latest_cli_asset_release_tag "$RELEASE_TAG") if [ -z "$PREVIOUS_CLI_RELEASE_TAG" ]; then - echo "No previous CLI asset release found; publishing native CLI assets." >&2 + echo "No previous project runner asset release found; publishing native project runner assets." >&2 SHOULD_PUBLISH=true elif release_commit_updates_cli_version "$RELEASE_TARGET_SHA" "$VERSION"; then - echo "CLI release metadata changed in $RELEASE_TARGET_SHA; publishing native CLI assets." >&2 + echo "Project runner release metadata changed in $RELEASE_TARGET_SHA; publishing native project runner assets." >&2 SHOULD_PUBLISH=true elif cli_release_inputs_changed "$PREVIOUS_CLI_RELEASE_TAG" "$TARGET_SHA"; then - echo "CLI release inputs changed since $PREVIOUS_CLI_RELEASE_TAG; publishing native CLI assets." >&2 + echo "Project runner release inputs changed since $PREVIOUS_CLI_RELEASE_TAG; publishing native project runner assets." >&2 SHOULD_PUBLISH=true else - echo "CLI release inputs are unchanged since $PREVIOUS_CLI_RELEASE_TAG; skipping native CLI publish." >&2 + echo "Project runner release inputs are unchanged since $PREVIOUS_CLI_RELEASE_TAG; skipping native CLI publish." >&2 fi fi diff --git a/scripts/sync-release-please-package-releases.sh b/scripts/sync-release-please-package-releases.sh index a0635f76f..6692d2d90 100755 --- a/scripts/sync-release-please-package-releases.sh +++ b/scripts/sync-release-please-package-releases.sh @@ -5,7 +5,7 @@ ROOT_DIR=${ULOOP_REPO_ROOT:-$(CDPATH= cd "$(dirname "$0")/.." && pwd)} CONFIG="$ROOT_DIR/release-please-config.json" MANIFEST="$ROOT_DIR/.release-please-manifest.json" CLI_PACKAGE_PATH="cli" -UNITY_PACKAGE_CLI_PIN_FILE="Packages/src/cli-pin.json" +UNITY_PACKAGE_CLI_PIN_FILE="Packages/src/project-runner-pin.json" REPO_FULL_NAME=${GITHUB_REPOSITORY:-hatayama/unity-cli-loop} TMP_DIR=$(mktemp -d) @@ -368,7 +368,7 @@ wait_for_cli_release_ready() { while :; do if cli_release_is_ready "$release_tag"; then if [ "$elapsed_seconds" -gt 0 ]; then - echo "CLI release $release_tag is now published with complete assets." + echo "Project runner release $release_tag is now published with complete assets." fi return 0 fi @@ -389,7 +389,7 @@ wait_for_cli_release_ready() { sleep_seconds=$delay_seconds fi - echo "CLI release $release_tag is not published with complete assets yet; waiting ${delay_seconds}s before retry." + echo "Project runner release $release_tag is not published with complete assets yet; waiting ${delay_seconds}s before retry." if [ "$sleep_seconds" -gt 0 ]; then sleep "$sleep_seconds" fi @@ -476,7 +476,7 @@ if [ -n "$cli_version" ] && jq -e --arg package_path "$CLI_PACKAGE_PATH" '.packa cli_release_tag=$(release_tag_from_config "$CLI_PACKAGE_PATH" "$cli_version") if ! wait_for_cli_release_ready "$cli_release_tag"; then mark_package_release_sync_ready false - echo "CLI release $cli_release_tag is not published with complete assets; package release sync will wait." + echo "Project runner release $cli_release_tag is not published with complete assets; package release sync will wait." exit 0 fi fetch_cli_release_tag "$cli_release_tag" diff --git a/scripts/test-release-please-config.sh b/scripts/test-release-please-config.sh index 5cfd6548f..4b52b5fba 100755 --- a/scripts/test-release-please-config.sh +++ b/scripts/test-release-please-config.sh @@ -120,21 +120,21 @@ assert_json_value '.packages["."].["exclude-paths"][0]' 'cli' assert_json_value '.packages["."].["extra-files"] | length' '3' assert_json_value '.packages["."].["extra-files"][0].path' 'Packages/src/package.json' assert_json_value '.packages["."].["extra-files"][0].jsonpath' '$.version' -assert_json_value '.packages["."].["extra-files"][1].path' 'Packages/src/cli-pin.json' +assert_json_value '.packages["."].["extra-files"][1].path' 'Packages/src/project-runner-pin.json' assert_json_value '.packages["."].["extra-files"][1].jsonpath' '$.packageVersion' -assert_json_value '.packages["."].["extra-files"][2].path' '.uloop/cli-pin.json' +assert_json_value '.packages["."].["extra-files"][2].path' '.uloop/project-runner-pin.json' assert_json_value '.packages["."].["extra-files"][2].jsonpath' '$.packageVersion' -assert_json_value '.packages["cli"].component' 'cli' +assert_json_value '.packages["cli"].component' 'uloop-project-runner' assert_json_value '.packages["cli"].["include-component-in-tag"]' 'true' assert_json_value '.packages["cli"].["changelog-path"]' 'CHANGELOG.md' assert_json_value '.packages["cli"].["extra-files"] | length' '4' assert_json_value '.packages["cli"].["extra-files"][0].path' 'internal/tools/default-tools.json' assert_json_value '.packages["cli"].["extra-files"][1].path' 'contract.json' -assert_json_value '.packages["cli"].["extra-files"][2].path' '/Packages/src/cli-pin.json' -assert_json_value '.packages["cli"].["extra-files"][2].jsonpath' '$.cliVersion' -assert_json_value '.packages["cli"].["extra-files"][3].path' '/.uloop/cli-pin.json' -assert_json_value '.packages["cli"].["extra-files"][3].jsonpath' '$.cliVersion' +assert_json_value '.packages["cli"].["extra-files"][2].path' '/Packages/src/project-runner-pin.json' +assert_json_value '.packages["cli"].["extra-files"][2].jsonpath' '$.projectRunnerVersion' +assert_json_value '.packages["cli"].["extra-files"][3].path' '/.uloop/project-runner-pin.json' +assert_json_value '.packages["cli"].["extra-files"][3].jsonpath' '$.projectRunnerVersion' assert_file_contains "$RELEASE_WORKFLOW" 'id: package_release_sync' assert_file_contains "$RELEASE_WORKFLOW" "steps.package_release_sync.outputs.ready != 'false'" diff --git a/scripts/test-resolve-native-cli-release-target.sh b/scripts/test-resolve-native-cli-release-target.sh index bb56efba5..0526c23b1 100755 --- a/scripts/test-resolve-native-cli-release-target.sh +++ b/scripts/test-resolve-native-cli-release-target.sh @@ -72,7 +72,7 @@ set -eu asset_json() { has_assets=$1 if [ "$has_assets" = "true" ]; then - printf '[{"name":"uloop-cli-darwin-amd64.tar.gz","size":1},{"name":"uloop-cli-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-cli-windows-amd64.zip","size":1},{"name":"uloop-cli-windows-amd64.zip.sha256","size":1},{"name":"uloop-darwin-amd64.tar.gz","size":1},{"name":"uloop-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-darwin-arm64.tar.gz","size":1},{"name":"uloop-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-windows-amd64.zip","size":1},{"name":"uloop-windows-amd64.zip.sha256","size":1}]' + printf '[{"name":"uloop-project-runner-darwin-amd64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-windows-amd64.zip","size":1},{"name":"uloop-project-runner-windows-amd64.zip.sha256","size":1}]' return fi printf '[]' @@ -103,14 +103,19 @@ release_json() { if [ "$1" = "release" ] && [ "$2" = "view" ]; then tag=$3 - if [ "$tag" = "cli-v$CURRENT_VERSION" ]; then - release_json "$CURRENT_RELEASE_STATE" "$CURRENT_RELEASE_HAS_ASSETS" - exit 0 - fi - - if [ -n "$PREVIOUS_RELEASE_TAG" ] && [ "$tag" = "$PREVIOUS_RELEASE_TAG" ]; then - release_json published "$PREVIOUS_RELEASE_HAS_ASSETS" - exit 0 + if [ "$tag" = "uloop-project-runner-v$CURRENT_VERSION" ]; then + release_json "$CURRENT_RELEASE_STATE" "$CURRENT_RELEASE_HAS_ASSETS" + exit 0 + fi + + if [ "$tag" = "cli-v$CURRENT_VERSION" ]; then + release_json "$LEGACY_CURRENT_RELEASE_STATE" true + exit 0 + fi + + if [ -n "$PREVIOUS_RELEASE_TAG" ] && [ "$tag" = "$PREVIOUS_RELEASE_TAG" ]; then + release_json published "$PREVIOUS_RELEASE_HAS_ASSETS" + exit 0 fi exit 1 @@ -154,6 +159,18 @@ assert_contains() { fi } +assert_line_equals() { + file=$1 + expected=$2 + + if ! grep -Fx "$expected" "$file" >/dev/null; then + echo "Expected $file to contain line: $expected" >&2 + echo "Actual content:" >&2 + cat "$file" >&2 + exit 1 + fi +} + assert_script_contains() { expected=$1 @@ -181,12 +198,13 @@ run_success_case() { build_sha_value=${15:-target-sha} release_commit_sha=${16:-target-sha} release_commit_subject=${17:-} - build_commit_subject=${18:-} - build_commit_updates_cli=${19:-false} - release_commit_updates_cli=${20:-false} - if [ -z "$release_commit_subject" ]; then - release_commit_subject="chore(v3-beta): release $current_version" - fi + build_commit_subject=${18:-} + build_commit_updates_cli=${19:-false} + release_commit_updates_cli=${20:-false} + legacy_current_release_state=${21:-missing} + if [ -z "$release_commit_subject" ]; then + release_commit_subject="chore(v3-beta): release $current_version" + fi if [ -z "$build_commit_subject" ]; then build_commit_subject=$release_commit_subject fi @@ -206,11 +224,12 @@ run_success_case() { BUILD_SHA_VALUE="$build_sha_value" \ BUILD_COMMIT_SUBJECT="$build_commit_subject" \ BUILD_COMMIT_UPDATES_CLI="$build_commit_updates_cli" \ - RELEASE_COMMIT_SHA="$release_commit_sha" \ - RELEASE_COMMIT_SUBJECT="$release_commit_subject" \ - RELEASE_COMMIT_UPDATES_CLI="$release_commit_updates_cli" \ - PREVIOUS_RELEASE_TAG="$previous_release_tag" \ - PREVIOUS_RELEASE_HAS_ASSETS="$previous_release_has_assets" \ + RELEASE_COMMIT_SHA="$release_commit_sha" \ + RELEASE_COMMIT_SUBJECT="$release_commit_subject" \ + RELEASE_COMMIT_UPDATES_CLI="$release_commit_updates_cli" \ + LEGACY_CURRENT_RELEASE_STATE="$legacy_current_release_state" \ + PREVIOUS_RELEASE_TAG="$previous_release_tag" \ + PREVIOUS_RELEASE_HAS_ASSETS="$previous_release_has_assets" \ CLI_SOURCE_CHANGED="$cli_source_changed" \ CONTRACT_CHANGED="$contract_changed" \ CLI_REQUIREMENT_CHANGED="$cli_requirement_changed" \ @@ -221,13 +240,13 @@ run_success_case() { INPUT_DRY_RUN=false \ "$SCRIPT" > output.txt 2> stderr.txt - assert_contains output.txt "publish=$expected_publish" - assert_contains output.txt "release=$expected_release" - assert_contains output.txt "tag=cli-v$current_version" - assert_contains output.txt "version=$current_version" - assert_contains output.txt "sha=$expected_sha" - assert_contains output.txt "build_sha=$build_sha_value" - assert_contains output.txt "dry_run=false" + assert_line_equals output.txt "publish=$expected_publish" + assert_line_equals output.txt "release=$expected_release" + assert_line_equals output.txt "tag=uloop-project-runner-v$current_version" + assert_line_equals output.txt "version=$current_version" + assert_line_equals output.txt "sha=$expected_sha" + assert_line_equals output.txt "build_sha=$build_sha_value" + assert_line_equals output.txt "dry_run=false" ) } @@ -238,6 +257,7 @@ run_failure_case() { branch_name=$4 expected_error=$5 current_release_state=${6:-missing} + input_release_tag=${7:-} work_dir="$TMP_DIR/$name" mock_bin="$work_dir/bin" @@ -254,9 +274,10 @@ run_failure_case() { CURRENT_RELEASE_HAS_ASSETS=false \ BUILD_SHA_VALUE=target-sha \ BUILD_COMMIT_SUBJECT="chore(v3-beta): release $current_version" \ - RELEASE_COMMIT_SHA=target-sha \ - RELEASE_COMMIT_SUBJECT="chore(v3-beta): release $current_version" \ - PREVIOUS_RELEASE_TAG=cli-v3.0.0-beta.1 \ + RELEASE_COMMIT_SHA=target-sha \ + RELEASE_COMMIT_SUBJECT="chore(v3-beta): release $current_version" \ + LEGACY_CURRENT_RELEASE_STATE=missing \ + PREVIOUS_RELEASE_TAG=uloop-project-runner-v3.0.0-beta.1 \ PREVIOUS_RELEASE_HAS_ASSETS=true \ CLI_SOURCE_CHANGED=false \ CONTRACT_CHANGED=false \ @@ -264,7 +285,7 @@ run_failure_case() { EVENT_NAME="$event_name" \ EVENT_REF_NAME="$branch_name" \ BEFORE_SHA=before \ - INPUT_RELEASE_TAG= \ + INPUT_RELEASE_TAG="$input_release_tag" \ INPUT_DRY_RUN=false \ "$SCRIPT" > output.txt 2> stderr.txt status=$? @@ -279,79 +300,84 @@ run_failure_case() { ) } -# Verifies already published complete CLI assets are not rebuilt. +# Verifies already published complete Project runner assets are not rebuilt. test_complete_current_release_skips() { - run_success_case current-complete 3.0.0-beta.2 push v3-beta published true cli-v3.0.0-beta.1 true true false false false false + run_success_case current-complete 3.0.0-beta.2 push v3-beta published true uloop-project-runner-v3.0.0-beta.1 true true false false false false } -# Verifies package-only version changes do not publish CLI assets. +# Verifies package-only version changes do not publish Project runner assets. test_package_version_change_without_cli_change_skips() { - run_success_case package-only 3.0.0-beta.3 push v3-beta missing false cli-v3.0.0-beta.1 true false false false false true + run_success_case package-only 3.0.0-beta.3 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true false false false false true } # Verifies CLI source changes publish assets on the current release tag. test_cli_change_publishes() { - run_success_case cli-change 3.0.0-beta.3 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true + run_success_case cli-change 3.0.0-beta.3 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true } -# Verifies missing CLI assets can be uploaded to an already published package release. +# Verifies missing Project runner assets can be uploaded to an already published package release. test_published_current_release_can_receive_cli_assets() { - run_success_case published-missing-assets 3.0.0-beta.3 push v3-beta published false cli-v3.0.0-beta.1 true true false false true false + run_success_case published-missing-assets 3.0.0-beta.3 push v3-beta published false uloop-project-runner-v3.0.0-beta.1 true true false false true false } # Verifies non-version CLI contract changes publish assets. test_cli_contract_change_publishes() { - run_success_case contract-change 3.0.0-beta.3 push v3-beta missing false cli-v3.0.0-beta.1 true false true false true true + run_success_case contract-change 3.0.0-beta.3 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true false true false true true } # Verifies CLI requirement version bumps publish assets for that required tag. test_cli_requirement_change_publishes() { - run_success_case cli-requirement-change 3.0.0-beta.3 push v3-beta missing false cli-v3.0.0-beta.1 true false false true true true + run_success_case cli-requirement-change 3.0.0-beta.3 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true false false true true true } -# Verifies CLI release metadata-only release commits still publish native CLI assets. +# Verifies Project runner release metadata-only release commits still publish native Project runner assets. test_cli_release_metadata_change_publishes() { - run_success_case cli-release-metadata-change 3.0.0-beta.3 push v3-beta missing false cli-v3.0.0-beta.1 true false false false true true target-sha target-sha target-sha "chore: release v3-beta" "chore: release v3-beta" true false + run_success_case cli-release-metadata-change 3.0.0-beta.3 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true false false false true true target-sha target-sha target-sha "chore: release v3-beta" "chore: release v3-beta" true false } -# Verifies the first CLI asset release is published when no previous asset tag exists. +# Verifies the first renamed project runner tag targets the commit that contains the rename. +test_renamed_tag_with_existing_legacy_release_targets_build_commit() { + run_success_case renamed-tag-targets-build 3.0.0-beta.43 push v3-beta missing false "" false true false false true true build-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.43" "refactor: rename project runner" false true published +} + +# Verifies the first Project runner asset release is published when no previous asset tag exists. test_missing_previous_cli_release_publishes() { run_success_case bootstrap 3.0.0-beta.0 push v3-beta missing false "" false false false false true true } # Verifies a recovered release still tags the original release PR merge commit. test_recovery_targets_release_commit() { - run_success_case recovery-target 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: follow-up change" + run_success_case recovery-target 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: follow-up change" } # Verifies grouped manifest release commits remain the recovery target. test_recovery_targets_grouped_release_commit() { - run_success_case recovery-grouped-target 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore: release v3-beta" "fix: follow-up change" false true + run_success_case recovery-grouped-target 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore: release v3-beta" "fix: follow-up change" false true } -# Verifies package-only grouped release commits do not steal the CLI release target. +# Verifies package-only grouped release commits do not steal the Project runner release target. test_recovery_ignores_grouped_package_only_release_commit() { - run_success_case recovery-grouped-package-only-target 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore: release v3-beta" false false + run_success_case recovery-grouped-package-only-target 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore: release v3-beta" false false } # Verifies recovery ignores follow-up commits that only mention the release version. test_recovery_ignores_non_release_subject_mentions() { - run_success_case recovery-non-release-subject 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: keep release 3.0.0-beta.2 on the release commit" + run_success_case recovery-non-release-subject 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: keep release 3.0.0-beta.2 on the release commit" } # Verifies recovery ignores metadata-touching commits unless their subject is a release commit. test_recovery_ignores_non_release_metadata_commit() { - run_success_case recovery-non-release-metadata 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: repair release metadata" true false + run_success_case recovery-non-release-metadata 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "fix: repair release metadata" true false } # Verifies recovery ignores non-release subjects with a later release marker. test_recovery_requires_release_marker_after_scope() { - run_success_case recovery-scoped-marker 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore(v3-beta) follow-up): release 3.0.0-beta.2" + run_success_case recovery-scoped-marker 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore(v3-beta) follow-up): release 3.0.0-beta.2" } # Verifies version matching does not confuse beta.2 with beta.20. test_recovery_target_uses_exact_version_boundary() { - run_success_case recovery-boundary 3.0.0-beta.2 push v3-beta missing false cli-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore(v3-beta): release 3.0.0-beta.20" + run_success_case recovery-boundary 3.0.0-beta.2 push v3-beta missing false uloop-project-runner-v3.0.0-beta.1 true true false false true true release-sha build-sha release-sha "chore(v3-beta): release 3.0.0-beta.2" "chore(v3-beta): release 3.0.0-beta.20" } # Verifies main refuses prerelease versions. @@ -369,6 +395,21 @@ test_release_lookup_error_fails() { run_failure_case release-lookup-error 3.0.0-beta.3 push v3-beta "gh auth failed" error } +# Verifies explicit project runner tags must use a full SemVer suffix. +test_invalid_release_tag_version_fails() { + run_failure_case invalid-release-tag-version 3.0.0-beta.3 push v3-beta "Invalid release tag: uloop-project-runner-v3-beta" missing uloop-project-runner-v3-beta +} + +# Verifies numeric prerelease identifiers cannot have leading zeroes. +test_invalid_release_tag_numeric_prerelease_fails() { + run_failure_case invalid-release-tag-numeric-prerelease 3.0.0-beta.3 push v3-beta "Invalid release tag: uloop-project-runner-v3.0.0-01" missing uloop-project-runner-v3.0.0-01 +} + +# Verifies prerelease identifiers cannot be empty. +test_invalid_release_tag_empty_prerelease_identifier_fails() { + run_failure_case invalid-release-tag-empty-prerelease 3.0.0-beta.3 push v3-beta "Invalid release tag: uloop-project-runner-v3.0.0-alpha..1" missing uloop-project-runner-v3.0.0-alpha..1 +} + assert_script_contains "cli/contract.json" test_complete_current_release_skips test_package_version_change_without_cli_change_skips @@ -377,6 +418,7 @@ test_published_current_release_can_receive_cli_assets test_cli_contract_change_publishes test_cli_requirement_change_publishes test_cli_release_metadata_change_publishes +test_renamed_tag_with_existing_legacy_release_targets_build_commit test_missing_previous_cli_release_publishes test_recovery_targets_release_commit test_recovery_targets_grouped_release_commit @@ -388,3 +430,6 @@ test_recovery_target_uses_exact_version_boundary test_main_prerelease_fails test_v3_beta_stable_fails test_release_lookup_error_fails +test_invalid_release_tag_version_fails +test_invalid_release_tag_numeric_prerelease_fails +test_invalid_release_tag_empty_prerelease_identifier_fails diff --git a/scripts/test-sync-release-please-package-releases.sh b/scripts/test-sync-release-please-package-releases.sh index b9a3ffe0c..967d32c07 100755 --- a/scripts/test-sync-release-please-package-releases.sh +++ b/scripts/test-sync-release-please-package-releases.sh @@ -26,13 +26,13 @@ printf '%s\n' "$*" >> "$GH_LOG" asset_json() { case "${CLI_RELEASE_ASSETS:-complete}" in complete) - printf '[{"name":"uloop-cli-darwin-amd64.tar.gz","size":1},{"name":"uloop-cli-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-cli-windows-amd64.zip","size":1},{"name":"uloop-cli-windows-amd64.zip.sha256","size":1},{"name":"uloop-darwin-amd64.tar.gz","size":1},{"name":"uloop-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-darwin-arm64.tar.gz","size":1},{"name":"uloop-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-windows-amd64.zip","size":1},{"name":"uloop-windows-amd64.zip.sha256","size":1}]' + printf '[{"name":"uloop-project-runner-darwin-amd64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-windows-amd64.zip","size":1},{"name":"uloop-project-runner-windows-amd64.zip.sha256","size":1}]' ;; missing) printf '[]' ;; empty) - printf '[{"name":"uloop-cli-darwin-amd64.tar.gz","size":0},{"name":"uloop-cli-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz","size":1},{"name":"uloop-cli-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-cli-windows-amd64.zip","size":1},{"name":"uloop-cli-windows-amd64.zip.sha256","size":1},{"name":"uloop-darwin-amd64.tar.gz","size":1},{"name":"uloop-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-darwin-arm64.tar.gz","size":1},{"name":"uloop-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-windows-amd64.zip","size":1},{"name":"uloop-windows-amd64.zip.sha256","size":1}]' + printf '[{"name":"uloop-project-runner-darwin-amd64.tar.gz","size":0},{"name":"uloop-project-runner-darwin-amd64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz","size":1},{"name":"uloop-project-runner-darwin-arm64.tar.gz.sha256","size":1},{"name":"uloop-project-runner-windows-amd64.zip","size":1},{"name":"uloop-project-runner-windows-amd64.zip.sha256","size":1}]' ;; esac } @@ -50,7 +50,7 @@ dispatcher_asset_json() { if [ "$1" = "release" ] && [ "$2" = "view" ]; then tag=$3 - if [ "$tag" = "${CLI_RELEASE_TAG:-cli-v3.0.0-beta.6}" ]; then + if [ "$tag" = "${CLI_RELEASE_TAG:-uloop-project-runner-v3.0.0-beta.6}" ]; then if [ -n "${CLI_RELEASE_READY_AFTER_ATTEMPTS:-}" ]; then attempt_file="$GH_LOG.cli-release-attempts" attempt=1 @@ -177,7 +177,7 @@ write_release_files() { "changelog-path": "Packages/src/CHANGELOG.md" }, "cli": { - "component": "cli", + "component": "uloop-project-runner", "release-type": "go", "include-v-in-tag": true, "include-component-in-tag": true, @@ -194,12 +194,12 @@ EOF_CONFIG } EOF_MANIFEST - cat > Packages/src/cli-pin.json < Packages/src/project-runner-pin.json < scripts/verify-native-cli-release-assets.sh <<'EOF_VERIFY' @@ -231,18 +231,12 @@ set -eu if [ "${1:-}" = "--list" ]; then printf '%s\n' \ - uloop-cli-darwin-amd64.tar.gz \ - uloop-cli-darwin-amd64.tar.gz.sha256 \ - uloop-cli-darwin-arm64.tar.gz \ - uloop-cli-darwin-arm64.tar.gz.sha256 \ - uloop-cli-windows-amd64.zip \ - uloop-cli-windows-amd64.zip.sha256 \ - uloop-darwin-amd64.tar.gz \ - uloop-darwin-amd64.tar.gz.sha256 \ - uloop-darwin-arm64.tar.gz \ - uloop-darwin-arm64.tar.gz.sha256 \ - uloop-windows-amd64.zip \ - uloop-windows-amd64.zip.sha256 + uloop-project-runner-darwin-amd64.tar.gz \ + uloop-project-runner-darwin-amd64.tar.gz.sha256 \ + uloop-project-runner-darwin-arm64.tar.gz \ + uloop-project-runner-darwin-arm64.tar.gz.sha256 \ + uloop-project-runner-windows-amd64.zip \ + uloop-project-runner-windows-amd64.zip.sha256 exit 0 fi @@ -294,7 +288,7 @@ create_release_repo() { write_release_files 3.0.0-beta.6 git add . git commit -q -m "chore: release v3-beta" - git tag cli-v3.0.0-beta.6 + git tag uloop-project-runner-v3.0.0-beta.6 git rev-parse HEAD > "$work_dir/release-sha.txt" printf '%s\n' "follow-up" > follow-up.txt @@ -315,7 +309,7 @@ prepare_origin_branch() { ( cd "$work_dir" git remote add origin "$remote_dir" - git push -q origin "$commit_sha:refs/heads/$branch_name" refs/tags/cli-v3.0.0-beta.6 + git push -q origin "$commit_sha:refs/heads/$branch_name" refs/tags/uloop-project-runner-v3.0.0-beta.6 ) } @@ -370,7 +364,7 @@ run_sync() { EXISTING_RELEASE_TARGET="$existing_target" \ CLI_RELEASE_STATE="$cli_release_state" \ CLI_RELEASE_ASSETS="$cli_release_assets" \ - CLI_RELEASE_TAG="${CLI_RELEASE_TAG:-cli-v3.0.0-beta.6}" \ + CLI_RELEASE_TAG="${CLI_RELEASE_TAG:-uloop-project-runner-v3.0.0-beta.6}" \ CLI_RELEASE_WAIT_TIMEOUT_SECONDS="$cli_release_wait_timeout" \ CLI_RELEASE_WAIT_INTERVAL_SECONDS="$cli_release_wait_interval" \ CLI_RELEASE_READY_AFTER_ATTEMPTS="$cli_release_ready_after_attempts" \ @@ -395,7 +389,7 @@ test_creates_missing_root_release_from_release_commit() { assert_contains "$work_dir/gh.log" "release view v3.0.0-beta.6 --repo hatayama/unity-cli-loop --json isDraft,targetCommitish" assert_contains "$work_dir/gh.log" "release create v3.0.0-beta.6 --repo hatayama/unity-cli-loop --title v3.0.0-beta.6 --notes-file" assert_contains "$work_dir/gh.log" "--target $release_sha --prerelease" - assert_contains "$work_dir/gh.log" "release view cli-v3.0.0-beta.6 --repo hatayama/unity-cli-loop --json isDraft,targetCommitish,assets" + assert_contains "$work_dir/gh.log" "release view uloop-project-runner-v3.0.0-beta.6 --repo hatayama/unity-cli-loop --json isDraft,targetCommitish,assets" assert_contains "$work_dir/go.log" "run ./cmd/check-protocol-minimum-version --verify-release --ref $release_sha" assert_contains "$work_dir/github-output.txt" "ready=true" } @@ -442,7 +436,7 @@ test_existing_draft_root_release_without_release_commit_fails() { write_release_files 3.0.0-beta.7 ) - if CLI_RELEASE_TAG=cli-v3.0.0-beta.7 run_sync "$work_dir" v3.0.0-beta.7 true "manual-release-target"; then + if CLI_RELEASE_TAG=uloop-project-runner-v3.0.0-beta.7 run_sync "$work_dir" v3.0.0-beta.7 true "manual-release-target"; then echo "Expected draft release without a release commit to fail." >&2 exit 1 fi @@ -451,24 +445,24 @@ test_existing_draft_root_release_without_release_commit_fails() { assert_not_contains "$work_dir/gh.log" "release edit v3.0.0-beta.7" } -# Verifies package releases wait until the matching CLI release is public. +# Verifies package releases wait until the matching Project runner release is public. test_waits_for_cli_release_before_creating_root_release() { work_dir=$(create_release_repo waits-for-cli) run_sync "$work_dir" "" false "" missing - assert_contains "$work_dir/output.txt" "CLI release cli-v3.0.0-beta.6 is not published with complete assets; package release sync will wait." + assert_contains "$work_dir/output.txt" "Project runner release uloop-project-runner-v3.0.0-beta.6 is not published with complete assets; package release sync will wait." assert_not_contains "$work_dir/gh.log" "release create v3.0.0-beta.6" assert_contains "$work_dir/github-output.txt" "ready=false" } -# Verifies package releases wait until the matching CLI release has all native assets. +# Verifies package releases wait until the matching Project runner release has all native assets. test_waits_for_cli_assets_before_creating_root_release() { work_dir=$(create_release_repo waits-for-cli-assets) run_sync "$work_dir" "" false "" published missing - assert_contains "$work_dir/output.txt" "CLI release cli-v3.0.0-beta.6 is not published with complete assets; package release sync will wait." + assert_contains "$work_dir/output.txt" "Project runner release uloop-project-runner-v3.0.0-beta.6 is not published with complete assets; package release sync will wait." assert_not_contains "$work_dir/gh.log" "release create v3.0.0-beta.6" assert_contains "$work_dir/github-output.txt" "ready=false" } @@ -495,16 +489,16 @@ test_waits_when_dispatcher_asset_list_fails() { assert_contains "$work_dir/github-output.txt" "ready=false" } -# Verifies the release sync waits for a concurrently publishing CLI release before creating package releases. +# Verifies the release sync waits for a concurrently publishing Project runner release before creating package releases. test_retries_until_cli_assets_are_ready() { work_dir=$(create_release_repo retries-until-cli-ready) release_sha=$(cat "$work_dir/release-sha.txt") run_sync "$work_dir" "" false "" published complete 3 0 3 - assert_contains "$work_dir/output.txt" "CLI release cli-v3.0.0-beta.6 is not published with complete assets yet; waiting 1s before retry." + assert_contains "$work_dir/output.txt" "Project runner release uloop-project-runner-v3.0.0-beta.6 is not published with complete assets yet; waiting 1s before retry." assert_contains "$work_dir/sleep.log" "1" - assert_contains "$work_dir/output.txt" "CLI release cli-v3.0.0-beta.6 is now published with complete assets." + assert_contains "$work_dir/output.txt" "Project runner release uloop-project-runner-v3.0.0-beta.6 is now published with complete assets." assert_contains "$work_dir/gh.log" "release create v3.0.0-beta.6 --repo hatayama/unity-cli-loop --title v3.0.0-beta.6 --notes-file" assert_contains "$work_dir/gh.log" "--target $release_sha --prerelease" assert_contains "$work_dir/github-output.txt" "ready=true" diff --git a/scripts/test-verify-native-cli-release-assets.sh b/scripts/test-verify-native-cli-release-assets.sh index 912294f28..d26606322 100755 --- a/scripts/test-verify-native-cli-release-assets.sh +++ b/scripts/test-verify-native-cli-release-assets.sh @@ -36,31 +36,23 @@ write_checksum() { ) } -write_executable "$PAYLOAD_DIR/uloop-cli" "real" -write_executable "$PAYLOAD_DIR/uloop" "dispatcher" +write_executable "$PAYLOAD_DIR/uloop-project-runner" "real" -tar -czf "$RELEASE_DIR/uloop-cli-darwin-amd64.tar.gz" -C "$PAYLOAD_DIR" ./uloop-cli -tar -czf "$RELEASE_DIR/uloop-cli-darwin-arm64.tar.gz" -C "$PAYLOAD_DIR" ./uloop-cli -tar -czf "$RELEASE_DIR/uloop-darwin-amd64.tar.gz" -C "$PAYLOAD_DIR" ./uloop ./uloop-cli -tar -czf "$RELEASE_DIR/uloop-darwin-arm64.tar.gz" -C "$PAYLOAD_DIR" ./uloop ./uloop-cli -write_checksum "uloop-cli-darwin-amd64.tar.gz" -write_checksum "uloop-cli-darwin-arm64.tar.gz" -write_checksum "uloop-darwin-amd64.tar.gz" -write_checksum "uloop-darwin-arm64.tar.gz" +tar -czf "$RELEASE_DIR/uloop-project-runner-darwin-amd64.tar.gz" -C "$PAYLOAD_DIR" ./uloop-project-runner +tar -czf "$RELEASE_DIR/uloop-project-runner-darwin-arm64.tar.gz" -C "$PAYLOAD_DIR" ./uloop-project-runner +write_checksum "uloop-project-runner-darwin-amd64.tar.gz" +write_checksum "uloop-project-runner-darwin-arm64.tar.gz" if ! command -v zip >/dev/null 2>&1; then echo "zip is required to test native CLI release asset verification" >&2 exit 1 fi -write_executable "$PAYLOAD_DIR/uloop-cli.exe" "real" -write_executable "$PAYLOAD_DIR/uloop.exe" "dispatcher" +write_executable "$PAYLOAD_DIR/uloop-project-runner.exe" "real" ( cd "$PAYLOAD_DIR" - zip -q "$RELEASE_DIR/uloop-cli-windows-amd64.zip" uloop-cli.exe - zip -q "$RELEASE_DIR/uloop-windows-amd64.zip" uloop.exe uloop-cli.exe + zip -q "$RELEASE_DIR/uloop-project-runner-windows-amd64.zip" uloop-project-runner.exe ) -write_checksum "uloop-cli-windows-amd64.zip" -write_checksum "uloop-windows-amd64.zip" +write_checksum "uloop-project-runner-windows-amd64.zip" "$ROOT_DIR/scripts/verify-native-cli-release-assets.sh" "$RELEASE_DIR" diff --git a/scripts/verify-go-cli-dist.sh b/scripts/verify-go-cli-dist.sh index 55262c6d1..431389d4d 100755 --- a/scripts/verify-go-cli-dist.sh +++ b/scripts/verify-go-cli-dist.sh @@ -5,11 +5,11 @@ ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) DIST_FILES=" cli/dist/darwin-arm64/uloop -cli/dist/darwin-arm64/uloop-cli +cli/dist/darwin-arm64/uloop-project-runner cli/dist/darwin-amd64/uloop -cli/dist/darwin-amd64/uloop-cli +cli/dist/darwin-amd64/uloop-project-runner cli/dist/windows-amd64/uloop.exe -cli/dist/windows-amd64/uloop-cli.exe +cli/dist/windows-amd64/uloop-project-runner.exe " "$ROOT_DIR/scripts/build-go-cli.sh" diff --git a/scripts/verify-native-cli-release-assets.sh b/scripts/verify-native-cli-release-assets.sh index 80ee78845..d7f118d88 100755 --- a/scripts/verify-native-cli-release-assets.sh +++ b/scripts/verify-native-cli-release-assets.sh @@ -3,18 +3,12 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) EXPECTED_ASSETS=" -uloop-cli-darwin-amd64.tar.gz -uloop-cli-darwin-amd64.tar.gz.sha256 -uloop-cli-darwin-arm64.tar.gz -uloop-cli-darwin-arm64.tar.gz.sha256 -uloop-cli-windows-amd64.zip -uloop-cli-windows-amd64.zip.sha256 -uloop-darwin-amd64.tar.gz -uloop-darwin-amd64.tar.gz.sha256 -uloop-darwin-arm64.tar.gz -uloop-darwin-arm64.tar.gz.sha256 -uloop-windows-amd64.zip -uloop-windows-amd64.zip.sha256 +uloop-project-runner-darwin-amd64.tar.gz +uloop-project-runner-darwin-amd64.tar.gz.sha256 +uloop-project-runner-darwin-arm64.tar.gz +uloop-project-runner-darwin-arm64.tar.gz.sha256 +uloop-project-runner-windows-amd64.zip +uloop-project-runner-windows-amd64.zip.sha256 " if [ "${1:-}" = "--list" ]; then @@ -104,21 +98,12 @@ for asset_name in $EXPECTED_ASSETS; do require_file "$asset_name" done -verify_checksum "uloop-cli-darwin-amd64.tar.gz" -verify_checksum "uloop-cli-darwin-arm64.tar.gz" -verify_checksum "uloop-cli-windows-amd64.zip" -verify_checksum "uloop-darwin-amd64.tar.gz" -verify_checksum "uloop-darwin-arm64.tar.gz" -verify_checksum "uloop-windows-amd64.zip" - -require_tar_entry "uloop-cli-darwin-amd64.tar.gz" "uloop-cli" -require_tar_entry "uloop-cli-darwin-arm64.tar.gz" "uloop-cli" -require_zip_entry "uloop-cli-windows-amd64.zip" "uloop-cli.exe" -require_tar_entry "uloop-darwin-amd64.tar.gz" "uloop" -require_tar_entry "uloop-darwin-amd64.tar.gz" "uloop-cli" -require_tar_entry "uloop-darwin-arm64.tar.gz" "uloop" -require_tar_entry "uloop-darwin-arm64.tar.gz" "uloop-cli" -require_zip_entry "uloop-windows-amd64.zip" "uloop.exe" -require_zip_entry "uloop-windows-amd64.zip" "uloop-cli.exe" +verify_checksum "uloop-project-runner-darwin-amd64.tar.gz" +verify_checksum "uloop-project-runner-darwin-arm64.tar.gz" +verify_checksum "uloop-project-runner-windows-amd64.zip" + +require_tar_entry "uloop-project-runner-darwin-amd64.tar.gz" "uloop-project-runner" +require_tar_entry "uloop-project-runner-darwin-arm64.tar.gz" "uloop-project-runner" +require_zip_entry "uloop-project-runner-windows-amd64.zip" "uloop-project-runner.exe" echo "Native CLI release assets are complete."