Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/native-cli-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
8 changes: 0 additions & 8 deletions .uloop/cli-pin.json

This file was deleted.

8 changes: 8 additions & 0 deletions .uloop/project-runner-pin.json
Original file line number Diff line number Diff line change
@@ -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
}
47 changes: 35 additions & 12 deletions Assets/Tests/Editor/CliPinSynchronizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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");
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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
{
Expand Down
16 changes: 0 additions & 16 deletions Assets/Tests/Editor/JsonRpcProcessorCliVersionGateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
14 changes: 0 additions & 14 deletions Assets/Tests/Editor/NativeCliInstallerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
14 changes: 7 additions & 7 deletions Packages/src/Editor/Domain/CliConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment thread
coderabbitai[bot] marked this conversation as resolved.
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;
Expand All @@ -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-";
Expand Down
10 changes: 5 additions & 5 deletions Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,24 +89,24 @@ 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),
Id = request["id"]?.ToObject<object>()
};
}

private static string ReadClientCliVersion(JObject request)
private static string ReadClientProjectRunnerVersion(JObject request)
{
JObject metadata = request["uloop"] as JObject;
if (metadata == null)
{
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)
Expand Down Expand Up @@ -216,7 +216,7 @@ private static async Task<string> ProcessRpcRequest(
{
return CreateCliProtocolMismatchResponse(
request.Id,
request.ClientCliVersion,
request.ClientProjectRunnerVersion,
request.ClientProtocolVersion);
}

Expand Down
2 changes: 1 addition & 1 deletion Packages/src/Editor/Infrastructure/Api/JsonRpcRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal class JsonRpcRequest
/// </summary>
public JToken Params { get; set; }

public string ClientCliVersion { get; set; }
public string ClientProjectRunnerVersion { get; set; }

/// <summary>
/// IPC protocol generation the client speaks. Null when the client predates the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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)
Expand Down
17 changes: 11 additions & 6 deletions Packages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
4 changes: 2 additions & 2 deletions Packages/src/Editor/Infrastructure/CLI/NativeCliInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading