diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 2564399544..d730823d22 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -35,9 +35,9 @@ reviews: path_instructions: - path: "**/*" instructions: | - For changes that touch the Unity package/native CLI IPC contract, explicitly check whether the integer protocol generation must be bumped. Relevant IPC surfaces include JSON-RPC method names, request/response shapes, request metadata under `uloop`, readiness/dispatch/heartbeat handshakes, tool catalog schema, `cli/internal/unityipc/**`, `Packages/src/Editor/Infrastructure/Api/**`, `cli/contract.json`, and `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION`. + For changes that touch the Unity package/native CLI IPC contract, explicitly check whether the integer protocol generation must be bumped. Relevant IPC surfaces include JSON-RPC method names, request/response shapes, request metadata under `uloop`, readiness/dispatch/heartbeat handshakes, tool catalog schema, `common/unityipc/**`, `Packages/src/Editor/Infrastructure/Api/**`, `common/clicontract/contract.json`, and `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION`. - 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. + 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 `common/clicontract/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. `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: diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index aad6ada98d..3cfdf13cca 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version + go-version-file: .go-version cache: false - name: Test release automation shell helpers @@ -50,24 +50,24 @@ jobs: - name: Check IPC protocol reminder if: github.event_name == 'pull_request' - working-directory: cli + working-directory: tools/release-automation run: go run ./cmd/check-ipc-protocol-reminder --base "origin/${{ github.base_ref }}" --head HEAD - name: Check protocol minimum version bump if: github.event_name == 'pull_request' && !(startsWith(github.head_ref, 'release-please--branches--') && (github.event.pull_request.user.login == 'github-actions[bot]' || github.event.pull_request.user.login == 'release-please[bot]')) - working-directory: cli + working-directory: tools/release-automation env: GH_TOKEN: ${{ github.token }} run: go run ./cmd/check-protocol-minimum-version --base "origin/${{ github.base_ref }}" --head HEAD - name: Check dispatcher minimum version if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - working-directory: cli + working-directory: tools/release-automation run: go run ./cmd/check-dispatcher-minimum-version - name: Check dispatcher version bump if: github.event_name == 'pull_request' - working-directory: cli + working-directory: tools/release-automation run: go run ./cmd/check-dispatcher-version-bump --base "origin/${{ github.base_ref }}" --head HEAD - name: Install golangci-lint @@ -91,7 +91,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version + go-version-file: .go-version cache: false # Windows-only code paths (named pipe transport, winio deadlines, typed error @@ -100,7 +100,7 @@ jobs: # separators, missing .exe suffixes, TCP-only fixtures) that predate this job. - name: Test native Go CLI transport on Windows shell: bash - run: cd cli && go test ./internal/unityipc/ + run: cd common && go test ./unityipc/ - name: Test install release filter in Git Bash shell: bash @@ -133,10 +133,10 @@ jobs: test -d "Packages/src/Editor" || exit 1 test ! -d "Packages/src/Cli~" || exit 1 test -d "Packages/src/Editor/CliOnlyTools~" || exit 1 - test -d "cli" || exit 1 - test -f "cli/go.mod" || exit 1 - test -d "cli/cmd" || exit 1 - test -d "cli/internal" || exit 1 + test -d "project-runner" || exit 1 + test -f "project-runner/go.mod" || exit 1 + test -d "project-runner/cmd" || exit 1 + test -d "project-runner/internal" || exit 1 echo "✅ Unity Package structure is valid" diff --git a/.github/workflows/code-complexity.yml b/.github/workflows/code-complexity.yml index 5cbd39e0a6..01e954e925 100644 --- a/.github/workflows/code-complexity.yml +++ b/.github/workflows/code-complexity.yml @@ -4,9 +4,12 @@ on: pull_request: branches: [ main, v3-beta ] paths: - - 'cli/**/*.go' - - 'cli/.go-version' - - 'cli/.golangci-complexity.yml' + - 'common/**/*.go' + - 'dispatcher/**/*.go' + - 'project-runner/**/*.go' + - 'tools/release-automation/**/*.go' + - '.go-version' + - '.golangci-complexity.yml' - 'Packages/src/**/*.cs' - 'tools/UnityCliLoop.CodeComplexity/**' - 'tests/UnityCliLoop.CodeComplexity.Tests/**' @@ -29,7 +32,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version + go-version-file: .go-version cache: false - name: Setup .NET @@ -54,7 +57,10 @@ jobs: if: always() run: | mkdir -p artifacts - (cd cli && golangci-lint run --config .golangci-complexity.yml --issues-exit-code=0 --show-stats=false --output.json.path ../artifacts/go-code-complexity.json ./...) + for module_dir in common dispatcher project-runner tools/release-automation; do + artifact_name="go-code-complexity-$(printf '%s' "$module_dir" | tr '/' '-').json" + (cd "$module_dir" && golangci-lint run --config "$GITHUB_WORKSPACE/.golangci-complexity.yml" --issues-exit-code=0 --show-stats=false --output.json.path "$GITHUB_WORKSPACE/artifacts/$artifact_name" ./...) + done dotnet run --project tools/UnityCliLoop.CodeComplexity/UnityCliLoop.CodeComplexity.csproj -- \ --root . \ --max-complexity 15 \ diff --git a/.github/workflows/dispatcher-publish.yml b/.github/workflows/dispatcher-publish.yml index 388cbe13b8..5555ff4340 100644 --- a/.github/workflows/dispatcher-publish.yml +++ b/.github/workflows/dispatcher-publish.yml @@ -70,8 +70,8 @@ jobs: if: steps.release.outputs.publish == 'true' || steps.release.outputs.release == 'true' uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version - cache-dependency-path: cli/**/go.sum + go-version-file: .go-version + cache-dependency-path: '**/go.sum' - name: Install golangci-lint if: steps.release.outputs.publish == 'true' @@ -95,7 +95,7 @@ jobs: if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 with: - subject-path: cli/dist/dispatcher-release/* + subject-path: dist/dispatcher-release/* - name: Verify dispatcher asset attestations if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' @@ -106,7 +106,7 @@ jobs: run: | set -eu - for asset_path in cli/dist/dispatcher-release/*; do + for asset_path in dist/dispatcher-release/*; do gh attestation verify "${asset_path}" \ --repo "${GITHUB_REPOSITORY}" \ --signer-workflow "${SIGNER_WORKFLOW}" @@ -117,7 +117,7 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: dispatcher-release-assets-${{ steps.release.outputs.tag }} - path: cli/dist/dispatcher-release/* + path: dist/dispatcher-release/* if-no-files-found: error - name: Create or reuse draft dispatcher release @@ -194,7 +194,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ steps.release.outputs.tag }} run: | - gh release upload "${RELEASE_TAG}" cli/dist/dispatcher-release/* --clobber + gh release upload "${RELEASE_TAG}" dist/dispatcher-release/* --clobber - name: Verify remote dispatcher release assets if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' @@ -246,4 +246,4 @@ jobs: - name: Dry run summary if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run == 'true' run: | - ls -lh cli/dist/dispatcher-release + ls -lh dist/dispatcher-release diff --git a/.github/workflows/native-cli-publish.yml b/.github/workflows/native-cli-publish.yml index cc9e8f393e..b62d499dc8 100644 --- a/.github/workflows/native-cli-publish.yml +++ b/.github/workflows/native-cli-publish.yml @@ -75,8 +75,8 @@ jobs: if: steps.release.outputs.publish == 'true' || steps.release.outputs.release == 'true' uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version - cache-dependency-path: cli/**/go.sum + go-version-file: .go-version + cache-dependency-path: '**/go.sum' - name: Install golangci-lint if: steps.release.outputs.publish == 'true' @@ -100,7 +100,7 @@ jobs: if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 with: - subject-path: cli/dist/release/* + subject-path: dist/release/* - name: Verify native CLI asset attestations if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' @@ -111,7 +111,7 @@ jobs: run: | set -eu - for asset_path in cli/dist/release/*; do + for asset_path in dist/release/*; do gh attestation verify "${asset_path}" \ --repo "${GITHUB_REPOSITORY}" \ --signer-workflow "${SIGNER_WORKFLOW}" @@ -122,7 +122,7 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: native-cli-release-assets-${{ steps.release.outputs.tag }} - path: cli/dist/release/* + path: dist/release/* if-no-files-found: error - name: Write release notes @@ -151,7 +151,7 @@ jobs: exit 1 } } - ' cli/CHANGELOG.md > "${NOTES_FILE}" + ' project-runner/CHANGELOG.md > "${NOTES_FILE}" echo "RELEASE_NOTES_FILE=${NOTES_FILE}" >> "$GITHUB_ENV" @@ -212,7 +212,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ steps.release.outputs.tag }} run: | - gh release upload "${RELEASE_TAG}" cli/dist/release/* --clobber + gh release upload "${RELEASE_TAG}" dist/release/* --clobber - name: Verify remote release assets if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true' @@ -281,4 +281,4 @@ jobs: - name: Dry run summary if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run == 'true' run: | - ls -lh cli/dist/release + ls -lh dist/release diff --git a/.github/workflows/protocol-minimum-version-warning.yml b/.github/workflows/protocol-minimum-version-warning.yml index a6ab92a981..162aa7d23c 100644 --- a/.github/workflows/protocol-minimum-version-warning.yml +++ b/.github/workflows/protocol-minimum-version-warning.yml @@ -23,14 +23,14 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version + go-version-file: .go-version cache: false - name: Fetch pull request merge run: git fetch --no-tags origin "pull/${{ github.event.pull_request.number }}/merge:protocol-minimum-version-pr-merge" - name: Comment on protocol minimum version guard - working-directory: cli + working-directory: tools/release-automation env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 98e41e811d..4d2f10f0d0 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -74,8 +74,8 @@ jobs: - name: Setup Go for package release sync uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version - cache-dependency-path: cli/**/go.sum + go-version-file: .go-version + cache-dependency-path: '**/go.sum' - name: 🏷️ Sync release-please package releases id: package_release_sync @@ -108,12 +108,12 @@ jobs: if: steps.target.outputs.branch == 'v3-beta' && steps.release_commit.outputs.skip != 'true' && steps.package_release_sync.outputs.ready != 'false' && steps.release.outputs.prs_created == 'true' uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version - cache-dependency-path: cli/**/go.sum + go-version-file: .go-version + cache-dependency-path: '**/go.sum' - name: Dispatch release PR checks if: steps.target.outputs.branch == 'v3-beta' && steps.release_commit.outputs.skip != 'true' && steps.package_release_sync.outputs.ready != 'false' && steps.release.outputs.prs_created == 'true' - working-directory: cli + working-directory: tools/release-automation env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} TARGET_BRANCH: ${{ steps.target.outputs.branch }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 0cced8c378..d8f276d909 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -6,21 +6,27 @@ on: paths: - 'Packages/src/**/*.cs' - 'Assets/**/*.cs' - - 'cli/**/*.go' - - 'cli/.golangci.yml' - - 'cli/.go-version' - - 'cli/**/go.mod' - - 'cli/**/go.sum' + - 'common/**/*.go' + - 'dispatcher/**/*.go' + - 'project-runner/**/*.go' + - 'tools/release-automation/**/*.go' + - '.golangci.yml' + - '.go-version' + - '**/go.mod' + - '**/go.sum' pull_request: branches: [ main, v3-beta ] paths: - 'Packages/src/**/*.cs' - 'Assets/**/*.cs' - - 'cli/**/*.go' - - 'cli/.golangci.yml' - - 'cli/.go-version' - - 'cli/**/go.mod' - - 'cli/**/go.sum' + - 'common/**/*.go' + - 'dispatcher/**/*.go' + - 'project-runner/**/*.go' + - 'tools/release-automation/**/*.go' + - '.golangci.yml' + - '.go-version' + - '**/go.mod' + - '**/go.sum' workflow_dispatch: jobs: @@ -114,7 +120,7 @@ jobs: - name: Setup Go uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c with: - go-version-file: cli/.go-version + go-version-file: .go-version cache: false - name: Install golangci-lint @@ -132,4 +138,6 @@ jobs: - name: Run govulncheck run: | - (cd cli && govulncheck ./...) + for module_dir in common dispatcher project-runner tools/release-automation; do + (cd "$module_dir" && govulncheck ./...) + done diff --git a/.gitignore b/.gitignore index ae8e364345..27334bae0c 100644 --- a/.gitignore +++ b/.gitignore @@ -112,12 +112,7 @@ yarn-error.log* **/[Oo]bj/ # Native Go CLI build and release outputs -/cli/dist/ -!cli/Core~/dist/darwin-arm64/uloop-core -!cli/Core~/dist/darwin-amd64/uloop-core -!cli/Core~/dist/windows-amd64/uloop-core.exe -cli/Dispatcher~/dist/ -cli/Dispatcher~/release/ +/dist/ # Environment configuration files .env diff --git a/cli/.go-version b/.go-version similarity index 100% rename from cli/.go-version rename to .go-version diff --git a/cli/.golangci-complexity.yml b/.golangci-complexity.yml similarity index 100% rename from cli/.golangci-complexity.yml rename to .golangci-complexity.yml diff --git a/cli/.golangci.yml b/.golangci.yml similarity index 100% rename from cli/.golangci.yml rename to .golangci.yml diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 521d14d679..a3f8e1a2d5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,4 @@ { "Packages/src": "3.0.0-beta.48", - "cli": "3.0.0-beta.46" + "project-runner": "3.0.0-beta.46" } diff --git a/AGENTS.md b/AGENTS.md index 2346420506..d5d03382a5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ Every test method must have a short comment that states what behavior the test v Runtime compatibility between the Unity package and the native CLI is gated on an integer protocol version, not on release numbers. Two declarations must always stay equal: -- Go side: `protocolVersion` in `cli/contract.json` (the generation the CLI advertises over IPC). +- Go side: `protocolVersion` in `common/clicontract/contract.json` (the generation the CLI advertises over IPC). - C# side: `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION` (the exact generation the package accepts). `TestProtocolVersionMatchesUnityPackage` fails the build if they diverge, so never bump one alone. @@ -33,14 +33,14 @@ compatible must not bump it. Do not touch the protocol version to "keep up with releases": -- `cli/contract.json` `cliVersion` and `cli/internal/tools/default-tools.json` `version` are - stamped by release-please only. Never edit them by hand in a feature PR. -- `CliConstants.MINIMUM_REQUIRED_CLI_VERSION` is the release that setup installs. It must always - point at a published CLI release. +- `common/clicontract/contract.json` `projectRunnerVersion` and `common/tools/default-tools.json` + `version` are stamped by release-please only. Never edit them by hand in a feature PR. +- `CliConstants.MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` is the release that setup installs. It + must always point at a published project runner release. - When a protocol bump changes `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION`, prepare the matching - CLI release tag first, then update `CliConstants.MINIMUM_REQUIRED_CLI_VERSION` in the same PR. - PR CI fails, and the PR warning comment stays open, until the minimum CLI release advances to a - published CLI release that advertises the required protocol. + project runner release tag first, then update `CliConstants.MINIMUM_REQUIRED_PROJECT_RUNNER_VERSION` + in the same PR. PR CI fails, and the PR warning comment stays open, until the minimum project + runner release advances to a published release that advertises the required protocol. - Runtime protocol mismatch guidance must use the unpinned CLI update path for older clients and tell newer clients to align the package and CLI releases. @@ -56,20 +56,20 @@ Shell scripts are acceptable only as thin wrappers or simple command sequences. ## Dispatcher Release Inputs -The global dispatcher has its own release version in `cli/dispatcher-contract.json`. +The global dispatcher has its own release version in `dispatcher/dispatcher-contract.json`. When changing dispatcher release inputs, update `dispatcherVersion` in the same PR. Pull request CI runs `check-dispatcher-version-bump` and fails if dispatcher inputs changed without a dispatcher version increase. The authoritative dispatcher release input list is `dispatcherReleaseInputPatterns` in -`cli/internal/automation/dispatcher_version_bump_guard.go`. +`tools/release-automation/internal/automation/dispatcher_version_bump_guard.go`. Dispatcher release inputs include these non-exhaustive examples: -- `cli/cmd/dispatcher/main.go` -- `cli/dispatcher-contract.json` -- `cli/internal/dispatcher/*.go` -- `cli/internal/clicore/*.go` -- `cli/internal/update/*.go` +- `dispatcher/cmd/dispatcher/main.go` +- `dispatcher/dispatcher-contract.json` +- `dispatcher/internal/dispatcher/*.go` +- `common/clicore/*.go` +- `dispatcher/internal/update/*.go` - `scripts/install.ps1` Do not bump `dispatcherContractVersion` unless the dispatcher contract itself changes. @@ -87,7 +87,7 @@ Before changing scripts, skill files, generated-file synchronization, path handl - Normalize relative paths at API boundaries. Do not compare raw path strings that may contain `/` on one side and `\` on another. Convert separators before storing, comparing, deleting, or syncing generated files. - Prefer forward slashes in JSON `file:` paths and other cross-platform config values. Use escaped backslashes only when the target format explicitly requires them. - Validate Windows-facing PowerShell with both `pwsh` and Windows PowerShell when practical, especially for multiline arguments, here-strings, UTF-8 files, and native executable calls. -- When validating this checkout on Windows, use the repo-local native binary (`cli/dist/windows-amd64/uloop.exe`) instead of a `PATH`-resolved `uloop`. If a bash validation command cannot see the expected Go toolchain on Windows, retry through a login shell such as `bash -lc`. +- When validating this checkout on Windows, use the repo-local native binary (`dist/windows-amd64/uloop.exe`) instead of a `PATH`-resolved `uloop`. If a bash validation command cannot see the expected Go toolchain on Windows, retry through a login shell such as `bash -lc`. - Add or update a regression test whenever a fix depends on encoding, line endings, or separator normalization. A passing macOS test alone is not enough for these cases. ## Dead Code Scanner @@ -117,19 +117,19 @@ Interpret scanner output conservatively: When running `uloop` commands for this project during CLI development, do not use the `uloop` command resolved from `PATH`. Run this checkout's built development binary directly so validation uses the code under review: ```bash -cli/dist/darwin-arm64/uloop compile --project-path "$(git rev-parse --show-toplevel)" +dist/darwin-arm64/uloop compile --project-path "$(git rev-parse --show-toplevel)" ``` Before running a command with `--project-path`, confirm that the path is the intended Unity project for the current task. Do not copy a sibling checkout path from another repository or prior session. When intentionally validating a different Unity project, use an explicit placeholder in notes and replace it at execution time: ```bash -cli/dist/darwin-arm64/uloop compile --project-path +dist/darwin-arm64/uloop compile --project-path ``` If CLI source changes affect the command behavior you are validating, rebuild the development binary before running it. -When changing Go CLI source files under `cli`, run `scripts/check-go-cli.sh`. -Use `scripts/build-go-cli.sh` when you need to refresh local development binaries under `cli/dist`; generated binaries are ignored and must not be committed. +When changing Go source files under any of the Go modules (`common`, `dispatcher`, `project-runner`, `tools/release-automation`), run `scripts/check-go-cli.sh`. +Use `scripts/build-go-cli.sh` when you need to refresh local development binaries under `dist`; generated binaries are ignored and must not be committed. This script is the local equivalent of the Go CLI CI validation: it runs formatting checks, vet, lint, tests, rebuilds the built native binaries, and verifies that required platform binaries exist. ## Unity Freeze Prevention diff --git a/README.md b/README.md index f52f3f8adf..2f8bd842f3 100644 --- a/README.md +++ b/README.md @@ -596,9 +596,9 @@ Run the native Go CLI checks before changing files under `cli`: scripts/check-go-cli.sh ``` -The check script verifies formatting with `goimports` and `gofumpt`, runs `go vet ./...`, runs `golangci-lint`, and then runs `go test ./...` for the Go CLI module. Install `golangci-lint` first if it is not available on your `PATH`. +The check script verifies formatting with `goimports` and `gofumpt`, runs `go vet ./...`, runs `golangci-lint`, and then runs `go test ./...` for every Go module in the repository. Install `golangci-lint` first if it is not available on your `PATH`. -Use the existing build script when you need to refresh local development binaries under `cli/dist`: +Use the existing build script when you need to refresh local development binaries under `dist`: ```bash scripts/build-go-cli.sh diff --git a/cli/internal/architecture/architecture_test.go b/cli/internal/architecture/architecture_test.go deleted file mode 100644 index 319837e213..0000000000 --- a/cli/internal/architecture/architecture_test.go +++ /dev/null @@ -1,339 +0,0 @@ -package architecture - -import ( - "encoding/json" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" -) - -const ( - cliModulePath = "github.com/hatayama/unity-cli-loop/cli" - maxProductionFileLines = 500 -) - -type goPackage struct { - ImportPath string - Imports []string -} - -type layoutContract struct { - SchemaVersion int `json:"schemaVersion"` - Layout layoutSection `json:"layout"` - Binaries binariesLayout `json:"binaries"` -} - -type layoutSection struct { - CliDir string `json:"cliDir"` - DistDir string `json:"distDir"` -} - -type binariesLayout struct { - Cli cliBinaryNames `json:"cli"` -} - -type cliBinaryNames struct { - Unix string `json:"unix"` - Windows string `json:"windows"` -} - -// Tests that every package outside the CLI orchestration layer (dispatcher, -// project runner, shared CLI core) and cmd/ stays free of orchestration -// imports. Skipping only known orchestration packages keeps future packages -// covered by default instead of requiring a hand-maintained feature list. -func TestCliFeaturePackagesDoNotImportOrchestrationLayer(t *testing.T) { - moduleRoot := findModuleRoot(t) - packages := listPackages(t, moduleRoot) - orchestrationPackagePrefixes := []string{ - cliModulePath + "/internal/dispatcher", - cliModulePath + "/internal/projectrunner", - cliModulePath + "/internal/clicore", - } - for _, goPackage := range packages { - if hasAnyPackagePrefix(goPackage.ImportPath, orchestrationPackagePrefixes) || - strings.HasPrefix(goPackage.ImportPath, cliModulePath+"/cmd/") { - continue - } - for _, importedPath := range goPackage.Imports { - if hasAnyPackagePrefix(importedPath, orchestrationPackagePrefixes) { - t.Fatalf("feature package %s must not import CLI orchestration package %s", goPackage.ImportPath, importedPath) - } - } - } -} - -// hasAnyPackagePrefix reports whether importPath is packagePrefix itself or a subpackage of it, -// avoiding false positives such as "/internal/project" matching "/internal/projectrunner". -func hasAnyPackagePrefix(importPath string, packagePrefixes []string) bool { - for _, packagePrefix := range packagePrefixes { - if importPath == packagePrefix || strings.HasPrefix(importPath, packagePrefix+"/") { - return true - } - } - return false -} - -// Tests that CLI internal packages stay inside explicit runtime boundaries. -func TestCliInternalPackagesStayInsideExplicitBoundaries(t *testing.T) { - moduleRoot := findModuleRoot(t) - packages := listPackages(t, moduleRoot) - boundaryPrefixes := []string{} - for _, boundary := range []string{"automation", "clicore", "dispatcher", "install", "project", "projectrunner", "skills", "tools", "uninstall", "unityipc", "update", "version"} { - boundaryPrefixes = append(boundaryPrefixes, cliModulePath+"/internal/"+boundary) - } - for _, goPackage := range packages { - if !strings.HasPrefix(goPackage.ImportPath, cliModulePath+"/internal/") { - continue - } - if goPackage.ImportPath == cliModulePath+"/internal/architecture" { - continue - } - if !hasAnyPackagePrefix(goPackage.ImportPath, boundaryPrefixes) { - t.Fatalf("CLI internal package must live under an explicit runtime boundary: %s", goPackage.ImportPath) - } - } -} - -// Tests that the dispatcher command only enters the dispatcher package. -func TestDispatcherCommandOnlyDependsOnDispatcherEntrypoint(t *testing.T) { - assertCommandOnlyDependsOnInternalEntrypoint(t, "./cmd/dispatcher", cliModulePath+"/internal/dispatcher") -} - -// 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") -} - -// Tests that the dispatcher binary does not transitively pull in the project runner package. -func TestDispatcherBinaryDoesNotTransitivelyDependOnProjectRunner(t *testing.T) { - assertBinaryDoesNotTransitivelyDependOn(t, "./cmd/dispatcher", cliModulePath+"/internal/projectrunner") -} - -// Tests that the project runner binary does not transitively pull in the dispatcher package. -func TestProjectRunnerBinaryDoesNotTransitivelyDependOnDispatcher(t *testing.T) { - assertBinaryDoesNotTransitivelyDependOn(t, "./cmd/project-runner", cliModulePath+"/internal/dispatcher") -} - -func assertBinaryDoesNotTransitivelyDependOn(t *testing.T, commandPath string, forbiddenPackage string) { - t.Helper() - moduleRoot := findModuleRoot(t) - command := exec.Command("go", "list", "-deps", commandPath) - command.Dir = moduleRoot - output, err := command.Output() - if err != nil { - t.Fatalf("go list -deps failed: %v", err) - } - for _, dependency := range strings.Split(strings.TrimSpace(string(output)), "\n") { - if dependency == forbiddenPackage { - t.Fatalf("%s must not transitively depend on %s", commandPath, forbiddenPackage) - } - } -} - -func assertCommandOnlyDependsOnInternalEntrypoint(t *testing.T, commandPath string, expectedEntrypoint string) { - t.Helper() - moduleRoot := findModuleRoot(t) - command := exec.Command("go", "list", "-json", commandPath) - command.Dir = moduleRoot - output, err := command.Output() - if err != nil { - t.Fatalf("go list failed: %v", err) - } - - var commandPackage goPackage - if err := json.Unmarshal(output, &commandPackage); err != nil { - t.Fatalf("failed to decode command package: %v", err) - } - for _, dependency := range commandPackage.Imports { - for _, removedModule := range []string{"/cli/Dispatcher", "/cli/Core", "/cli/Shared"} { - if strings.Contains(dependency, removedModule) { - t.Fatalf("CLI command must not depend on removed split module package %s", dependency) - } - } - if !strings.HasPrefix(dependency, cliModulePath+"/internal/") { - continue - } - if dependency != expectedEntrypoint { - t.Fatalf("%s must enter internal code through %s, got %s", commandPath, expectedEntrypoint, dependency) - } - } -} - -// Tests that the parent CLI layout manifest matches repository paths used by tooling. -func TestLayoutContractMatchesRepositoryPaths(t *testing.T) { - moduleRoot := findModuleRoot(t) - repositoryRoot := findRepositoryRoot(t, moduleRoot) - contract := readLayoutContract(t, filepath.Join(moduleRoot, "layout-contract.json")) - - if contract.SchemaVersion != 1 { - t.Fatalf("layout contract schema version mismatch: %d", contract.SchemaVersion) - } - assertDirectoryName(t, moduleRoot, contract.Layout.CliDir) - assertPathExists(t, filepath.Join(moduleRoot, "cmd")) - assertPathExists(t, filepath.Join(moduleRoot, "internal")) - assertPathDoesNotExist(t, filepath.Join(moduleRoot, "Core~")) - assertPathDoesNotExist(t, filepath.Join(moduleRoot, "Dispatcher~")) - assertPathDoesNotExist(t, filepath.Join(moduleRoot, "Shared~")) - assertTextContains(t, filepath.Join(repositoryRoot, "scripts", "build-go-cli.sh"), packagePath(contract, "")) - assertTextContains(t, filepath.Join(repositoryRoot, "scripts", "verify-go-cli-dist.sh"), filepath.ToSlash(filepath.Join(packagePath(contract, contract.Layout.DistDir), "darwin-arm64", contract.Binaries.Cli.Unix))) -} - -// Tests that production files stay small enough to keep each file focused on one responsibility. -func TestProductionGoFilesStayFocused(t *testing.T) { - moduleRoot := findModuleRoot(t) - err := filepath.WalkDir(moduleRoot, func(path string, entry os.DirEntry, walkErr error) error { - if walkErr != nil { - return walkErr - } - if entry.IsDir() { - if entry.Name() == "dist" { - return filepath.SkipDir - } - return nil - } - if !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") { - return nil - } - lineCount, err := countLines(path) - if err != nil { - return err - } - if lineCount > maxProductionFileLines { - relativePath, err := filepath.Rel(moduleRoot, path) - if err != nil { - return err - } - return fmt.Errorf("%s has %d lines; split files above %d lines", relativePath, lineCount, maxProductionFileLines) - } - return nil - }) - if err != nil { - t.Fatal(err) - } -} - -func listPackages(t *testing.T, moduleRoot string) []goPackage { - t.Helper() - command := exec.Command("go", "list", "-json", "./...") - command.Dir = moduleRoot - output, err := command.Output() - if err != nil { - t.Fatalf("go list failed: %v", err) - } - - decoder := json.NewDecoder(strings.NewReader(string(output))) - packages := []goPackage{} - for { - var goPackage goPackage - err := decoder.Decode(&goPackage) - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("failed to decode go list output: %v", err) - } - packages = append(packages, goPackage) - } - return packages -} - -func readLayoutContract(t *testing.T, path string) layoutContract { - t.Helper() - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("failed to read layout contract: %v", err) - } - var contract layoutContract - if err := json.Unmarshal(content, &contract); err != nil { - t.Fatalf("failed to parse layout contract: %v", err) - } - return contract -} - -func packagePath(contract layoutContract, childDir string) string { - return filepath.ToSlash(filepath.Join(contract.Layout.CliDir, childDir)) -} - -func assertDirectoryName(t *testing.T, path string, expectedName string) { - t.Helper() - if filepath.Base(path) != expectedName { - t.Fatalf("directory name mismatch: %s", path) - } -} - -func assertPathExists(t *testing.T, path string) { - t.Helper() - if _, err := os.Stat(path); err != nil { - t.Fatalf("expected path to exist: %s", path) - } -} - -func assertPathDoesNotExist(t *testing.T, path string) { - t.Helper() - if _, err := os.Stat(path); err == nil { - t.Fatalf("expected path not to exist: %s", path) - } -} - -func assertTextContains(t *testing.T, path string, expected string) { - t.Helper() - content, err := os.ReadFile(path) - if err != nil { - t.Fatalf("failed to read %s: %v", path, err) - } - if !strings.Contains(string(content), expected) { - t.Fatalf("%s must contain %s", path, expected) - } -} - -func findModuleRoot(t *testing.T) string { - t.Helper() - currentPath, err := os.Getwd() - if err != nil { - t.Fatalf("failed to get working directory: %v", err) - } - for { - if _, err := os.Stat(filepath.Join(currentPath, "go.mod")); err == nil { - return currentPath - } - parentPath := filepath.Dir(currentPath) - if parentPath == currentPath { - t.Fatal("go.mod not found") - } - currentPath = parentPath - } -} - -func findRepositoryRoot(t *testing.T, startPath string) string { - t.Helper() - currentPath := startPath - for { - if _, err := os.Stat(filepath.Join(currentPath, ".git")); err == nil { - return currentPath - } - parentPath := filepath.Dir(currentPath) - if parentPath == currentPath { - t.Fatal(".git not found") - } - currentPath = parentPath - } -} - -func countLines(path string) (int, error) { - content, err := os.ReadFile(path) - if err != nil { - return 0, err - } - if len(content) == 0 { - return 0, nil - } - lineCount := strings.Count(string(content), "\n") - if !strings.HasSuffix(string(content), "\n") { - lineCount++ - } - return lineCount, nil -} diff --git a/cli/layout-contract.json b/cli/layout-contract.json deleted file mode 100644 index 894dc53cef..0000000000 --- a/cli/layout-contract.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "schemaVersion": 1, - "layout": { - "cliDir": "cli", - "distDir": "dist" - }, - "binaries": { - "cli": { - "unix": "uloop", - "windows": "uloop.exe" - } - } -} diff --git a/common/clicontract/contract.go b/common/clicontract/contract.go new file mode 100644 index 0000000000..071a02e4dc --- /dev/null +++ b/common/clicontract/contract.go @@ -0,0 +1,51 @@ +package clicontract + +import ( + "embed" + "encoding/json" + "fmt" +) + +const ( + contractFileName = "contract.json" + schemaVersion = 1 +) + +//go:embed contract.json +var contractFiles embed.FS + +var Current = mustLoadContract() + +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"` + ProjectRunnerVersion string `json:"projectRunnerVersion"` +} + +func mustLoadContract() Contract { + content, err := contractFiles.ReadFile(contractFileName) + if err != nil { + panic(fmt.Sprintf("CLI contract is not embedded: %v", err)) + } + + var contract Contract + if err := json.Unmarshal(content, &contract); err != nil { + panic(fmt.Sprintf("CLI contract is invalid JSON: %v", err)) + } + if contract.SchemaVersion != schemaVersion { + panic(fmt.Sprintf("CLI contract schema version mismatch: %d", contract.SchemaVersion)) + } + requireString(contract.ProjectRunnerVersion, "projectRunnerVersion") + if contract.ProtocolVersion < 1 { + panic(fmt.Sprintf("CLI contract protocolVersion must be at least 1, got %d", contract.ProtocolVersion)) + } + return contract +} + +func requireString(value string, key string) { + if value == "" { + panic(fmt.Sprintf("contract field %s must not be empty", key)) + } +} diff --git a/cli/contract.json b/common/clicontract/contract.json similarity index 100% rename from cli/contract.json rename to common/clicontract/contract.json diff --git a/common/clicontract/contract_test.go b/common/clicontract/contract_test.go new file mode 100644 index 0000000000..04c69f2775 --- /dev/null +++ b/common/clicontract/contract_test.go @@ -0,0 +1,61 @@ +package clicontract + +import ( + "encoding/json" + "testing" + + "github.com/hatayama/unity-cli-loop/common/version" +) + +func TestCliContractProvidesRuntimeVersion(t *testing.T) { + // Verifies that the project runner owns its runtime version from the single CLI module. + requireValidContractVersion(t, "projectRunnerVersion", Current.ProjectRunnerVersion) +} + +func TestCliContractProvidesProtocolVersion(t *testing.T) { + // Verifies that the contract declares which C#-side IPC protocol the binary speaks. + if Current.ProtocolVersion < 1 { + t.Fatalf("protocolVersion must be at least 1, got %d", Current.ProtocolVersion) + } +} + +func TestCliContractDoesNotDeclareDispatcherReleaseFields(t *testing.T) { + // Verifies release-please CLI version stamping cannot accidentally move dispatcher release metadata. + fields := requireContractFieldMap(t, contractFileName) + requireContractFieldMissing(t, fields, "dispatcherVersion") + requireContractFieldMissing(t, fields, "dispatcherContractVersion") +} + +func requireValidContractVersion(t *testing.T, label string, value string) { + t.Helper() + + if value == "" { + t.Fatalf("%s must not be empty", label) + } + _, ok := version.Compare(value, value) + if !ok { + t.Fatalf("%s must be valid semver: %s", label, value) + } +} + +func requireContractFieldMap(t *testing.T, fileName string) map[string]any { + t.Helper() + + content, err := contractFiles.ReadFile(fileName) + if err != nil { + t.Fatalf("failed to read %s: %v", fileName, err) + } + fields := map[string]any{} + if err := json.Unmarshal(content, &fields); err != nil { + t.Fatalf("%s is invalid JSON: %v", fileName, err) + } + return fields +} + +func requireContractFieldMissing(t *testing.T, fields map[string]any, fieldName string) { + t.Helper() + + if _, ok := fields[fieldName]; ok { + t.Fatalf("contract must not declare %s", fieldName) + } +} diff --git a/cli/protocol_version_consistency_test.go b/common/clicontract/protocol_version_consistency_test.go similarity index 95% rename from cli/protocol_version_consistency_test.go rename to common/clicontract/protocol_version_consistency_test.go index b27672bb78..c4cec55ba5 100644 --- a/cli/protocol_version_consistency_test.go +++ b/common/clicontract/protocol_version_consistency_test.go @@ -10,12 +10,12 @@ import ( ) // unityProtocolConstantPath is the C# source that declares the protocol generation the -// Unity package accepts. It is relative to this package directory (the cli/ module root). +// Unity package accepts. It is relative to this package directory (common/clicontract). const ( - unityProtocolConstantPath = "../Packages/src/Editor/Domain/CliConstants.cs" - unityPackageManifestPath = "../Packages/src/package.json" - unityPackageCliPinPath = "../Packages/src/project-runner-pin.json" - unityProjectCliPinPath = "../.uloop/project-runner-pin.json" + unityProtocolConstantPath = "../../Packages/src/Editor/Domain/CliConstants.cs" + unityPackageManifestPath = "../../Packages/src/package.json" + unityPackageCliPinPath = "../../Packages/src/project-runner-pin.json" + unityProjectCliPinPath = "../../.uloop/project-runner-pin.json" ) var ( diff --git a/cli/internal/clicore/argument_error.go b/common/clicore/argument_error.go similarity index 100% rename from cli/internal/clicore/argument_error.go rename to common/clicore/argument_error.go diff --git a/cli/internal/clicore/busy_status.go b/common/clicore/busy_status.go similarity index 100% rename from cli/internal/clicore/busy_status.go rename to common/clicore/busy_status.go diff --git a/cli/internal/clicore/cli_vibe.go b/common/clicore/cli_vibe.go similarity index 100% rename from cli/internal/clicore/cli_vibe.go rename to common/clicore/cli_vibe.go diff --git a/cli/internal/clicore/cli_vibe_test.go b/common/clicore/cli_vibe_test.go similarity index 100% rename from cli/internal/clicore/cli_vibe_test.go rename to common/clicore/cli_vibe_test.go diff --git a/cli/internal/clicore/command_registry.go b/common/clicore/command_registry.go similarity index 100% rename from cli/internal/clicore/command_registry.go rename to common/clicore/command_registry.go diff --git a/cli/internal/clicore/command_registry_test.go b/common/clicore/command_registry_test.go similarity index 100% rename from cli/internal/clicore/command_registry_test.go rename to common/clicore/command_registry_test.go diff --git a/cli/internal/clicore/error_editor_unresponsive.go b/common/clicore/error_editor_unresponsive.go similarity index 94% rename from cli/internal/clicore/error_editor_unresponsive.go rename to common/clicore/error_editor_unresponsive.go index fb88b54bc9..9dadb6e0b3 100644 --- a/cli/internal/clicore/error_editor_unresponsive.go +++ b/common/clicore/error_editor_unresponsive.go @@ -1,6 +1,6 @@ package clicore -import "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" +import "github.com/hatayama/unity-cli-loop/common/unityipc" func connectionAttemptCause(err *unityipc.ConnectionAttemptError) string { if err == nil { diff --git a/cli/internal/clicore/error_envelope.go b/common/clicore/error_envelope.go similarity index 99% rename from cli/internal/clicore/error_envelope.go rename to common/clicore/error_envelope.go index 0e5fcbf74c..783e02a240 100644 --- a/cli/internal/clicore/error_envelope.go +++ b/common/clicore/error_envelope.go @@ -6,7 +6,7 @@ import ( "io" "net" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/clicore/error_envelope_classification.go b/common/clicore/error_envelope_classification.go similarity index 98% rename from cli/internal/clicore/error_envelope_classification.go rename to common/clicore/error_envelope_classification.go index 9cd480a5a8..f327de9219 100644 --- a/cli/internal/clicore/error_envelope_classification.go +++ b/common/clicore/error_envelope_classification.go @@ -5,7 +5,7 @@ import ( "errors" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func ClassifyError(err error, context ErrorContext) CLIError { diff --git a/cli/internal/clicore/error_envelope_test.go b/common/clicore/error_envelope_test.go similarity index 99% rename from cli/internal/clicore/error_envelope_test.go rename to common/clicore/error_envelope_test.go index 8e927f6b1e..1591d2a09a 100644 --- a/cli/internal/clicore/error_envelope_test.go +++ b/common/clicore/error_envelope_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies the stderr envelope written for a caller-facing error is machine-readable JSON. diff --git a/cli/internal/clicore/focus.go b/common/clicore/focus.go similarity index 100% rename from cli/internal/clicore/focus.go rename to common/clicore/focus.go diff --git a/cli/internal/clicore/focus_test.go b/common/clicore/focus_test.go similarity index 100% rename from cli/internal/clicore/focus_test.go rename to common/clicore/focus_test.go diff --git a/cli/internal/clicore/global_args.go b/common/clicore/global_args.go similarity index 100% rename from cli/internal/clicore/global_args.go rename to common/clicore/global_args.go diff --git a/cli/internal/clicore/output.go b/common/clicore/output.go similarity index 100% rename from cli/internal/clicore/output.go rename to common/clicore/output.go diff --git a/cli/internal/clicore/output_test.go b/common/clicore/output_test.go similarity index 100% rename from cli/internal/clicore/output_test.go rename to common/clicore/output_test.go diff --git a/cli/internal/clicore/runner_version.go b/common/clicore/runner_version.go similarity index 85% rename from cli/internal/clicore/runner_version.go rename to common/clicore/runner_version.go index 34c3128542..cfabbaf042 100644 --- a/cli/internal/clicore/runner_version.go +++ b/common/clicore/runner_version.go @@ -4,7 +4,7 @@ import ( "encoding/json" "io" - clicontract "github.com/hatayama/unity-cli-loop/cli" + clicontract "github.com/hatayama/unity-cli-loop/common/clicontract" ) var ( diff --git a/cli/internal/clicore/server_not_responding.go b/common/clicore/server_not_responding.go similarity index 95% rename from cli/internal/clicore/server_not_responding.go rename to common/clicore/server_not_responding.go index bd460c95e0..e3b68d52ff 100644 --- a/cli/internal/clicore/server_not_responding.go +++ b/common/clicore/server_not_responding.go @@ -3,7 +3,7 @@ package clicore import ( "fmt" - "github.com/hatayama/unity-cli-loop/cli/internal/project" + "github.com/hatayama/unity-cli-loop/common/project" ) // UnityServerNotRespondingError reports that a running Unity Editor accepted a diff --git a/cli/internal/clicore/skill_sources.go b/common/clicore/skill_sources.go similarity index 98% rename from cli/internal/clicore/skill_sources.go rename to common/clicore/skill_sources.go index e3fc0d5440..0b75cf9e79 100644 --- a/cli/internal/clicore/skill_sources.go +++ b/common/clicore/skill_sources.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/skills" + "github.com/hatayama/unity-cli-loop/common/skills" ) const ( diff --git a/cli/internal/clicore/skills_packages.go b/common/clicore/skills_packages.go similarity index 100% rename from cli/internal/clicore/skills_packages.go rename to common/clicore/skills_packages.go diff --git a/cli/internal/clicore/skills_packages_test.go b/common/clicore/skills_packages_test.go similarity index 100% rename from cli/internal/clicore/skills_packages_test.go rename to common/clicore/skills_packages_test.go diff --git a/cli/internal/clicore/spinner.go b/common/clicore/spinner.go similarity index 98% rename from cli/internal/clicore/spinner.go rename to common/clicore/spinner.go index 92b9341043..fa281cb210 100644 --- a/cli/internal/clicore/spinner.go +++ b/common/clicore/spinner.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/clicore/spinner_test.go b/common/clicore/spinner_test.go similarity index 98% rename from cli/internal/clicore/spinner_test.go rename to common/clicore/spinner_test.go index 8f15643c0f..9f515f872c 100644 --- a/cli/internal/clicore/spinner_test.go +++ b/common/clicore/spinner_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func TestSpinnerDoesNotWriteWhenDisabled(t *testing.T) { diff --git a/cli/internal/clicore/string_helpers.go b/common/clicore/string_helpers.go similarity index 100% rename from cli/internal/clicore/string_helpers.go rename to common/clicore/string_helpers.go diff --git a/cli/internal/clicore/tool_catalog.go b/common/clicore/tool_catalog.go similarity index 96% rename from cli/internal/clicore/tool_catalog.go rename to common/clicore/tool_catalog.go index 3623ee3a20..df5e619073 100644 --- a/cli/internal/clicore/tool_catalog.go +++ b/common/clicore/tool_catalog.go @@ -1,7 +1,7 @@ package clicore import ( - "github.com/hatayama/unity-cli-loop/cli/internal/tools" + "github.com/hatayama/unity-cli-loop/common/tools" ) const ( diff --git a/cli/internal/clicore/tool_catalog_test.go b/common/clicore/tool_catalog_test.go similarity index 100% rename from cli/internal/clicore/tool_catalog_test.go rename to common/clicore/tool_catalog_test.go diff --git a/cli/internal/clicore/tool_option_help.go b/common/clicore/tool_option_help.go similarity index 100% rename from cli/internal/clicore/tool_option_help.go rename to common/clicore/tool_option_help.go diff --git a/cli/internal/clicore/tool_options.go b/common/clicore/tool_options.go similarity index 100% rename from cli/internal/clicore/tool_options.go rename to common/clicore/tool_options.go diff --git a/cli/internal/clicore/tool_readiness.go b/common/clicore/tool_readiness.go similarity index 97% rename from cli/internal/clicore/tool_readiness.go rename to common/clicore/tool_readiness.go index eb629735a5..5de5087494 100644 --- a/cli/internal/clicore/tool_readiness.go +++ b/common/clicore/tool_readiness.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/project" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/project" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/clicore/tool_readiness_test.go b/common/clicore/tool_readiness_test.go similarity index 98% rename from cli/internal/clicore/tool_readiness_test.go rename to common/clicore/tool_readiness_test.go index 6514c51d54..8507bb4aba 100644 --- a/cli/internal/clicore/tool_readiness_test.go +++ b/common/clicore/tool_readiness_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies shared readiness waits keep the shorter non-launch timeout. diff --git a/cli/internal/clicore/tool_settings.go b/common/clicore/tool_settings.go similarity index 100% rename from cli/internal/clicore/tool_settings.go rename to common/clicore/tool_settings.go diff --git a/cli/internal/clicore/transport_errors.go b/common/clicore/transport_errors.go similarity index 100% rename from cli/internal/clicore/transport_errors.go rename to common/clicore/transport_errors.go diff --git a/cli/internal/clicore/transport_errors_test.go b/common/clicore/transport_errors_test.go similarity index 100% rename from cli/internal/clicore/transport_errors_test.go rename to common/clicore/transport_errors_test.go diff --git a/cli/go.mod b/common/go.mod similarity index 67% rename from cli/go.mod rename to common/go.mod index 5e44c02074..58d9563f1c 100644 --- a/cli/go.mod +++ b/common/go.mod @@ -1,4 +1,4 @@ -module github.com/hatayama/unity-cli-loop/cli +module github.com/hatayama/unity-cli-loop/common go 1.26 diff --git a/cli/go.sum b/common/go.sum similarity index 100% rename from cli/go.sum rename to common/go.sum diff --git a/cli/internal/project/project.go b/common/project/project.go similarity index 99% rename from cli/internal/project/project.go rename to common/project/project.go index f01546529d..306efda28e 100644 --- a/cli/internal/project/project.go +++ b/common/project/project.go @@ -10,7 +10,7 @@ import ( "sort" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/project/project_test.go b/common/project/project_test.go similarity index 99% rename from cli/internal/project/project_test.go rename to common/project/project_test.go index e84effd201..c8ef4ff086 100644 --- a/cli/internal/project/project_test.go +++ b/common/project/project_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func TestCreateEndpointUsesStableProjectHash(t *testing.T) { diff --git a/cli/internal/skills/source.go b/common/skills/source.go similarity index 100% rename from cli/internal/skills/source.go rename to common/skills/source.go diff --git a/cli/internal/tools/catalog.go b/common/tools/catalog.go similarity index 100% rename from cli/internal/tools/catalog.go rename to common/tools/catalog.go diff --git a/cli/internal/tools/default-tools.json b/common/tools/default-tools.json similarity index 100% rename from cli/internal/tools/default-tools.json rename to common/tools/default-tools.json diff --git a/cli/internal/tools/types.go b/common/tools/types.go similarity index 100% rename from cli/internal/tools/types.go rename to common/tools/types.go diff --git a/cli/internal/unityipc/client.go b/common/unityipc/client.go similarity index 99% rename from cli/internal/unityipc/client.go rename to common/unityipc/client.go index d17c51ef1d..810990adb1 100644 --- a/cli/internal/unityipc/client.go +++ b/common/unityipc/client.go @@ -10,7 +10,7 @@ import ( "os" "time" - clicontract "github.com/hatayama/unity-cli-loop/cli" + clicontract "github.com/hatayama/unity-cli-loop/common/clicontract" ) const ( diff --git a/cli/internal/unityipc/client_heartbeat_test.go b/common/unityipc/client_heartbeat_test.go similarity index 100% rename from cli/internal/unityipc/client_heartbeat_test.go rename to common/unityipc/client_heartbeat_test.go diff --git a/cli/internal/unityipc/client_test.go b/common/unityipc/client_test.go similarity index 99% rename from cli/internal/unityipc/client_test.go rename to common/unityipc/client_test.go index 7953b21090..f55bc9aaeb 100644 --- a/cli/internal/unityipc/client_test.go +++ b/common/unityipc/client_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - clicontract "github.com/hatayama/unity-cli-loop/cli" + clicontract "github.com/hatayama/unity-cli-loop/common/clicontract" ) func TestFormatConnectionAttemptErrorExplainsDialFailureWithoutDisconnectClaim(t *testing.T) { diff --git a/cli/internal/unityipc/client_windows_test.go b/common/unityipc/client_windows_test.go similarity index 100% rename from cli/internal/unityipc/client_windows_test.go rename to common/unityipc/client_windows_test.go diff --git a/cli/internal/unityipc/connection.go b/common/unityipc/connection.go similarity index 100% rename from cli/internal/unityipc/connection.go rename to common/unityipc/connection.go diff --git a/cli/internal/unityipc/dial_unix.go b/common/unityipc/dial_unix.go similarity index 100% rename from cli/internal/unityipc/dial_unix.go rename to common/unityipc/dial_unix.go diff --git a/cli/internal/unityipc/dial_windows.go b/common/unityipc/dial_windows.go similarity index 100% rename from cli/internal/unityipc/dial_windows.go rename to common/unityipc/dial_windows.go diff --git a/cli/internal/unityipc/framing.go b/common/unityipc/framing.go similarity index 100% rename from cli/internal/unityipc/framing.go rename to common/unityipc/framing.go diff --git a/cli/internal/unityipc/framing_test.go b/common/unityipc/framing_test.go similarity index 100% rename from cli/internal/unityipc/framing_test.go rename to common/unityipc/framing_test.go diff --git a/cli/internal/unityipc/outcome.go b/common/unityipc/outcome.go similarity index 100% rename from cli/internal/unityipc/outcome.go rename to common/unityipc/outcome.go diff --git a/cli/internal/version/compare.go b/common/version/compare.go similarity index 100% rename from cli/internal/version/compare.go rename to common/version/compare.go diff --git a/cli/internal/version/compare_test.go b/common/version/compare_test.go similarity index 100% rename from cli/internal/version/compare_test.go rename to common/version/compare_test.go diff --git a/cli/cmd/dispatcher/main.go b/dispatcher/cmd/dispatcher/main.go similarity index 67% rename from cli/cmd/dispatcher/main.go rename to dispatcher/cmd/dispatcher/main.go index d21bf541be..3239b36af9 100644 --- a/cli/cmd/dispatcher/main.go +++ b/dispatcher/cmd/dispatcher/main.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/dispatcher" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/dispatcher" ) func main() { diff --git a/cli/contract.go b/dispatcher/contract.go similarity index 56% rename from cli/contract.go rename to dispatcher/contract.go index ace313b6b8..bcc4e6d9b8 100644 --- a/cli/contract.go +++ b/dispatcher/contract.go @@ -1,4 +1,7 @@ -package clicontract +// Package dispatchercontract holds the dispatcher-side release contract. The runner-side +// contract lives separately in common/clicontract; this package owns the dispatcher release +// contract at the dispatcher module root. +package dispatchercontract import ( "embed" @@ -7,26 +10,14 @@ import ( ) const ( - contractFileName = "contract.json" dispatcherContractFileName = "dispatcher-contract.json" schemaVersion = 1 ) -//go:embed contract.json dispatcher-contract.json +//go:embed dispatcher-contract.json var contractFiles embed.FS -var ( - Current = mustLoadContract() - DispatcherCurrent = mustLoadDispatcherContract() -) - -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"` - ProjectRunnerVersion string `json:"projectRunnerVersion"` -} +var DispatcherCurrent = mustLoadDispatcherContract() type DispatcherContract struct { SchemaVersion int `json:"schemaVersion"` @@ -38,26 +29,6 @@ type DispatcherContract struct { DispatcherContractVersion int `json:"dispatcherContractVersion"` } -func mustLoadContract() Contract { - content, err := contractFiles.ReadFile(contractFileName) - if err != nil { - panic(fmt.Sprintf("CLI contract is not embedded: %v", err)) - } - - var contract Contract - if err := json.Unmarshal(content, &contract); err != nil { - panic(fmt.Sprintf("CLI contract is invalid JSON: %v", err)) - } - if contract.SchemaVersion != schemaVersion { - panic(fmt.Sprintf("CLI contract schema version mismatch: %d", contract.SchemaVersion)) - } - requireString(contract.ProjectRunnerVersion, "projectRunnerVersion") - if contract.ProtocolVersion < 1 { - panic(fmt.Sprintf("CLI contract protocolVersion must be at least 1, got %d", contract.ProtocolVersion)) - } - return contract -} - func mustLoadDispatcherContract() DispatcherContract { content, err := contractFiles.ReadFile(dispatcherContractFileName) if err != nil { diff --git a/cli/contract_test.go b/dispatcher/contract_test.go similarity index 66% rename from cli/contract_test.go rename to dispatcher/contract_test.go index 6f46a1b7f7..49d87de3c2 100644 --- a/cli/contract_test.go +++ b/dispatcher/contract_test.go @@ -1,24 +1,12 @@ -package clicontract +package dispatchercontract import ( "encoding/json" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/version" + "github.com/hatayama/unity-cli-loop/common/version" ) -func TestCliContractProvidesRuntimeVersion(t *testing.T) { - // Verifies that the project runner owns its runtime version from the single CLI module. - requireValidContractVersion(t, "projectRunnerVersion", Current.ProjectRunnerVersion) -} - -func TestCliContractProvidesProtocolVersion(t *testing.T) { - // Verifies that the contract declares which C#-side IPC protocol the binary speaks. - if Current.ProtocolVersion < 1 { - t.Fatalf("protocolVersion must be at least 1, got %d", Current.ProtocolVersion) - } -} - func TestCliContractProvidesDispatcherContractVersion(t *testing.T) { // Verifies that the contract declares which dispatcher capability generation the binary provides. if DispatcherCurrent.DispatcherContractVersion < 1 { @@ -31,13 +19,6 @@ func TestDispatcherContractProvidesRuntimeVersion(t *testing.T) { requireValidContractVersion(t, "dispatcherVersion", DispatcherCurrent.DispatcherVersion) } -func TestCliContractDoesNotDeclareDispatcherReleaseFields(t *testing.T) { - // Verifies release-please CLI version stamping cannot accidentally move dispatcher release metadata. - fields := requireContractFieldMap(t, contractFileName) - requireContractFieldMissing(t, fields, "dispatcherVersion") - requireContractFieldMissing(t, fields, "dispatcherContractVersion") -} - func TestDispatcherContractDoesNotDeclareCliReleaseFields(t *testing.T) { // Verifies dispatcher releases stay independent from project-local CLI release metadata. fields := requireContractFieldMap(t, dispatcherContractFileName) diff --git a/cli/dispatcher-contract.json b/dispatcher/dispatcher-contract.json similarity index 59% rename from cli/dispatcher-contract.json rename to dispatcher/dispatcher-contract.json index e1d8770c0d..f24cca038f 100644 --- a/cli/dispatcher-contract.json +++ b/dispatcher/dispatcher-contract.json @@ -1,5 +1,5 @@ { "schemaVersion": 1, - "dispatcherVersion": "3.0.1-beta.10", + "dispatcherVersion": "3.0.1-beta.11", "dispatcherContractVersion": 1 } diff --git a/dispatcher/go.mod b/dispatcher/go.mod new file mode 100644 index 0000000000..8c4740f600 --- /dev/null +++ b/dispatcher/go.mod @@ -0,0 +1,12 @@ +module github.com/hatayama/unity-cli-loop/dispatcher + +go 1.26 + +require github.com/hatayama/unity-cli-loop/common v0.0.0-00010101000000-000000000000 + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + golang.org/x/sys v0.10.0 // indirect +) + +replace github.com/hatayama/unity-cli-loop/common => ../common diff --git a/dispatcher/go.sum b/dispatcher/go.sum new file mode 100644 index 0000000000..a001052ff3 --- /dev/null +++ b/dispatcher/go.sum @@ -0,0 +1,4 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/cli/internal/dispatcher/bootstrap_platform_errors.go b/dispatcher/internal/dispatcher/bootstrap_platform_errors.go similarity index 97% rename from cli/internal/dispatcher/bootstrap_platform_errors.go rename to dispatcher/internal/dispatcher/bootstrap_platform_errors.go index fbe1ce7685..9ef437d7ef 100644 --- a/cli/internal/dispatcher/bootstrap_platform_errors.go +++ b/dispatcher/internal/dispatcher/bootstrap_platform_errors.go @@ -1,6 +1,6 @@ package dispatcher -import "github.com/hatayama/unity-cli-loop/cli/internal/clicore" +import "github.com/hatayama/unity-cli-loop/common/clicore" // unsupportedPlatformError builds a clicore.CLIError for bootstrap commands (install, update, // uninstall) that fail because the current OS is not supported, matching on the diff --git a/cli/internal/dispatcher/command_help.go b/dispatcher/internal/dispatcher/command_help.go similarity index 96% rename from cli/internal/dispatcher/command_help.go rename to dispatcher/internal/dispatcher/command_help.go index 69a851bbd7..f980baa213 100644 --- a/cli/internal/dispatcher/command_help.go +++ b/dispatcher/internal/dispatcher/command_help.go @@ -4,8 +4,8 @@ import ( "io" "sort" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" ) func tryHandleCommandHelp(command string, startPath string, projectPath string, stdout io.Writer, stderr io.Writer) (bool, int) { diff --git a/cli/internal/dispatcher/completion.go b/dispatcher/internal/dispatcher/completion.go similarity index 99% rename from cli/internal/dispatcher/completion.go rename to dispatcher/internal/dispatcher/completion.go index 47f5d321b5..0a7439fe17 100644 --- a/cli/internal/dispatcher/completion.go +++ b/dispatcher/internal/dispatcher/completion.go @@ -11,7 +11,7 @@ import ( "sort" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) const ( diff --git a/cli/internal/dispatcher/completion_options.go b/dispatcher/internal/dispatcher/completion_options.go similarity index 92% rename from cli/internal/dispatcher/completion_options.go rename to dispatcher/internal/dispatcher/completion_options.go index e0a59b7be9..4e8bda04c9 100644 --- a/cli/internal/dispatcher/completion_options.go +++ b/dispatcher/internal/dispatcher/completion_options.go @@ -1,6 +1,6 @@ package dispatcher -import "github.com/hatayama/unity-cli-loop/cli/internal/clicore" +import "github.com/hatayama/unity-cli-loop/common/clicore" var nativeCommandOptions = map[string][]string{ clicore.CompletionCommand: {installCompletionFlag, shellFlag}, diff --git a/cli/internal/dispatcher/completion_test.go b/dispatcher/internal/dispatcher/completion_test.go similarity index 99% rename from cli/internal/dispatcher/completion_test.go rename to dispatcher/internal/dispatcher/completion_test.go index f0a600af9d..3b36121a68 100644 --- a/cli/internal/dispatcher/completion_test.go +++ b/dispatcher/internal/dispatcher/completion_test.go @@ -9,7 +9,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func TestCompletionListCommandsIncludesNativeCommandsAndDefaultTools(t *testing.T) { diff --git a/cli/internal/dispatcher/dispatcher.go b/dispatcher/internal/dispatcher/dispatcher.go similarity index 100% rename from cli/internal/dispatcher/dispatcher.go rename to dispatcher/internal/dispatcher/dispatcher.go diff --git a/cli/internal/dispatcher/dispatcher_bootstrap_routing_test.go b/dispatcher/internal/dispatcher/dispatcher_bootstrap_routing_test.go similarity index 98% rename from cli/internal/dispatcher/dispatcher_bootstrap_routing_test.go rename to dispatcher/internal/dispatcher/dispatcher_bootstrap_routing_test.go index 685c304d91..8a494aa951 100644 --- a/cli/internal/dispatcher/dispatcher_bootstrap_routing_test.go +++ b/dispatcher/internal/dispatcher/dispatcher_bootstrap_routing_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/update" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/update" ) // These tests lock the dispatcher-process routing of bootstrap commands across diff --git a/cli/internal/dispatcher/dispatcher_download.go b/dispatcher/internal/dispatcher/dispatcher_download.go similarity index 98% rename from cli/internal/dispatcher/dispatcher_download.go rename to dispatcher/internal/dispatcher/dispatcher_download.go index 003336e449..8f1c9b3428 100644 --- a/cli/internal/dispatcher/dispatcher_download.go +++ b/dispatcher/internal/dispatcher/dispatcher_download.go @@ -18,8 +18,8 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - sharedupdate "github.com/hatayama/unity-cli-loop/cli/internal/update" + "github.com/hatayama/unity-cli-loop/common/clicore" + sharedupdate "github.com/hatayama/unity-cli-loop/dispatcher/internal/update" ) var dispatcherHTTPClient = &http.Client{Timeout: 2 * time.Minute} diff --git a/cli/internal/dispatcher/dispatcher_pin.go b/dispatcher/internal/dispatcher/dispatcher_pin.go similarity index 100% rename from cli/internal/dispatcher/dispatcher_pin.go rename to dispatcher/internal/dispatcher/dispatcher_pin.go diff --git a/cli/internal/dispatcher/dispatcher_process.go b/dispatcher/internal/dispatcher/dispatcher_process.go similarity index 97% rename from cli/internal/dispatcher/dispatcher_process.go rename to dispatcher/internal/dispatcher/dispatcher_process.go index 6ae3cd981d..ac43425143 100644 --- a/cli/internal/dispatcher/dispatcher_process.go +++ b/dispatcher/internal/dispatcher/dispatcher_process.go @@ -4,7 +4,7 @@ import ( "context" "io" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // tryHandleProjectScopeHelpRequest serves project-scoped help locally; help diff --git a/cli/internal/dispatcher/dispatcher_test.go b/dispatcher/internal/dispatcher/dispatcher_test.go similarity index 99% rename from cli/internal/dispatcher/dispatcher_test.go rename to dispatcher/internal/dispatcher/dispatcher_test.go index 5a300385e1..67c9eb7dec 100644 --- a/cli/internal/dispatcher/dispatcher_test.go +++ b/dispatcher/internal/dispatcher/dispatcher_test.go @@ -18,7 +18,7 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) type dispatcherArchiveTestEntry struct { diff --git a/cli/internal/dispatcher/dispatcher_update_version.go b/dispatcher/internal/dispatcher/dispatcher_update_version.go similarity index 97% rename from cli/internal/dispatcher/dispatcher_update_version.go rename to dispatcher/internal/dispatcher/dispatcher_update_version.go index 4b9dc0c655..df89375cf0 100644 --- a/cli/internal/dispatcher/dispatcher_update_version.go +++ b/dispatcher/internal/dispatcher/dispatcher_update_version.go @@ -8,7 +8,7 @@ import ( "os" "os/exec" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) var dispatcherReadInstalledVersion = readInstalledDispatcherVersion diff --git a/cli/internal/dispatcher/dispatcher_update_version_test.go b/dispatcher/internal/dispatcher/dispatcher_update_version_test.go similarity index 100% rename from cli/internal/dispatcher/dispatcher_update_version_test.go rename to dispatcher/internal/dispatcher/dispatcher_update_version_test.go diff --git a/cli/internal/dispatcher/dispatcher_version.go b/dispatcher/internal/dispatcher/dispatcher_version.go similarity index 54% rename from cli/internal/dispatcher/dispatcher_version.go rename to dispatcher/internal/dispatcher/dispatcher_version.go index c336636765..042604123e 100644 --- a/cli/internal/dispatcher/dispatcher_version.go +++ b/dispatcher/internal/dispatcher/dispatcher_version.go @@ -4,13 +4,13 @@ import ( "encoding/json" "io" - clicontract "github.com/hatayama/unity-cli-loop/cli" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" + dispatchercontract "github.com/hatayama/unity-cli-loop/dispatcher" ) var ( - dispatcherVersion = clicontract.DispatcherCurrent.DispatcherVersion - dispatcherContractVersion = clicontract.DispatcherCurrent.DispatcherContractVersion + dispatcherVersion = dispatchercontract.DispatcherCurrent.DispatcherVersion + dispatcherContractVersion = dispatchercontract.DispatcherCurrent.DispatcherContractVersion ) func writeDispatcherVersionJSON(stdout io.Writer) { diff --git a/cli/internal/dispatcher/error_envelope_test.go b/dispatcher/internal/dispatcher/error_envelope_test.go similarity index 97% rename from cli/internal/dispatcher/error_envelope_test.go rename to dispatcher/internal/dispatcher/error_envelope_test.go index 448e1b5772..ad01bf4fec 100644 --- a/cli/internal/dispatcher/error_envelope_test.go +++ b/dispatcher/internal/dispatcher/error_envelope_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func TestClassifyLaunchStartupTimeoutError(t *testing.T) { diff --git a/cli/internal/dispatcher/help_test.go b/dispatcher/internal/dispatcher/help_test.go similarity index 99% rename from cli/internal/dispatcher/help_test.go rename to dispatcher/internal/dispatcher/help_test.go index 9fa749d76a..d8cd4f56b6 100644 --- a/cli/internal/dispatcher/help_test.go +++ b/dispatcher/internal/dispatcher/help_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // Tests that launcher help lists native commands and live-tool discovery guidance without baked-in tools. diff --git a/cli/internal/dispatcher/install.go b/dispatcher/internal/dispatcher/install.go similarity index 97% rename from cli/internal/dispatcher/install.go rename to dispatcher/internal/dispatcher/install.go index 964b8a71b4..ed098fa11c 100644 --- a/cli/internal/dispatcher/install.go +++ b/dispatcher/internal/dispatcher/install.go @@ -9,8 +9,8 @@ import ( "runtime" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/install" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/install" ) const ( diff --git a/cli/internal/dispatcher/install_test.go b/dispatcher/internal/dispatcher/install_test.go similarity index 98% rename from cli/internal/dispatcher/install_test.go rename to dispatcher/internal/dispatcher/install_test.go index 5f40992a3f..68fae326fb 100644 --- a/cli/internal/dispatcher/install_test.go +++ b/dispatcher/internal/dispatcher/install_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func TestRunDispatcherInstallHelpDoesNotRequireUnityProject(t *testing.T) { diff --git a/cli/internal/dispatcher/launch.go b/dispatcher/internal/dispatcher/launch.go similarity index 99% rename from cli/internal/dispatcher/launch.go rename to dispatcher/internal/dispatcher/launch.go index f02215f879..335b6ec57f 100644 --- a/cli/internal/dispatcher/launch.go +++ b/dispatcher/internal/dispatcher/launch.go @@ -13,8 +13,8 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" ) const ( diff --git a/cli/internal/dispatcher/launch_focus_log.go b/dispatcher/internal/dispatcher/launch_focus_log.go similarity index 96% rename from cli/internal/dispatcher/launch_focus_log.go rename to dispatcher/internal/dispatcher/launch_focus_log.go index d725587eb4..8d51df3e28 100644 --- a/cli/internal/dispatcher/launch_focus_log.go +++ b/dispatcher/internal/dispatcher/launch_focus_log.go @@ -3,7 +3,7 @@ package dispatcher import ( "context" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func logLaunchExistingFocus(ctx context.Context, projectRoot string, pid int) { diff --git a/cli/internal/dispatcher/launch_options.go b/dispatcher/internal/dispatcher/launch_options.go similarity index 98% rename from cli/internal/dispatcher/launch_options.go rename to dispatcher/internal/dispatcher/launch_options.go index 794cfa54c8..a4d5752d22 100644 --- a/cli/internal/dispatcher/launch_options.go +++ b/dispatcher/internal/dispatcher/launch_options.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func applyLaunchOption(options *launchOptions, args []string, index int) (int, error) { diff --git a/cli/internal/dispatcher/launch_process_exit_timeout_error.go b/dispatcher/internal/dispatcher/launch_process_exit_timeout_error.go similarity index 95% rename from cli/internal/dispatcher/launch_process_exit_timeout_error.go rename to dispatcher/internal/dispatcher/launch_process_exit_timeout_error.go index ef27c32328..ea15089707 100644 --- a/cli/internal/dispatcher/launch_process_exit_timeout_error.go +++ b/dispatcher/internal/dispatcher/launch_process_exit_timeout_error.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) type launchProcessExitTimeoutError struct { diff --git a/cli/internal/dispatcher/launch_process_unix.go b/dispatcher/internal/dispatcher/launch_process_unix.go similarity index 100% rename from cli/internal/dispatcher/launch_process_unix.go rename to dispatcher/internal/dispatcher/launch_process_unix.go diff --git a/cli/internal/dispatcher/launch_process_unix_test.go b/dispatcher/internal/dispatcher/launch_process_unix_test.go similarity index 100% rename from cli/internal/dispatcher/launch_process_unix_test.go rename to dispatcher/internal/dispatcher/launch_process_unix_test.go diff --git a/cli/internal/dispatcher/launch_process_windows.go b/dispatcher/internal/dispatcher/launch_process_windows.go similarity index 100% rename from cli/internal/dispatcher/launch_process_windows.go rename to dispatcher/internal/dispatcher/launch_process_windows.go diff --git a/cli/internal/dispatcher/launch_ready.go b/dispatcher/internal/dispatcher/launch_ready.go similarity index 98% rename from cli/internal/dispatcher/launch_ready.go rename to dispatcher/internal/dispatcher/launch_ready.go index 2b22e23cf1..5104e27931 100644 --- a/cli/internal/dispatcher/launch_ready.go +++ b/dispatcher/internal/dispatcher/launch_ready.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) const ( diff --git a/cli/internal/dispatcher/launch_startup_timeout_error.go b/dispatcher/internal/dispatcher/launch_startup_timeout_error.go similarity index 97% rename from cli/internal/dispatcher/launch_startup_timeout_error.go rename to dispatcher/internal/dispatcher/launch_startup_timeout_error.go index 6caa30db32..a675ee9bb1 100644 --- a/cli/internal/dispatcher/launch_startup_timeout_error.go +++ b/dispatcher/internal/dispatcher/launch_startup_timeout_error.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) type launchStartupTimeoutError struct { diff --git a/cli/internal/dispatcher/launch_test.go b/dispatcher/internal/dispatcher/launch_test.go similarity index 99% rename from cli/internal/dispatcher/launch_test.go rename to dispatcher/internal/dispatcher/launch_test.go index a4e3eecb2e..e639fbfef7 100644 --- a/cli/internal/dispatcher/launch_test.go +++ b/dispatcher/internal/dispatcher/launch_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func TestParseLaunchOptionsSupportsCoreFlags(t *testing.T) { diff --git a/cli/internal/dispatcher/native_install_dir.go b/dispatcher/internal/dispatcher/native_install_dir.go similarity index 100% rename from cli/internal/dispatcher/native_install_dir.go rename to dispatcher/internal/dispatcher/native_install_dir.go diff --git a/cli/internal/dispatcher/run_dispatcher.go b/dispatcher/internal/dispatcher/run_dispatcher.go similarity index 97% rename from cli/internal/dispatcher/run_dispatcher.go rename to dispatcher/internal/dispatcher/run_dispatcher.go index 19479969e2..7ebe007c8d 100644 --- a/cli/internal/dispatcher/run_dispatcher.go +++ b/dispatcher/internal/dispatcher/run_dispatcher.go @@ -12,10 +12,10 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" - sharedupdate "github.com/hatayama/unity-cli-loop/cli/internal/update" - sharedversion "github.com/hatayama/unity-cli-loop/cli/internal/version" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" + sharedversion "github.com/hatayama/unity-cli-loop/common/version" + sharedupdate "github.com/hatayama/unity-cli-loop/dispatcher/internal/update" ) const ( diff --git a/cli/internal/dispatcher/run_help.go b/dispatcher/internal/dispatcher/run_help.go similarity index 97% rename from cli/internal/dispatcher/run_help.go rename to dispatcher/internal/dispatcher/run_help.go index 1159d593b6..41cc5d37a5 100644 --- a/cli/internal/dispatcher/run_help.go +++ b/dispatcher/internal/dispatcher/run_help.go @@ -5,8 +5,8 @@ import ( "os" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" ) const ( diff --git a/cli/internal/dispatcher/skills.go b/dispatcher/internal/dispatcher/skills.go similarity index 99% rename from cli/internal/dispatcher/skills.go rename to dispatcher/internal/dispatcher/skills.go index 1b5d0968f1..0895dc81eb 100644 --- a/cli/internal/dispatcher/skills.go +++ b/dispatcher/internal/dispatcher/skills.go @@ -6,8 +6,8 @@ import ( "path/filepath" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" ) const ( diff --git a/cli/internal/dispatcher/skills_content.go b/dispatcher/internal/dispatcher/skills_content.go similarity index 100% rename from cli/internal/dispatcher/skills_content.go rename to dispatcher/internal/dispatcher/skills_content.go diff --git a/cli/internal/dispatcher/skills_discovery.go b/dispatcher/internal/dispatcher/skills_discovery.go similarity index 98% rename from cli/internal/dispatcher/skills_discovery.go rename to dispatcher/internal/dispatcher/skills_discovery.go index 6b68106ba8..20b11b3dae 100644 --- a/cli/internal/dispatcher/skills_discovery.go +++ b/dispatcher/internal/dispatcher/skills_discovery.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func collectSkillDefinitions(projectRoot string) ([]skillDefinition, error) { diff --git a/cli/internal/dispatcher/skills_dispatch.go b/dispatcher/internal/dispatcher/skills_dispatch.go similarity index 97% rename from cli/internal/dispatcher/skills_dispatch.go rename to dispatcher/internal/dispatcher/skills_dispatch.go index af774024f0..84d69100d4 100644 --- a/cli/internal/dispatcher/skills_dispatch.go +++ b/dispatcher/internal/dispatcher/skills_dispatch.go @@ -3,7 +3,7 @@ package dispatcher import ( "io" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func runV3MigrationSkillsSubcommand( diff --git a/cli/internal/dispatcher/skills_display.go b/dispatcher/internal/dispatcher/skills_display.go similarity index 98% rename from cli/internal/dispatcher/skills_display.go rename to dispatcher/internal/dispatcher/skills_display.go index a7d4af9e64..b843a12e61 100644 --- a/cli/internal/dispatcher/skills_display.go +++ b/dispatcher/internal/dispatcher/skills_display.go @@ -4,7 +4,7 @@ import ( "io" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func defaultSkillTargets() []skillTarget { diff --git a/cli/internal/dispatcher/skills_sync.go b/dispatcher/internal/dispatcher/skills_sync.go similarity index 98% rename from cli/internal/dispatcher/skills_sync.go rename to dispatcher/internal/dispatcher/skills_sync.go index 64f8021ad5..7a2e8ae4e8 100644 --- a/cli/internal/dispatcher/skills_sync.go +++ b/dispatcher/internal/dispatcher/skills_sync.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func syncSkillDirectory(sourceDir string, destinationDir string) error { diff --git a/cli/internal/dispatcher/skills_targets.go b/dispatcher/internal/dispatcher/skills_targets.go similarity index 98% rename from cli/internal/dispatcher/skills_targets.go rename to dispatcher/internal/dispatcher/skills_targets.go index 6d4bbed826..559a038886 100644 --- a/cli/internal/dispatcher/skills_targets.go +++ b/dispatcher/internal/dispatcher/skills_targets.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) var userHomeDir = os.UserHomeDir diff --git a/cli/internal/dispatcher/skills_test.go b/dispatcher/internal/dispatcher/skills_test.go similarity index 100% rename from cli/internal/dispatcher/skills_test.go rename to dispatcher/internal/dispatcher/skills_test.go diff --git a/cli/internal/dispatcher/skills_v3_migration.go b/dispatcher/internal/dispatcher/skills_v3_migration.go similarity index 98% rename from cli/internal/dispatcher/skills_v3_migration.go rename to dispatcher/internal/dispatcher/skills_v3_migration.go index e8faf1ea46..f8e4a8dfb1 100644 --- a/cli/internal/dispatcher/skills_v3_migration.go +++ b/dispatcher/internal/dispatcher/skills_v3_migration.go @@ -5,7 +5,7 @@ import ( "os" "path/filepath" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func runV3MigrationSkillInstall(projectRoot string, skills []skillDefinition, options skillCommandOptions, stdout io.Writer, stderr io.Writer) int { diff --git a/cli/internal/dispatcher/uninstall.go b/dispatcher/internal/dispatcher/uninstall.go similarity index 97% rename from cli/internal/dispatcher/uninstall.go rename to dispatcher/internal/dispatcher/uninstall.go index 1ed63082f4..61538c3443 100644 --- a/cli/internal/dispatcher/uninstall.go +++ b/dispatcher/internal/dispatcher/uninstall.go @@ -9,8 +9,8 @@ import ( "path/filepath" "runtime" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/uninstall" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/uninstall" ) const ( diff --git a/cli/internal/dispatcher/uninstall_test.go b/dispatcher/internal/dispatcher/uninstall_test.go similarity index 100% rename from cli/internal/dispatcher/uninstall_test.go rename to dispatcher/internal/dispatcher/uninstall_test.go diff --git a/cli/internal/dispatcher/update.go b/dispatcher/internal/dispatcher/update.go similarity index 97% rename from cli/internal/dispatcher/update.go rename to dispatcher/internal/dispatcher/update.go index cf65c12fa4..650cec136d 100644 --- a/cli/internal/dispatcher/update.go +++ b/dispatcher/internal/dispatcher/update.go @@ -6,8 +6,8 @@ import ( "os/exec" "runtime" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/update" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/update" ) const ( diff --git a/cli/internal/dispatcher/update_test.go b/dispatcher/internal/dispatcher/update_test.go similarity index 93% rename from cli/internal/dispatcher/update_test.go rename to dispatcher/internal/dispatcher/update_test.go index b63155a2f2..534bb7b571 100644 --- a/cli/internal/dispatcher/update_test.go +++ b/dispatcher/internal/dispatcher/update_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" - clicontract "github.com/hatayama/unity-cli-loop/cli" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/update" + "github.com/hatayama/unity-cli-loop/common/clicore" + dispatchercontract "github.com/hatayama/unity-cli-loop/dispatcher" + "github.com/hatayama/unity-cli-loop/dispatcher/internal/update" ) func TestUpdateCommandForDarwinUsesDirectInstaller(t *testing.T) { @@ -24,8 +24,8 @@ func TestUpdateCommandForDarwinUsesDirectInstaller(t *testing.T) { t.Fatalf("command mismatch: %s", commandName) } joinedArgs := strings.Join(args, " ") - expectedScriptURL := update.ScriptURL(clicontract.DispatcherCurrent.DispatcherVersion, update.PosixScriptName) - expectedReleaseTag := update.UpdateSelectorForVersion(clicontract.DispatcherCurrent.DispatcherVersion) + expectedScriptURL := update.ScriptURL(dispatchercontract.DispatcherCurrent.DispatcherVersion, update.PosixScriptName) + expectedReleaseTag := update.UpdateSelectorForVersion(dispatchercontract.DispatcherCurrent.DispatcherVersion) if !strings.Contains(joinedArgs, expectedScriptURL) { t.Fatalf("installer URL missing: %s", joinedArgs) } @@ -51,8 +51,8 @@ func TestUpdateCommandForWindowsUsesPowerShellInstaller(t *testing.T) { t.Fatalf("command mismatch: %s", commandName) } joinedArgs := strings.Join(args, " ") - expectedScriptURL := update.ScriptURL(clicontract.DispatcherCurrent.DispatcherVersion, update.WindowsScriptName) - expectedReleaseTag := update.UpdateSelectorForVersion(clicontract.DispatcherCurrent.DispatcherVersion) + expectedScriptURL := update.ScriptURL(dispatchercontract.DispatcherCurrent.DispatcherVersion, update.WindowsScriptName) + expectedReleaseTag := update.UpdateSelectorForVersion(dispatchercontract.DispatcherCurrent.DispatcherVersion) if !strings.Contains(joinedArgs, expectedScriptURL) { t.Fatalf("installer URL missing: %s", joinedArgs) } diff --git a/cli/internal/install/command.go b/dispatcher/internal/install/command.go similarity index 100% rename from cli/internal/install/command.go rename to dispatcher/internal/install/command.go diff --git a/cli/internal/install/command_test.go b/dispatcher/internal/install/command_test.go similarity index 100% rename from cli/internal/install/command_test.go rename to dispatcher/internal/install/command_test.go diff --git a/cli/internal/install/posix_install.go b/dispatcher/internal/install/posix_install.go similarity index 100% rename from cli/internal/install/posix_install.go rename to dispatcher/internal/install/posix_install.go diff --git a/cli/internal/uninstall/command.go b/dispatcher/internal/uninstall/command.go similarity index 100% rename from cli/internal/uninstall/command.go rename to dispatcher/internal/uninstall/command.go diff --git a/cli/internal/uninstall/command_test.go b/dispatcher/internal/uninstall/command_test.go similarity index 100% rename from cli/internal/uninstall/command_test.go rename to dispatcher/internal/uninstall/command_test.go diff --git a/cli/internal/update/command.go b/dispatcher/internal/update/command.go similarity index 96% rename from cli/internal/update/command.go rename to dispatcher/internal/update/command.go index 1e4990c225..2ade30adf5 100644 --- a/cli/internal/update/command.go +++ b/dispatcher/internal/update/command.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - sharedversion "github.com/hatayama/unity-cli-loop/cli/internal/version" + sharedversion "github.com/hatayama/unity-cli-loop/common/version" ) const ( diff --git a/cli/internal/update/installer.go b/dispatcher/internal/update/installer.go similarity index 100% rename from cli/internal/update/installer.go rename to dispatcher/internal/update/installer.go diff --git a/cli/internal/update/installer_test.go b/dispatcher/internal/update/installer_test.go similarity index 100% rename from cli/internal/update/installer_test.go rename to dispatcher/internal/update/installer_test.go diff --git a/docs/architecture/native-cli-simplification-plan.md b/docs/architecture/native-cli-simplification-plan.md index 75100f64ee..dbe145b916 100644 --- a/docs/architecture/native-cli-simplification-plan.md +++ b/docs/architecture/native-cli-simplification-plan.md @@ -64,11 +64,11 @@ cli ```bash scripts/check-go-cli.sh -cli/dist/darwin-arm64/uloop compile --wait-for-domain-reload +dist/darwin-arm64/uloop compile --wait-for-domain-reload ``` skill discovery や CLI-only skill source path を動かした場合は、`ToolSkillSynchronizerTests` の targeted EditMode test も実行します。 ```bash -cli/dist/darwin-arm64/uloop run-tests --test-mode EditMode --filter-type regex --filter-value ToolSkillSynchronizerTests +dist/darwin-arm64/uloop run-tests --test-mode EditMode --filter-type regex --filter-value ToolSkillSynchronizerTests ``` diff --git a/go.work b/go.work new file mode 100644 index 0000000000..71583884b6 --- /dev/null +++ b/go.work @@ -0,0 +1,8 @@ +go 1.26 + +use ( + ./project-runner + ./common + ./dispatcher + ./tools/release-automation +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000000..cc90caa9bb --- /dev/null +++ b/go.work.sum @@ -0,0 +1,3 @@ +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= diff --git a/layout-contract.json b/layout-contract.json new file mode 100644 index 0000000000..3ad525967a --- /dev/null +++ b/layout-contract.json @@ -0,0 +1,22 @@ +{ + "schemaVersion": 2, + "layout": { + "modules": { + "common": "common", + "dispatcher": "dispatcher", + "projectRunner": "project-runner", + "releaseAutomation": "tools/release-automation" + }, + "distDir": "dist" + }, + "binaries": { + "dispatcher": { + "unix": "uloop", + "windows": "uloop.exe" + }, + "projectRunner": { + "unix": "uloop-project-runner", + "windows": "uloop-project-runner.exe" + } + } +} diff --git a/cli/CHANGELOG.md b/project-runner/CHANGELOG.md similarity index 100% rename from cli/CHANGELOG.md rename to project-runner/CHANGELOG.md diff --git a/cli/cmd/project-runner/main.go b/project-runner/cmd/project-runner/main.go similarity index 65% rename from cli/cmd/project-runner/main.go rename to project-runner/cmd/project-runner/main.go index 3ebca6633d..b93c2132c9 100644 --- a/cli/cmd/project-runner/main.go +++ b/project-runner/cmd/project-runner/main.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/projectrunner" + "github.com/hatayama/unity-cli-loop/project-runner/internal/projectrunner" ) func main() { diff --git a/project-runner/go.mod b/project-runner/go.mod new file mode 100644 index 0000000000..b6be3b3a2f --- /dev/null +++ b/project-runner/go.mod @@ -0,0 +1,12 @@ +module github.com/hatayama/unity-cli-loop/project-runner + +go 1.26 + +require github.com/hatayama/unity-cli-loop/common v0.0.0-00010101000000-000000000000 + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + golang.org/x/sys v0.10.0 // indirect +) + +replace github.com/hatayama/unity-cli-loop/common => ../common diff --git a/project-runner/go.sum b/project-runner/go.sum new file mode 100644 index 0000000000..a001052ff3 --- /dev/null +++ b/project-runner/go.sum @@ -0,0 +1,4 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/cli/internal/projectrunner/compile_wait.go b/project-runner/internal/projectrunner/compile_wait.go similarity index 99% rename from cli/internal/projectrunner/compile_wait.go rename to project-runner/internal/projectrunner/compile_wait.go index 1a47337169..1517e7d953 100644 --- a/cli/internal/projectrunner/compile_wait.go +++ b/project-runner/internal/projectrunner/compile_wait.go @@ -8,8 +8,8 @@ import ( "fmt" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/compile_wait_test.go b/project-runner/internal/projectrunner/compile_wait_test.go similarity index 99% rename from cli/internal/projectrunner/compile_wait_test.go rename to project-runner/internal/projectrunner/compile_wait_test.go index 1ee85a02c6..5fdb36f694 100644 --- a/cli/internal/projectrunner/compile_wait_test.go +++ b/project-runner/internal/projectrunner/compile_wait_test.go @@ -12,8 +12,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func TestEnsureCompileRequestIDPreservesSafeValue(t *testing.T) { diff --git a/cli/internal/projectrunner/connection_retry.go b/project-runner/internal/projectrunner/connection_retry.go similarity index 98% rename from cli/internal/projectrunner/connection_retry.go rename to project-runner/internal/projectrunner/connection_retry.go index 1ac23e1edf..3bcf3b193d 100644 --- a/cli/internal/projectrunner/connection_retry.go +++ b/project-runner/internal/projectrunner/connection_retry.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) var ( diff --git a/cli/internal/projectrunner/connection_retry_flow.go b/project-runner/internal/projectrunner/connection_retry_flow.go similarity index 97% rename from cli/internal/projectrunner/connection_retry_flow.go rename to project-runner/internal/projectrunner/connection_retry_flow.go index cb22d7c156..27966d13d7 100644 --- a/cli/internal/projectrunner/connection_retry_flow.go +++ b/project-runner/internal/projectrunner/connection_retry_flow.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func newConnectionRetryClient( diff --git a/cli/internal/projectrunner/connection_retry_test.go b/project-runner/internal/projectrunner/connection_retry_test.go similarity index 99% rename from cli/internal/projectrunner/connection_retry_test.go rename to project-runner/internal/projectrunner/connection_retry_test.go index e66f0a0c4e..fa22263505 100644 --- a/cli/internal/projectrunner/connection_retry_test.go +++ b/project-runner/internal/projectrunner/connection_retry_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies transient IPC connection failures focus Unity once and restore focus before reporting server-not-responding. diff --git a/cli/internal/projectrunner/control_play_mode_wait.go b/project-runner/internal/projectrunner/control_play_mode_wait.go similarity index 98% rename from cli/internal/projectrunner/control_play_mode_wait.go rename to project-runner/internal/projectrunner/control_play_mode_wait.go index 5cfdc87f71..357f303016 100644 --- a/cli/internal/projectrunner/control_play_mode_wait.go +++ b/project-runner/internal/projectrunner/control_play_mode_wait.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/control_play_mode_wait_test.go b/project-runner/internal/projectrunner/control_play_mode_wait_test.go similarity index 99% rename from cli/internal/projectrunner/control_play_mode_wait_test.go rename to project-runner/internal/projectrunner/control_play_mode_wait_test.go index 2ff4e4653d..9090c2ef3c 100644 --- a/cli/internal/projectrunner/control_play_mode_wait_test.go +++ b/project-runner/internal/projectrunner/control_play_mode_wait_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies that control-play-mode polls a status-only request before returning stale PlayMode state. diff --git a/cli/internal/projectrunner/debug_timing.go b/project-runner/internal/projectrunner/debug_timing.go similarity index 94% rename from cli/internal/projectrunner/debug_timing.go rename to project-runner/internal/projectrunner/debug_timing.go index ee4896ac9b..ce2da215a5 100644 --- a/cli/internal/projectrunner/debug_timing.go +++ b/project-runner/internal/projectrunner/debug_timing.go @@ -8,8 +8,8 @@ import ( "strings" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/debug_timing_test.go b/project-runner/internal/projectrunner/debug_timing_test.go similarity index 97% rename from cli/internal/projectrunner/debug_timing_test.go rename to project-runner/internal/projectrunner/debug_timing_test.go index 651e65e8f6..79e0b67c89 100644 --- a/cli/internal/projectrunner/debug_timing_test.go +++ b/project-runner/internal/projectrunner/debug_timing_test.go @@ -6,8 +6,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies that timing output stays disabled unless explicitly requested. diff --git a/cli/internal/projectrunner/dynamic_code_file.go b/project-runner/internal/projectrunner/dynamic_code_file.go similarity index 97% rename from cli/internal/projectrunner/dynamic_code_file.go rename to project-runner/internal/projectrunner/dynamic_code_file.go index 92590acfd7..04d491b97b 100644 --- a/cli/internal/projectrunner/dynamic_code_file.go +++ b/project-runner/internal/projectrunner/dynamic_code_file.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) const dynamicCodeCodePropertyName = "Code" diff --git a/cli/internal/projectrunner/dynamic_code_file_test.go b/project-runner/internal/projectrunner/dynamic_code_file_test.go similarity index 98% rename from cli/internal/projectrunner/dynamic_code_file_test.go rename to project-runner/internal/projectrunner/dynamic_code_file_test.go index f47db8e4aa..de5503415a 100644 --- a/cli/internal/projectrunner/dynamic_code_file_test.go +++ b/project-runner/internal/projectrunner/dynamic_code_file_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // Verifies --code-file is extracted from execute-dynamic-code args in both flag forms. diff --git a/cli/internal/projectrunner/dynamic_code_wait.go b/project-runner/internal/projectrunner/dynamic_code_wait.go similarity index 93% rename from cli/internal/projectrunner/dynamic_code_wait.go rename to project-runner/internal/projectrunner/dynamic_code_wait.go index b67c01b18a..297ba32c50 100644 --- a/cli/internal/projectrunner/dynamic_code_wait.go +++ b/project-runner/internal/projectrunner/dynamic_code_wait.go @@ -3,8 +3,8 @@ package projectrunner import ( "encoding/json" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/execution_errors.go b/project-runner/internal/projectrunner/execution_errors.go similarity index 94% rename from cli/internal/projectrunner/execution_errors.go rename to project-runner/internal/projectrunner/execution_errors.go index 19a6183213..905bf81c51 100644 --- a/cli/internal/projectrunner/execution_errors.go +++ b/project-runner/internal/projectrunner/execution_errors.go @@ -3,7 +3,7 @@ package projectrunner import ( "fmt" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func compileWaitTimeoutError(projectRoot string) clicore.CLIError { diff --git a/cli/internal/projectrunner/list_output.go b/project-runner/internal/projectrunner/list_output.go similarity index 98% rename from cli/internal/projectrunner/list_output.go rename to project-runner/internal/projectrunner/list_output.go index c769902b79..7452536d22 100644 --- a/cli/internal/projectrunner/list_output.go +++ b/project-runner/internal/projectrunner/list_output.go @@ -4,7 +4,7 @@ import ( "encoding/json" "sort" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) type listCatalog struct { diff --git a/cli/internal/projectrunner/list_output_test.go b/project-runner/internal/projectrunner/list_output_test.go similarity index 98% rename from cli/internal/projectrunner/list_output_test.go rename to project-runner/internal/projectrunner/list_output_test.go index 1154ef8d13..ca387c2662 100644 --- a/cli/internal/projectrunner/list_output_test.go +++ b/project-runner/internal/projectrunner/list_output_test.go @@ -4,7 +4,7 @@ import ( "encoding/json" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // Tests that list output exposes the actual CLI option names instead of schema property names. diff --git a/cli/internal/projectrunner/native_tool_settings.go b/project-runner/internal/projectrunner/native_tool_settings.go similarity index 91% rename from cli/internal/projectrunner/native_tool_settings.go rename to project-runner/internal/projectrunner/native_tool_settings.go index 6b19d7b99f..69239ca24e 100644 --- a/cli/internal/projectrunner/native_tool_settings.go +++ b/project-runner/internal/projectrunner/native_tool_settings.go @@ -1,6 +1,6 @@ package projectrunner -import "github.com/hatayama/unity-cli-loop/cli/internal/clicore" +import "github.com/hatayama/unity-cli-loop/common/clicore" func isSettingsManagedNativeToolCommand(command string) bool { switch command { diff --git a/cli/internal/projectrunner/pause_point_errors.go b/project-runner/internal/projectrunner/pause_point_errors.go similarity index 99% rename from cli/internal/projectrunner/pause_point_errors.go rename to project-runner/internal/projectrunner/pause_point_errors.go index 2c4d1ac378..c1efc2ca36 100644 --- a/cli/internal/projectrunner/pause_point_errors.go +++ b/project-runner/internal/projectrunner/pause_point_errors.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func pausePointWaitError( diff --git a/cli/internal/projectrunner/pause_point_logs.go b/project-runner/internal/projectrunner/pause_point_logs.go similarity index 98% rename from cli/internal/projectrunner/pause_point_logs.go rename to project-runner/internal/projectrunner/pause_point_logs.go index 9a015bed86..c62b4ac7c9 100644 --- a/cli/internal/projectrunner/pause_point_logs.go +++ b/project-runner/internal/projectrunner/pause_point_logs.go @@ -5,8 +5,8 @@ import ( "encoding/json" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/pause_point_wait.go b/project-runner/internal/projectrunner/pause_point_wait.go similarity index 99% rename from cli/internal/projectrunner/pause_point_wait.go rename to project-runner/internal/projectrunner/pause_point_wait.go index 740ffb509c..5e24dc08e8 100644 --- a/cli/internal/projectrunner/pause_point_wait.go +++ b/project-runner/internal/projectrunner/pause_point_wait.go @@ -8,8 +8,8 @@ import ( "strconv" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) const ( diff --git a/cli/internal/projectrunner/pause_point_wait_test.go b/project-runner/internal/projectrunner/pause_point_wait_test.go similarity index 99% rename from cli/internal/projectrunner/pause_point_wait_test.go rename to project-runner/internal/projectrunner/pause_point_wait_test.go index 02031c4666..4c5a051482 100644 --- a/cli/internal/projectrunner/pause_point_wait_test.go +++ b/project-runner/internal/projectrunner/pause_point_wait_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) // Verifies wait-for-pause-point polls until Unity reports the marker hit. diff --git a/cli/internal/projectrunner/projectrunner.go b/project-runner/internal/projectrunner/projectrunner.go similarity index 100% rename from cli/internal/projectrunner/projectrunner.go rename to project-runner/internal/projectrunner/projectrunner.go diff --git a/cli/internal/projectrunner/run.go b/project-runner/internal/projectrunner/run.go similarity index 98% rename from cli/internal/projectrunner/run.go rename to project-runner/internal/projectrunner/run.go index a52d9e7204..336ead5b72 100644 --- a/cli/internal/projectrunner/run.go +++ b/project-runner/internal/projectrunner/run.go @@ -9,9 +9,9 @@ import ( "path/filepath" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func RunProjectLocal(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) int { diff --git a/cli/internal/projectrunner/run_test.go b/project-runner/internal/projectrunner/run_test.go similarity index 96% rename from cli/internal/projectrunner/run_test.go rename to project-runner/internal/projectrunner/run_test.go index e4991951ce..b4f9811149 100644 --- a/cli/internal/projectrunner/run_test.go +++ b/project-runner/internal/projectrunner/run_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func TestRunProjectLocalVersionJSONIncludesProtocolVersion(t *testing.T) { diff --git a/cli/internal/projectrunner/runner_commands.go b/project-runner/internal/projectrunner/runner_commands.go similarity index 95% rename from cli/internal/projectrunner/runner_commands.go rename to project-runner/internal/projectrunner/runner_commands.go index 728bc8d3ba..e5066ab4f8 100644 --- a/cli/internal/projectrunner/runner_commands.go +++ b/project-runner/internal/projectrunner/runner_commands.go @@ -4,9 +4,9 @@ import ( "context" "io" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" - "github.com/hatayama/unity-cli-loop/cli/internal/project" - "github.com/hatayama/unity-cli-loop/cli/internal/unityipc" + "github.com/hatayama/unity-cli-loop/common/clicore" + "github.com/hatayama/unity-cli-loop/common/project" + "github.com/hatayama/unity-cli-loop/common/unityipc" ) func runResolvedProjectCommand( diff --git a/cli/internal/projectrunner/runner_usage.go b/project-runner/internal/projectrunner/runner_usage.go similarity index 96% rename from cli/internal/projectrunner/runner_usage.go rename to project-runner/internal/projectrunner/runner_usage.go index db48f09693..259597d1b8 100644 --- a/cli/internal/projectrunner/runner_usage.go +++ b/project-runner/internal/projectrunner/runner_usage.go @@ -3,7 +3,7 @@ package projectrunner import ( "io" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // tryHandleRunnerInfoRequest answers the project runner's own identity diff --git a/cli/internal/projectrunner/runner_usage_test.go b/project-runner/internal/projectrunner/runner_usage_test.go similarity index 100% rename from cli/internal/projectrunner/runner_usage_test.go rename to project-runner/internal/projectrunner/runner_usage_test.go diff --git a/cli/internal/projectrunner/tool_params.go b/project-runner/internal/projectrunner/tool_params.go similarity index 98% rename from cli/internal/projectrunner/tool_params.go rename to project-runner/internal/projectrunner/tool_params.go index 1eef170090..a0ef3eaa87 100644 --- a/cli/internal/projectrunner/tool_params.go +++ b/project-runner/internal/projectrunner/tool_params.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) func buildToolParams(args []string, tool clicore.ToolDefinition) (map[string]any, string, error) { diff --git a/cli/internal/projectrunner/tools_test.go b/project-runner/internal/projectrunner/tools_test.go similarity index 99% rename from cli/internal/projectrunner/tools_test.go rename to project-runner/internal/projectrunner/tools_test.go index 39e394bd5a..2a77882a31 100644 --- a/cli/internal/projectrunner/tools_test.go +++ b/project-runner/internal/projectrunner/tools_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "testing" - "github.com/hatayama/unity-cli-loop/cli/internal/clicore" + "github.com/hatayama/unity-cli-loop/common/clicore" ) // Tests that tool arguments are converted according to their schema types. diff --git a/release-please-config.json b/release-please-config.json index 0938eba795..8456d30d7a 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -28,7 +28,7 @@ } ] }, - "cli": { + "project-runner": { "component": "uloop-project-runner", "release-type": "go", "versioning": "prerelease", @@ -36,22 +36,16 @@ "prerelease-type": "beta", "include-v-in-tag": true, "include-component-in-tag": true, - "exclude-paths": [ - "cli/cmd/dispatch-release-please-pr-checks", - "cli/cmd/dispatcher", - "cli/internal/automation", - "cli/internal/dispatcher" - ], "changelog-path": "CHANGELOG.md", "extra-files": [ { "type": "json", - "path": "internal/tools/default-tools.json", + "path": "/common/tools/default-tools.json", "jsonpath": "$.version" }, { "type": "json", - "path": "contract.json", + "path": "/common/clicontract/contract.json", "jsonpath": "$.projectRunnerVersion" }, { diff --git a/scripts/build-go-cli.sh b/scripts/build-go-cli.sh index b4530e9293..a1686a3c77 100755 --- a/scripts/build-go-cli.sh +++ b/scripts/build-go-cli.sh @@ -2,7 +2,8 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) -CLI_DIR="$ROOT_DIR/cli" +RUNNER_DIR="$ROOT_DIR/project-runner" +DISPATCHER_DIR="$ROOT_DIR/dispatcher" . "$ROOT_DIR/scripts/go-cli-toolchain.sh" require_go_cli_toolchain "$ROOT_DIR" @@ -19,7 +20,7 @@ build_binary() { extension=".exe" fi - output_dir="$CLI_DIR/dist/$os-$arch" + output_dir="$ROOT_DIR/dist/$os-$arch" mkdir -p "$output_dir" ( @@ -28,9 +29,9 @@ build_binary() { ) } -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 +build_binary darwin arm64 uloop "$DISPATCHER_DIR" ./cmd/dispatcher +build_binary darwin arm64 uloop-project-runner "$RUNNER_DIR" ./cmd/project-runner +build_binary darwin amd64 uloop "$DISPATCHER_DIR" ./cmd/dispatcher +build_binary darwin amd64 uloop-project-runner "$RUNNER_DIR" ./cmd/project-runner +build_binary windows amd64 uloop "$DISPATCHER_DIR" ./cmd/dispatcher +build_binary windows amd64 uloop-project-runner "$RUNNER_DIR" ./cmd/project-runner diff --git a/scripts/check-build-link-go-cli.sh b/scripts/check-build-link-go-cli.sh index 450456bb9f..4eb3cb6993 100755 --- a/scripts/check-build-link-go-cli.sh +++ b/scripts/check-build-link-go-cli.sh @@ -77,13 +77,13 @@ arch=$(uname -m) case "$os:$arch" in Darwin:arm64 | Darwin:aarch64) - cli_path="$ROOT_DIR/cli/dist/darwin-arm64/uloop" + cli_path="$ROOT_DIR/dist/darwin-arm64/uloop" ;; Darwin:x86_64 | Darwin:amd64) - cli_path="$ROOT_DIR/cli/dist/darwin-amd64/uloop" + cli_path="$ROOT_DIR/dist/darwin-amd64/uloop" ;; MINGW*:x86_64 | MINGW*:amd64 | MSYS*:x86_64 | MSYS*:amd64 | CYGWIN*:x86_64 | CYGWIN*:amd64 | Windows_NT:x86_64 | Windows_NT:amd64) - cli_path="$ROOT_DIR/cli/dist/windows-amd64/uloop.exe" + cli_path="$ROOT_DIR/dist/windows-amd64/uloop.exe" global_command_name="uloop.exe" ;; esac diff --git a/scripts/check-code-complexity.sh b/scripts/check-code-complexity.sh index e2a6a6c6b0..87266219df 100755 --- a/scripts/check-code-complexity.sh +++ b/scripts/check-code-complexity.sh @@ -4,7 +4,7 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) MAX_COMPLEXITY=${CODE_COMPLEXITY_MAX_COMPLEXITY:-15} FAIL_ON_EXCEEDED=$(printf '%s' "${CODE_COMPLEXITY_FAIL_ON_EXCEEDED:-false}" | tr '[:upper:]' '[:lower:]') -GO_CONFIG="$ROOT_DIR/cli/.golangci-complexity.yml" +GO_CONFIG="$ROOT_DIR/.golangci-complexity.yml" TEMP_GO_CONFIG= GO_STATUS=0 @@ -19,7 +19,7 @@ cleanup() { trap cleanup 0 1 2 15 if [ "$MAX_COMPLEXITY" != "15" ]; then - TEMP_GO_CONFIG="$ROOT_DIR/cli/.golangci-complexity.$$.yml" + TEMP_GO_CONFIG="$ROOT_DIR/.golangci-complexity.$$.yml" awk -v max_complexity="$MAX_COMPLEXITY" ' $1 == "max-complexity:" { print " max-complexity: " max_complexity @@ -33,10 +33,19 @@ if [ "$MAX_COMPLEXITY" != "15" ]; then fi echo "=== Go complexity (cyclop, max ${MAX_COMPLEXITY}) ===" -( - cd "$ROOT_DIR/cli" - golangci-lint run --config "$GO_CONFIG" ./... -) || GO_STATUS=$? +for module_dir in common dispatcher project-runner tools/release-automation; do + ( + cd "$ROOT_DIR/$module_dir" + golangci-lint run --config "$GO_CONFIG" ./... + ) || { + module_status=$? + # Keep a fatal status (anything but the findings exit code 1) from being + # masked by a later module that only reports findings. + if [ "$GO_STATUS" -eq 0 ] || [ "$module_status" -ne 1 ]; then + GO_STATUS=$module_status + fi + } +done echo "" echo "=== C# complexity (CA1502, max ${MAX_COMPLEXITY}) ===" diff --git a/scripts/check-go-cli-source.sh b/scripts/check-go-cli-source.sh index d1cbef269c..6058db960d 100755 --- a/scripts/check-go-cli-source.sh +++ b/scripts/check-go-cli-source.sh @@ -2,7 +2,7 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) -CLI_DIR="$ROOT_DIR/cli" +RUNNER_DIR="$ROOT_DIR/project-runner" . "$ROOT_DIR/scripts/go-cli-toolchain.sh" require_go_cli_toolchain "$ROOT_DIR" @@ -28,11 +28,14 @@ run_module_checks() { print "./" substr($0, length(root) + 2) } ') - golangci-lint fmt --config "$CLI_DIR/.golangci.yml" --diff + golangci-lint fmt --config "$ROOT_DIR/.golangci.yml" --diff go vet $packages - golangci-lint run --config "$CLI_DIR/.golangci.yml" $packages + golangci-lint run --config "$ROOT_DIR/.golangci.yml" $packages go test $packages ) } -run_module_checks "$CLI_DIR" +run_module_checks "$ROOT_DIR/common" +run_module_checks "$ROOT_DIR/dispatcher" +run_module_checks "$ROOT_DIR/tools/release-automation" +run_module_checks "$RUNNER_DIR" diff --git a/scripts/go-cli-toolchain.sh b/scripts/go-cli-toolchain.sh index 8cd5b9032c..5f1ef7bfa8 100644 --- a/scripts/go-cli-toolchain.sh +++ b/scripts/go-cli-toolchain.sh @@ -3,7 +3,7 @@ set -eu require_go_cli_toolchain() { root_dir="$1" - version_file="$root_dir/cli/.go-version" + version_file="$root_dir/.go-version" if [ ! -f "$version_file" ]; then echo "Go CLI toolchain version file is missing: $version_file" >&2 diff --git a/scripts/package-dispatcher.sh b/scripts/package-dispatcher.sh index f45ae14bc8..12399db392 100755 --- a/scripts/package-dispatcher.sh +++ b/scripts/package-dispatcher.sh @@ -2,9 +2,8 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) -CLI_DIR="$ROOT_DIR/cli" -DIST_DIR="$CLI_DIR/dist" -RELEASE_DIR="$CLI_DIR/dist/dispatcher-release" +DIST_DIR="$ROOT_DIR/dist" +RELEASE_DIR="$DIST_DIR/dispatcher-release" rm -rf "$RELEASE_DIR" mkdir -p "$RELEASE_DIR" diff --git a/scripts/package-go-cli.sh b/scripts/package-go-cli.sh index d9af1d411e..0016b6af64 100755 --- a/scripts/package-go-cli.sh +++ b/scripts/package-go-cli.sh @@ -2,8 +2,7 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) -CLI_DIR="$ROOT_DIR/cli" -DIST_DIR="$CLI_DIR/dist" +DIST_DIR="$ROOT_DIR/dist" RELEASE_DIR="$DIST_DIR/release" rm -rf "$RELEASE_DIR" diff --git a/scripts/refresh-neighbor-game-skills-macos.sh b/scripts/refresh-neighbor-game-skills-macos.sh index e84b8e0f54..222f8039f6 100644 --- a/scripts/refresh-neighbor-game-skills-macos.sh +++ b/scripts/refresh-neighbor-game-skills-macos.sh @@ -199,7 +199,7 @@ parse_args() { has_local_uloop() { candidate=$1 - [ -x "$candidate/cli/dist/darwin-arm64/uloop" ] + [ -x "$candidate/dist/darwin-arm64/uloop" ] } resolve_uloop_root() { @@ -404,7 +404,7 @@ resolve_uloop_root discover_projects assert_project_count -uloop_bin="$uloop_root/cli/dist/darwin-arm64/uloop" +uloop_bin="$uloop_root/dist/darwin-arm64/uloop" command -v launch-unity >/dev/null 2>&1 || fail "launch-unity is not available on PATH" log "Using uloop: $uloop_bin" diff --git a/scripts/refresh-neighbor-game-skills-windows.sh b/scripts/refresh-neighbor-game-skills-windows.sh index 7725bc7a91..f8d00b0ef6 100644 --- a/scripts/refresh-neighbor-game-skills-windows.sh +++ b/scripts/refresh-neighbor-game-skills-windows.sh @@ -239,10 +239,10 @@ uloop_binary_for_root() { if [ "$(uname -m)" = "x86_64" ] || [ "$(uname -m)" = "amd64" ]; then arch=amd64 fi - printf '%s\n' "$candidate/cli/dist/darwin-$arch/uloop" + printf '%s\n' "$candidate/dist/darwin-$arch/uloop" ;; windows|wsl) - printf '%s\n' "$candidate/cli/dist/windows-amd64/uloop.exe" + printf '%s\n' "$candidate/dist/windows-amd64/uloop.exe" ;; *) fail "unsupported host OS for local uloop binary: $host_os" diff --git a/scripts/resolve-dispatcher-release-target.sh b/scripts/resolve-dispatcher-release-target.sh index 8fef4c161a..7c7911290c 100755 --- a/scripts/resolve-dispatcher-release-target.sh +++ b/scripts/resolve-dispatcher-release-target.sh @@ -123,9 +123,9 @@ dispatcher_release_is_prerelease() { [ "$ref_name" = "v3-beta" ] } -VERSION=$(jq -r '.dispatcherVersion' cli/dispatcher-contract.json) +VERSION=$(jq -r '.dispatcherVersion' dispatcher/dispatcher-contract.json) if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then - echo "Could not resolve dispatcherVersion from cli/dispatcher-contract.json." >&2 + echo "Could not resolve dispatcherVersion from dispatcher/dispatcher-contract.json." >&2 exit 1 fi diff --git a/scripts/resolve-native-cli-release-target.sh b/scripts/resolve-native-cli-release-target.sh index 98b3a326d9..36f1a9ebae 100755 --- a/scripts/resolve-native-cli-release-target.sh +++ b/scripts/resolve-native-cli-release-target.sh @@ -11,15 +11,15 @@ INPUT_RELEASE_TAG=${INPUT_RELEASE_TAG:-} INPUT_DRY_RUN=${INPUT_DRY_RUN:-false} RELEASE_DATA="" CLI_RELEASE_INPUT_PATHS=" -cli/.go-version -cli/layout-contract.json -cli/contract.go -cli/contract.json -cli/contract_test.go -cli/cmd -cli/go.mod -cli/go.sum -cli/internal +.go-version +go.work +go.work.sum +layout-contract.json +common +project-runner/cmd +project-runner/go.mod +project-runner/go.sum +project-runner/internal scripts/build-go-cli.sh scripts/go-cli-toolchain.sh scripts/package-go-cli.sh @@ -149,10 +149,10 @@ cli_release_inputs_changed() { release_commit_updates_cli_version() { commit_sha=$1 version=$2 - expected_manifest_entry="\"cli\": \"$version\"" + expected_manifest_entry="\"project-runner\": \"$version\"" expected_changelog_heading="## [$version]" - commit_diff=$(git show --format= "$commit_sha" -- .release-please-manifest.json cli/CHANGELOG.md 2>/dev/null || true) + commit_diff=$(git show --format= "$commit_sha" -- .release-please-manifest.json project-runner/CHANGELOG.md 2>/dev/null || true) printf '%s\n' "$commit_diff" \ | awk -v manifest_entry="$expected_manifest_entry" -v changelog_heading="$expected_changelog_heading" ' substr($0, 1, 1) == "+" && (index($0, manifest_entry) > 0 || index($0, changelog_heading) > 0) { @@ -251,7 +251,7 @@ release_commit_sha_for_version() { done } -VERSION=$(jq -r '.["cli"]' .release-please-manifest.json) +VERSION=$(jq -r '.["project-runner"]' .release-please-manifest.json) if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then echo "Could not resolve project runner release version from .release-please-manifest.json." >&2 exit 1 diff --git a/scripts/run-posix-e2e.sh b/scripts/run-posix-e2e.sh index b684addce7..86e5c2aaeb 100755 --- a/scripts/run-posix-e2e.sh +++ b/scripts/run-posix-e2e.sh @@ -93,13 +93,13 @@ default_uloop_path() { Darwin) machine=$(uname -m) if [ "$machine" = "arm64" ] || [ "$machine" = "aarch64" ]; then - printf '%s\n' "$ROOT_DIR/cli/dist/darwin-arm64/uloop" + printf '%s\n' "$ROOT_DIR/dist/darwin-arm64/uloop" else - printf '%s\n' "$ROOT_DIR/cli/dist/darwin-amd64/uloop" + printf '%s\n' "$ROOT_DIR/dist/darwin-amd64/uloop" fi ;; MINGW*|MSYS*|CYGWIN*) - printf '%s\n' "$ROOT_DIR/cli/dist/windows-amd64/uloop.exe" + printf '%s\n' "$ROOT_DIR/dist/windows-amd64/uloop.exe" ;; *) printf '%s\n' "" diff --git a/scripts/smoke-cli-recovery-readiness.go b/scripts/smoke-cli-recovery-readiness.go index 47efd51901..d805fb4124 100644 --- a/scripts/smoke-cli-recovery-readiness.go +++ b/scripts/smoke-cli-recovery-readiness.go @@ -128,9 +128,9 @@ func defaultUloopPath() (string, error) { if runtime.GOARCH == "arm64" { arch = "darwin-arm64" } - return filepath.Join(repoRoot, "cli", "dist", arch, "uloop"), nil + return filepath.Join(repoRoot, "dist", arch, "uloop"), nil case "windows": - return filepath.Join(repoRoot, "cli", "dist", "windows-amd64", "uloop.exe"), nil + return filepath.Join(repoRoot, "dist", "windows-amd64", "uloop.exe"), nil default: return "", nil } diff --git a/scripts/sync-release-please-package-releases.sh b/scripts/sync-release-please-package-releases.sh index b23764bcc7..53005dc320 100755 --- a/scripts/sync-release-please-package-releases.sh +++ b/scripts/sync-release-please-package-releases.sh @@ -2,9 +2,12 @@ set -eu ROOT_DIR=${ULOOP_REPO_ROOT:-$(CDPATH= cd "$(dirname "$0")/.." && pwd)} +# SCRIPT_DIR must resolve from $0, not ROOT_DIR: ULOOP_REPO_ROOT may point at a +# repository that does not contain the helper scripts this script calls. +SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) CONFIG="$ROOT_DIR/release-please-config.json" MANIFEST="$ROOT_DIR/.release-please-manifest.json" -CLI_PACKAGE_PATH="cli" +CLI_PACKAGE_PATH="project-runner" UNITY_PACKAGE_CLI_PIN_FILE="Packages/src/project-runner-pin.json" REPO_FULL_NAME=${GITHUB_REPOSITORY:-hatayama/unity-cli-loop} TMP_DIR=$(mktemp -d) @@ -69,8 +72,15 @@ release_commit_sha_for_package() { version=$2 changelog_path=$3 - git log --format=%H HEAD | - while IFS= read -r commit_sha; do + # Only release-please release commits may qualify. Content matching alone is + # not enough: a commit that moves a package changelog re-adds every changelog + # line in the pathspec-limited diff (the rename source is outside the + # pathspec), so it would otherwise impersonate the release commit. + git log --format='%H%x09%s' HEAD | + while IFS=' ' read -r commit_sha commit_subject; do + if ! "$SCRIPT_DIR/is-release-please-release-commit.sh" "$commit_subject"; then + continue + fi if release_commit_updates_package_version "$commit_sha" "$package_path" "$version" "$changelog_path"; then printf '%s\n' "$commit_sha" break @@ -441,7 +451,7 @@ verify_minimum_cli_release_protocol() { release_ref=$1 ( - cd "$ROOT_DIR/cli" + cd "$ROOT_DIR/tools/release-automation" go run ./cmd/check-protocol-minimum-version --verify-release --ref "$release_ref" ) } diff --git a/scripts/test-go-cli-toolchain.sh b/scripts/test-go-cli-toolchain.sh index 566aa84462..08aed92a9b 100755 --- a/scripts/test-go-cli-toolchain.sh +++ b/scripts/test-go-cli-toolchain.sh @@ -38,8 +38,8 @@ EOF_GO create_repo() { version=$1 repo_dir="$TMP_DIR/repo-$version" - mkdir -p "$repo_dir/cli" - printf '%s\n' "$version" > "$repo_dir/cli/.go-version" + mkdir -p "$repo_dir" + printf '%s\n' "$version" > "$repo_dir/.go-version" printf '%s\n' "$repo_dir" } diff --git a/scripts/test-native-cli-publish-workflow.sh b/scripts/test-native-cli-publish-workflow.sh index 6a71190e73..31e53632d7 100755 --- a/scripts/test-native-cli-publish-workflow.sh +++ b/scripts/test-native-cli-publish-workflow.sh @@ -48,7 +48,7 @@ test_release_assets_are_attested() { assert_contains "$WORKFLOW" " - name: Attest native CLI release assets" assert_contains "$WORKFLOW" " if: steps.release.outputs.publish == 'true' && steps.release.outputs.dry_run != 'true'" assert_contains "$WORKFLOW" " uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26" - assert_contains "$WORKFLOW" " subject-path: cli/dist/release/*" + assert_contains "$WORKFLOW" " subject-path: dist/release/*" assert_before "$WORKFLOW" " - name: Verify packaged release assets" " - name: Attest native CLI release assets" assert_before "$WORKFLOW" " - name: Attest native CLI release assets" " - name: Upload native CLI assets" } @@ -56,7 +56,7 @@ test_release_assets_are_attested() { test_release_asset_attestations_are_verified() { assert_contains "$WORKFLOW" " - name: Verify native CLI asset attestations" assert_contains "$WORKFLOW" ' SIGNER_WORKFLOW: ${{ github.repository }}/.github/workflows/native-cli-publish.yml' - assert_contains "$WORKFLOW" ' for asset_path in cli/dist/release/*; do' + assert_contains "$WORKFLOW" ' for asset_path in dist/release/*; do' assert_contains "$WORKFLOW" ' gh attestation verify "${asset_path}" \' assert_contains "$WORKFLOW" ' --repo "${GITHUB_REPOSITORY}" \' assert_contains "$WORKFLOW" ' --signer-workflow "${SIGNER_WORKFLOW}"' diff --git a/scripts/test-release-please-config.sh b/scripts/test-release-please-config.sh index 295a5ea010..ccd00d2c08 100755 --- a/scripts/test-release-please-config.sh +++ b/scripts/test-release-please-config.sh @@ -119,7 +119,7 @@ assert_json_value '.["pull-request-header"] | contains("Dispatcher releases")' ' # Package roots are the release boundary: the repository root must not be a # package, otherwise unrelated root-level changes leak into unity-package releases. -assert_json_value '.packages | keys | sort | join(",")' 'Packages/src,cli' +assert_json_value '.packages | keys | sort | join(",")' 'Packages/src,project-runner' assert_json_value '.packages["Packages/src"].["changelog-path"]' 'CHANGELOG.md' assert_json_value '.packages["Packages/src"].component' 'unity-package' @@ -133,22 +133,20 @@ assert_json_value '.packages["Packages/src"].["extra-files"][1].jsonpath' '$.pac assert_json_value '.packages["Packages/src"].["extra-files"][2].path' '/.uloop/project-runner-pin.json' assert_json_value '.packages["Packages/src"].["extra-files"][2].jsonpath' '$.packageVersion' -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' -# Dispatcher-owned paths must stay out of uloop-project-runner releases so a -# dispatcher-only change cannot regenerate a project-runner release PR. -# cli/dispatcher-contract.json cannot be listed here: release-please matches -# exclude-paths as directory prefixes only, so a plain-file entry is a silent -# no-op (see the directory check below). -assert_json_value '.packages["cli"].["exclude-paths"] | sort | join(",")' 'cli/cmd/dispatch-release-please-pr-checks,cli/cmd/dispatcher,cli/internal/automation,cli/internal/dispatcher' -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/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_json_value '.packages["project-runner"].component' 'uloop-project-runner' +assert_json_value '.packages["project-runner"].["include-component-in-tag"]' 'true' +assert_json_value '.packages["project-runner"].["changelog-path"]' 'CHANGELOG.md' +# The release automation cmd and internal packages that used to live under cli +# moved to tools/release-automation, so there is nothing left under project-runner +# to exclude from uloop-project-runner releases. +assert_json_value '.packages["project-runner"] | has("exclude-paths")' 'false' +assert_json_value '.packages["project-runner"].["extra-files"] | length' '4' +assert_json_value '.packages["project-runner"].["extra-files"][0].path' '/common/tools/default-tools.json' +assert_json_value '.packages["project-runner"].["extra-files"][1].path' '/common/clicontract/contract.json' +assert_json_value '.packages["project-runner"].["extra-files"][2].path' '/Packages/src/project-runner-pin.json' +assert_json_value '.packages["project-runner"].["extra-files"][2].jsonpath' '$.projectRunnerVersion' +assert_json_value '.packages["project-runner"].["extra-files"][3].path' '/.uloop/project-runner-pin.json' +assert_json_value '.packages["project-runner"].["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'" @@ -157,7 +155,7 @@ assert_file_contains "$RELEASE_WORKFLOW" ' checks: read' assert_file_contains "$RELEASE_WORKFLOW" ' - name: Setup Go for package release sync' assert_file_contains "$RELEASE_WORKFLOW" ' - name: Setup Go for release PR automation' assert_file_contains "$RELEASE_WORKFLOW" ' - name: Dispatch release PR checks' -assert_file_contains "$RELEASE_WORKFLOW" ' working-directory: cli' +assert_file_contains "$RELEASE_WORKFLOW" ' working-directory: tools/release-automation' assert_file_contains "$RELEASE_WORKFLOW" ' run: go run ./cmd/dispatch-release-please-pr-checks' assert_step_contains "$RELEASE_WORKFLOW" ' - name: Setup Go for release PR automation' " if: steps.target.outputs.branch == 'v3-beta' && steps.release_commit.outputs.skip != 'true' && steps.package_release_sync.outputs.ready != 'false' && steps.release.outputs.prs_created == 'true'" assert_step_contains "$RELEASE_WORKFLOW" ' - name: Dispatch release PR checks' " if: steps.target.outputs.branch == 'v3-beta' && steps.release_commit.outputs.skip != 'true' && steps.package_release_sync.outputs.ready != 'false' && steps.release.outputs.prs_created == 'true'" @@ -165,7 +163,7 @@ assert_file_order "$RELEASE_WORKFLOW" ' - name: Setup Go for package releas assert_file_order "$RELEASE_WORKFLOW" ' - name: Setup Go for release PR automation' ' - name: Dispatch release PR checks' assert_manifest_semver '.["Packages/src"]' -assert_manifest_semver '.["cli"]' +assert_manifest_semver '.["project-runner"]' # The old repository-root package key must not linger after the boundary move. # has(".") detects the key even when its value is null, which a plain diff --git a/scripts/test-resolve-dispatcher-release-target.sh b/scripts/test-resolve-dispatcher-release-target.sh index 1ba398735e..00e0992959 100755 --- a/scripts/test-resolve-dispatcher-release-target.sh +++ b/scripts/test-resolve-dispatcher-release-target.sh @@ -74,13 +74,13 @@ MOCK_GH write_contract() { version=$1 - mkdir -p cli + mkdir -p dispatcher printf '%s\n' \ '{' \ ' "schemaVersion": 1,' \ ' "dispatcherVersion": "'"$version"'",' \ ' "dispatcherContractVersion": 1' \ - '}' > cli/dispatcher-contract.json + '}' > dispatcher/dispatcher-contract.json } run_case() { diff --git a/scripts/test-resolve-native-cli-release-target.sh b/scripts/test-resolve-native-cli-release-target.sh index 0526c23b11..182837f9a0 100755 --- a/scripts/test-resolve-native-cli-release-target.sh +++ b/scripts/test-resolve-native-cli-release-target.sh @@ -23,8 +23,8 @@ set -eu emit_cli_release_diff() { version=$1 printf '%s\n' 'diff --git a/.release-please-manifest.json b/.release-please-manifest.json' - printf '+ "cli": "%s"\n' "$version" - printf '%s\n' 'diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md' + printf '+ "project-runner": "%s"\n' "$version" + printf '%s\n' 'diff --git a/project-runner/CHANGELOG.md b/project-runner/CHANGELOG.md' printf '+## [%s]\n' "$version" } @@ -142,7 +142,7 @@ write_manifest() { version=$1 cat > .release-please-manifest.json < release-please-config.json < .release-please-manifest.json < cli/CHANGELOG.md < project-runner/CHANGELOG.md < "$work_dir/release-sha.txt" + + write_release_files 3.0.0-beta.6 + git rm -q CHANGELOG.md + git add . + git commit -q -m "chore: move unity-package release root" + ) + + printf '%s\n' "$work_dir" +} + prepare_origin_branch() { work_dir=$1 branch_name=$2 @@ -543,6 +574,17 @@ test_key_rename_commit_is_not_treated_as_release_commit() { assert_not_contains "$work_dir/stderr.txt" "points at" } +# Verifies a package-root move that relocates the changelog file is not mistaken for the release-please release commit even when git rename detection is unavailable. +test_changelog_move_commit_is_not_treated_as_release_commit() { + work_dir=$(create_changelog_move_repo changelog-move-root) + release_sha=$(cat "$work_dir/release-sha.txt") + + run_sync "$work_dir" v3.0.0-beta.6 false "$release_sha" + + assert_contains "$work_dir/output.txt" "Release v3.0.0-beta.6 already exists." + assert_not_contains "$work_dir/stderr.txt" "points at" +} + # Verifies a root package release created by another workflow during creation is reused. test_concurrent_root_release_creation_is_reused() { work_dir=$(create_release_repo concurrent-root-create) @@ -568,4 +610,5 @@ test_waits_for_dispatcher_assets_before_creating_root_release test_waits_when_dispatcher_asset_list_fails test_retries_until_cli_assets_are_ready test_key_rename_commit_is_not_treated_as_release_commit +test_changelog_move_commit_is_not_treated_as_release_commit test_concurrent_root_release_creation_is_reused diff --git a/scripts/test-use-local-uloop.sh b/scripts/test-use-local-uloop.sh index 15334c9640..7bb0957b3e 100755 --- a/scripts/test-use-local-uloop.sh +++ b/scripts/test-use-local-uloop.sh @@ -31,9 +31,9 @@ if [ ! -L "$BIN_DIR/uloop" ]; then exit 1 fi -expected_target="$ROOT_DIR/cli/dist/darwin-arm64/uloop" +expected_target="$ROOT_DIR/dist/darwin-arm64/uloop" if [ "$(uname -m)" = "x86_64" ] || [ "$(uname -m)" = "amd64" ]; then - expected_target="$ROOT_DIR/cli/dist/darwin-amd64/uloop" + expected_target="$ROOT_DIR/dist/darwin-amd64/uloop" fi actual_target=$(readlink "$BIN_DIR/uloop") diff --git a/scripts/use-local-uloop.sh b/scripts/use-local-uloop.sh index bcbbffccd1..2e88f13dd3 100755 --- a/scripts/use-local-uloop.sh +++ b/scripts/use-local-uloop.sh @@ -20,10 +20,10 @@ select_local_uloop() { case "$os_name:$arch_name" in Darwin:arm64|Darwin:aarch64) - local_uloop="$ROOT_DIR/cli/dist/darwin-arm64/uloop" + local_uloop="$ROOT_DIR/dist/darwin-arm64/uloop" ;; Darwin:x86_64|Darwin:amd64) - local_uloop="$ROOT_DIR/cli/dist/darwin-amd64/uloop" + local_uloop="$ROOT_DIR/dist/darwin-amd64/uloop" ;; *) echo "No built local uloop binary for $os_name $arch_name." >&2 diff --git a/scripts/verify-dispatcher-release-assets.sh b/scripts/verify-dispatcher-release-assets.sh index 495f741040..7e4ea935b0 100755 --- a/scripts/verify-dispatcher-release-assets.sh +++ b/scripts/verify-dispatcher-release-assets.sh @@ -18,7 +18,7 @@ if [ "${1:-}" = "--list" ]; then exit 0 fi -RELEASE_DIR="${1:-$ROOT_DIR/cli/dist/dispatcher-release}" +RELEASE_DIR="${1:-$ROOT_DIR/dist/dispatcher-release}" fail() { echo "$1" >&2 diff --git a/scripts/verify-go-cli-dist.sh b/scripts/verify-go-cli-dist.sh index 431389d4d6..7abcf40b2a 100755 --- a/scripts/verify-go-cli-dist.sh +++ b/scripts/verify-go-cli-dist.sh @@ -4,12 +4,12 @@ set -eu ROOT_DIR=$(CDPATH= cd "$(dirname "$0")/.." && pwd) DIST_FILES=" -cli/dist/darwin-arm64/uloop -cli/dist/darwin-arm64/uloop-project-runner -cli/dist/darwin-amd64/uloop -cli/dist/darwin-amd64/uloop-project-runner -cli/dist/windows-amd64/uloop.exe -cli/dist/windows-amd64/uloop-project-runner.exe +dist/darwin-arm64/uloop +dist/darwin-arm64/uloop-project-runner +dist/darwin-amd64/uloop +dist/darwin-amd64/uloop-project-runner +dist/windows-amd64/uloop.exe +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 d7f118d883..dde91afdbb 100755 --- a/scripts/verify-native-cli-release-assets.sh +++ b/scripts/verify-native-cli-release-assets.sh @@ -16,7 +16,7 @@ if [ "${1:-}" = "--list" ]; then exit 0 fi -RELEASE_DIR="${1:-$ROOT_DIR/cli/dist/release}" +RELEASE_DIR="${1:-$ROOT_DIR/dist/release}" fail() { echo "$1" >&2 diff --git a/cli/cmd/check-dispatcher-minimum-version/main.go b/tools/release-automation/cmd/check-dispatcher-minimum-version/main.go similarity index 80% rename from cli/cmd/check-dispatcher-minimum-version/main.go rename to tools/release-automation/cmd/check-dispatcher-minimum-version/main.go index a402766c26..07a79e8685 100644 --- a/cli/cmd/check-dispatcher-minimum-version/main.go +++ b/tools/release-automation/cmd/check-dispatcher-minimum-version/main.go @@ -5,7 +5,7 @@ import ( "os" "time" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) const dispatcherMinimumVersionCheckTimeout = 2 * time.Minute diff --git a/cli/cmd/check-dispatcher-version-bump/main.go b/tools/release-automation/cmd/check-dispatcher-version-bump/main.go similarity index 83% rename from cli/cmd/check-dispatcher-version-bump/main.go rename to tools/release-automation/cmd/check-dispatcher-version-bump/main.go index e24f123765..9e4a38eb5d 100644 --- a/cli/cmd/check-dispatcher-version-bump/main.go +++ b/tools/release-automation/cmd/check-dispatcher-version-bump/main.go @@ -5,7 +5,7 @@ import ( "flag" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) func main() { diff --git a/cli/cmd/check-ipc-protocol-reminder/main.go b/tools/release-automation/cmd/check-ipc-protocol-reminder/main.go similarity index 85% rename from cli/cmd/check-ipc-protocol-reminder/main.go rename to tools/release-automation/cmd/check-ipc-protocol-reminder/main.go index 15a6d95880..e527aa6e81 100644 --- a/cli/cmd/check-ipc-protocol-reminder/main.go +++ b/tools/release-automation/cmd/check-ipc-protocol-reminder/main.go @@ -5,7 +5,7 @@ import ( "flag" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) func main() { diff --git a/cli/cmd/check-protocol-minimum-version/main.go b/tools/release-automation/cmd/check-protocol-minimum-version/main.go similarity index 91% rename from cli/cmd/check-protocol-minimum-version/main.go rename to tools/release-automation/cmd/check-protocol-minimum-version/main.go index 1987e3f9d8..8d4b83f040 100644 --- a/cli/cmd/check-protocol-minimum-version/main.go +++ b/tools/release-automation/cmd/check-protocol-minimum-version/main.go @@ -5,7 +5,7 @@ import ( "flag" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) func main() { diff --git a/cli/cmd/comment-protocol-minimum-version/main.go b/tools/release-automation/cmd/comment-protocol-minimum-version/main.go similarity index 66% rename from cli/cmd/comment-protocol-minimum-version/main.go rename to tools/release-automation/cmd/comment-protocol-minimum-version/main.go index ab6223b2c8..1e20862b61 100644 --- a/cli/cmd/comment-protocol-minimum-version/main.go +++ b/tools/release-automation/cmd/comment-protocol-minimum-version/main.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) func main() { diff --git a/cli/cmd/dispatch-release-please-pr-checks/main.go b/tools/release-automation/cmd/dispatch-release-please-pr-checks/main.go similarity index 64% rename from cli/cmd/dispatch-release-please-pr-checks/main.go rename to tools/release-automation/cmd/dispatch-release-please-pr-checks/main.go index 0d43b56de5..e20e621ec2 100644 --- a/cli/cmd/dispatch-release-please-pr-checks/main.go +++ b/tools/release-automation/cmd/dispatch-release-please-pr-checks/main.go @@ -4,7 +4,7 @@ import ( "context" "os" - "github.com/hatayama/unity-cli-loop/cli/internal/automation" + "github.com/hatayama/unity-cli-loop/tools/release-automation/internal/automation" ) func main() { diff --git a/tools/release-automation/go.mod b/tools/release-automation/go.mod new file mode 100644 index 0000000000..7167f891e1 --- /dev/null +++ b/tools/release-automation/go.mod @@ -0,0 +1,7 @@ +module github.com/hatayama/unity-cli-loop/tools/release-automation + +go 1.26 + +require github.com/hatayama/unity-cli-loop/common v0.0.0-00010101000000-000000000000 + +replace github.com/hatayama/unity-cli-loop/common => ../../common diff --git a/tools/release-automation/internal/architecture/architecture_test.go b/tools/release-automation/internal/architecture/architecture_test.go new file mode 100644 index 0000000000..aa2f8d7a09 --- /dev/null +++ b/tools/release-automation/internal/architecture/architecture_test.go @@ -0,0 +1,574 @@ +package architecture + +import ( + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "testing" +) + +const ( + repositoryModulePrefix = "github.com/hatayama/unity-cli-loop/" + commonModulePath = repositoryModulePrefix + "common" + dispatcherModulePath = repositoryModulePrefix + "dispatcher" + projectRunnerModulePath = repositoryModulePrefix + "project-runner" + releaseAutomationModulePath = repositoryModulePrefix + "tools/release-automation" + maxProductionFileLines = 500 +) + +// goPackage mirrors the subset of `go list -json` output that these tests read. +type goPackage struct { + ImportPath string + Imports []string +} + +// goModEditJSON mirrors the subset of `go mod edit -json` output that these tests read. +type goModEditJSON struct { + Module goModEditModule `json:"Module"` + Go string `json:"Go"` + Require []goModEditRequire `json:"Require"` +} + +type goModEditModule struct { + Path string `json:"Path"` +} + +type goModEditRequire struct { + Path string `json:"Path"` +} + +// goWorkEditJSON mirrors the subset of `go work edit -json` output that these tests read. +type goWorkEditJSON struct { + Go string `json:"Go"` + Use []goWorkEditUse `json:"Use"` +} + +type goWorkEditUse struct { + DiskPath string `json:"DiskPath"` +} + +type layoutContract struct { + SchemaVersion int `json:"schemaVersion"` + Layout layoutSection `json:"layout"` + Binaries binariesLayout `json:"binaries"` +} + +type layoutSection struct { + Modules layoutModules `json:"modules"` + DistDir string `json:"distDir"` +} + +type layoutModules struct { + Common string `json:"common"` + Dispatcher string `json:"dispatcher"` + ProjectRunner string `json:"projectRunner"` + ReleaseAutomation string `json:"releaseAutomation"` +} + +type binariesLayout struct { + Dispatcher binaryNames `json:"dispatcher"` + ProjectRunner binaryNames `json:"projectRunner"` +} + +type binaryNames struct { + Unix string `json:"unix"` + Windows string `json:"windows"` +} + +// Tests that the pre-split top-level `cli/` directory no longer exists at the repo root. +func TestNoTopLevelCliDirectory(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + cliPath := filepath.Join(repositoryRoot, "cli") + if _, err := os.Stat(cliPath); err == nil { + t.Fatalf("pre-split top-level directory must not exist: %s", cliPath) + } +} + +// Tests that every module's require directives respect the repo's dependency direction: +// common depends on nothing else in the repo; the other three modules only depend on common. +func TestModuleDependencyDirections(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + commonDir := filepath.Join(repositoryRoot, contract.Layout.Modules.Common) + commonRequires := readGoModEdit(t, commonDir).Require + for _, require := range commonRequires { + if strings.HasPrefix(require.Path, repositoryModulePrefix) { + t.Fatalf("common module must not require any other repo module, got %s", require.Path) + } + } + + dependentModuleDirs := []string{ + filepath.Join(repositoryRoot, contract.Layout.Modules.Dispatcher), + filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner), + filepath.Join(repositoryRoot, contract.Layout.Modules.ReleaseAutomation), + } + for _, moduleDir := range dependentModuleDirs { + requires := readGoModEdit(t, moduleDir).Require + for _, require := range requires { + if !strings.HasPrefix(require.Path, repositoryModulePrefix) { + continue + } + if require.Path != commonModulePath { + t.Fatalf("module %s may only depend on %s among repo modules, got %s", moduleDir, commonModulePath, require.Path) + } + } + } +} + +// Tests that the Go toolchain directive stays identical across every go.mod, go.work, and .go-version. +func TestGoToolchainSingleSourceOfTruth(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + workDirective := readGoWorkEdit(t, repositoryRoot).Go + if workDirective == "" { + t.Fatalf("go.work must declare a go directive") + } + + for _, moduleDir := range allModuleDirs(repositoryRoot, contract) { + moduleDirective := readGoModEdit(t, moduleDir).Go + if moduleDirective != workDirective { + t.Fatalf("go directive mismatch: %s has %q, go.work has %q", moduleDir, moduleDirective, workDirective) + } + } + + goVersionRaw, err := os.ReadFile(filepath.Join(repositoryRoot, ".go-version")) + if err != nil { + t.Fatalf("failed to read .go-version: %v", err) + } + goVersion := strings.TrimSpace(string(goVersionRaw)) + if goVersion != workDirective && !strings.HasPrefix(goVersion, workDirective+".") { + t.Fatalf(".go-version %q must equal or start with %q followed by '.'", goVersion, workDirective) + } +} + +// Tests that the dispatcher command only enters dispatcher-internal code through the dispatcher entrypoint. +func TestDispatcherCommandOnlyDependsOnDispatcherEntrypoint(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + dispatcherDir := filepath.Join(repositoryRoot, contract.Layout.Modules.Dispatcher) + assertCommandOnlyDependsOnInternalEntrypoint(t, dispatcherDir, dispatcherModulePath, "./cmd/dispatcher", dispatcherModulePath+"/internal/dispatcher") +} + +// Tests that the project runner command only enters project-runner-internal code through the projectrunner entrypoint. +func TestProjectRunnerCommandOnlyDependsOnProjectRunnerEntrypoint(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + projectRunnerDir := filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner) + assertCommandOnlyDependsOnInternalEntrypoint(t, projectRunnerDir, projectRunnerModulePath, "./cmd/project-runner", projectRunnerModulePath+"/internal/projectrunner") +} + +// Tests that the dispatcher binary does not transitively pull in the project runner's projectrunner package. +func TestDispatcherBinaryDoesNotTransitivelyDependOnProjectRunner(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + dispatcherDir := filepath.Join(repositoryRoot, contract.Layout.Modules.Dispatcher) + assertBinaryDoesNotTransitivelyDependOn(t, dispatcherDir, "./cmd/dispatcher", projectRunnerModulePath+"/internal/projectrunner") +} + +// Tests that the project runner binary does not transitively pull in the dispatcher module's dispatcher package. +func TestProjectRunnerBinaryDoesNotTransitivelyDependOnDispatcher(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + projectRunnerDir := filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner) + assertBinaryDoesNotTransitivelyDependOn(t, projectRunnerDir, "./cmd/project-runner", dispatcherModulePath+"/internal/dispatcher") +} + +// Tests that production Go files across every module stay small enough to keep each file focused on one responsibility. +func TestProductionGoFilesStayFocused(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + for _, moduleDir := range allModuleDirs(repositoryRoot, contract) { + walkErr := filepath.WalkDir(moduleDir, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if entry.IsDir() { + if entry.Name() == "dist" { + return filepath.SkipDir + } + return nil + } + if !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") { + return nil + } + lineCount, err := countLines(path) + if err != nil { + return err + } + if lineCount > maxProductionFileLines { + relativePath, err := filepath.Rel(repositoryRoot, path) + if err != nil { + return err + } + return fmt.Errorf("%s has %d lines; split files above %d lines", relativePath, lineCount, maxProductionFileLines) + } + return nil + }) + if walkErr != nil { + t.Fatal(walkErr) + } + } +} + +// Tests that every package in the common module (except clicore itself) stays free of clicore imports. +func TestCommonPackagesDoNotImportClicore(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + commonDir := filepath.Join(repositoryRoot, contract.Layout.Modules.Common) + + clicorePackage := commonModulePath + "/clicore" + for _, goPackage := range listPackages(t, commonDir) { + if goPackage.ImportPath == clicorePackage || strings.HasPrefix(goPackage.ImportPath, clicorePackage+"/") { + continue + } + for _, importedPath := range goPackage.Imports { + if importedPath == clicorePackage || strings.HasPrefix(importedPath, clicorePackage+"/") { + t.Fatalf("common package %s must not import %s", goPackage.ImportPath, importedPath) + } + } + } +} + +// Tests that every module's internal packages sit under the explicit boundary list allowed for that module. +func TestInternalBoundariesPerModule(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + type moduleBoundary struct { + moduleDir string + modulePath string + allowed []string + } + moduleBoundaries := []moduleBoundary{ + { + // The common module publishes shared packages only; it must never grow internal packages. + moduleDir: filepath.Join(repositoryRoot, contract.Layout.Modules.Common), + modulePath: commonModulePath, + allowed: []string{}, + }, + { + moduleDir: filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner), + modulePath: projectRunnerModulePath, + allowed: []string{"projectrunner"}, + }, + { + moduleDir: filepath.Join(repositoryRoot, contract.Layout.Modules.Dispatcher), + modulePath: dispatcherModulePath, + allowed: []string{"dispatcher", "install", "uninstall", "update"}, + }, + { + moduleDir: filepath.Join(repositoryRoot, contract.Layout.Modules.ReleaseAutomation), + modulePath: releaseAutomationModulePath, + allowed: []string{"automation", "architecture"}, + }, + } + + for _, boundary := range moduleBoundaries { + allowedPrefixes := []string{} + for _, name := range boundary.allowed { + allowedPrefixes = append(allowedPrefixes, boundary.modulePath+"/internal/"+name) + } + internalPrefix := boundary.modulePath + "/internal/" + for _, goPackage := range listPackages(t, boundary.moduleDir) { + if !strings.HasPrefix(goPackage.ImportPath, internalPrefix) { + continue + } + if !hasAnyPackagePrefix(goPackage.ImportPath, allowedPrefixes) { + t.Fatalf("%s internal package must live under one of %v, got %s", boundary.modulePath, boundary.allowed, goPackage.ImportPath) + } + } + } +} + +// Tests that the layout contract matches repository paths used by tooling and downstream scripts. +func TestLayoutContractMatchesRepositoryPaths(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + if contract.SchemaVersion != 2 { + t.Fatalf("layout contract schema version mismatch: %d", contract.SchemaVersion) + } + moduleDirs := map[string]string{ + "common": contract.Layout.Modules.Common, + "dispatcher": contract.Layout.Modules.Dispatcher, + "projectRunner": contract.Layout.Modules.ProjectRunner, + "releaseAutomation": contract.Layout.Modules.ReleaseAutomation, + } + for name, moduleDir := range moduleDirs { + if moduleDir == "" { + t.Fatalf("layout contract module %s is empty", name) + } + assertPathExists(t, filepath.Join(repositoryRoot, moduleDir)) + } + projectRunnerDir := filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner) + assertPathExists(t, filepath.Join(projectRunnerDir, "cmd")) + assertPathExists(t, filepath.Join(projectRunnerDir, "internal")) + assertPathDoesNotExist(t, filepath.Join(projectRunnerDir, "Core~")) + assertPathDoesNotExist(t, filepath.Join(projectRunnerDir, "Dispatcher~")) + assertPathDoesNotExist(t, filepath.Join(projectRunnerDir, "Shared~")) + + assertTextContains(t, filepath.Join(repositoryRoot, "scripts", "build-go-cli.sh"), contract.Layout.Modules.ProjectRunner) + + distDir := contract.Layout.DistDir + verifyScript := filepath.Join(repositoryRoot, "scripts", "verify-go-cli-dist.sh") + assertTextContains(t, verifyScript, filepath.ToSlash(filepath.Join(distDir, "darwin-arm64", contract.Binaries.Dispatcher.Unix))) + assertTextContains(t, verifyScript, filepath.ToSlash(filepath.Join(distDir, "darwin-arm64", contract.Binaries.ProjectRunner.Unix))) + assertTextContains(t, verifyScript, filepath.ToSlash(filepath.Join(distDir, "windows-amd64", contract.Binaries.Dispatcher.Windows))) + assertTextContains(t, verifyScript, filepath.ToSlash(filepath.Join(distDir, "windows-amd64", contract.Binaries.ProjectRunner.Windows))) +} + +// Tests that the set of module dirs in the layout contract equals the go.work `use` set +// and that each module dir string is referenced from the scripts and workflows that drive multi-module CI. +func TestModuleEnumerationConsistency(t *testing.T) { + repositoryRoot := findRepositoryRoot(t) + contract := readLayoutContract(t, filepath.Join(repositoryRoot, "layout-contract.json")) + + contractModuleDirs := []string{ + contract.Layout.Modules.Common, + contract.Layout.Modules.Dispatcher, + contract.Layout.Modules.ProjectRunner, + contract.Layout.Modules.ReleaseAutomation, + } + sort.Strings(contractModuleDirs) + + workEdit := readGoWorkEdit(t, repositoryRoot) + workModuleDirs := []string{} + for _, use := range workEdit.Use { + workModuleDirs = append(workModuleDirs, normalizeWorkDiskPath(use.DiskPath)) + } + sort.Strings(workModuleDirs) + + if !stringSlicesEqual(contractModuleDirs, workModuleDirs) { + t.Fatalf("layout contract modules %v must equal go.work use entries %v", contractModuleDirs, workModuleDirs) + } + + scriptsToCheck := []string{ + filepath.Join(repositoryRoot, "scripts", "check-go-cli-source.sh"), + filepath.Join(repositoryRoot, "scripts", "check-code-complexity.sh"), + filepath.Join(repositoryRoot, ".github", "workflows", "code-complexity.yml"), + } + for _, moduleDir := range contractModuleDirs { + for _, scriptPath := range scriptsToCheck { + assertTextContains(t, scriptPath, moduleDir) + } + } +} + +// assertBinaryDoesNotTransitivelyDependOn runs `go list -deps` for the given command +// and fails when forbiddenPackage appears in its transitive dependency list. +func assertBinaryDoesNotTransitivelyDependOn(t *testing.T, moduleDir string, commandPath string, forbiddenPackage string) { + t.Helper() + command := exec.Command("go", "list", "-deps", commandPath) + command.Dir = moduleDir + output, err := command.Output() + if err != nil { + t.Fatalf("go list -deps failed: %v", err) + } + for _, dependency := range strings.Split(strings.TrimSpace(string(output)), "\n") { + if dependency == forbiddenPackage { + t.Fatalf("%s must not transitively depend on %s", commandPath, forbiddenPackage) + } + } +} + +// assertCommandOnlyDependsOnInternalEntrypoint fails when the command's direct imports include +// any internal package other than expectedEntrypoint from its own module. +func assertCommandOnlyDependsOnInternalEntrypoint(t *testing.T, moduleDir string, modulePath string, commandPath string, expectedEntrypoint string) { + t.Helper() + command := exec.Command("go", "list", "-json", commandPath) + command.Dir = moduleDir + output, err := command.Output() + if err != nil { + t.Fatalf("go list failed: %v", err) + } + + var commandPackage goPackage + if err := json.Unmarshal(output, &commandPackage); err != nil { + t.Fatalf("failed to decode command package: %v", err) + } + for _, dependency := range commandPackage.Imports { + if !strings.HasPrefix(dependency, modulePath+"/internal/") { + continue + } + if dependency != expectedEntrypoint { + t.Fatalf("%s must enter internal code through %s, got %s", commandPath, expectedEntrypoint, dependency) + } + } +} + +// hasAnyPackagePrefix reports whether importPath is packagePrefix itself or a subpackage of it. +func hasAnyPackagePrefix(importPath string, packagePrefixes []string) bool { + for _, packagePrefix := range packagePrefixes { + if importPath == packagePrefix || strings.HasPrefix(importPath, packagePrefix+"/") { + return true + } + } + return false +} + +// allModuleDirs returns the absolute directory paths of every repo module in a stable order. +func allModuleDirs(repositoryRoot string, contract layoutContract) []string { + return []string{ + filepath.Join(repositoryRoot, contract.Layout.Modules.Common), + filepath.Join(repositoryRoot, contract.Layout.Modules.Dispatcher), + filepath.Join(repositoryRoot, contract.Layout.Modules.ProjectRunner), + filepath.Join(repositoryRoot, contract.Layout.Modules.ReleaseAutomation), + } +} + +// listPackages runs `go list -json ./...` inside moduleDir and decodes each package object. +func listPackages(t *testing.T, moduleDir string) []goPackage { + t.Helper() + command := exec.Command("go", "list", "-json", "./...") + command.Dir = moduleDir + output, err := command.Output() + if err != nil { + t.Fatalf("go list failed in %s: %v", moduleDir, err) + } + + decoder := json.NewDecoder(strings.NewReader(string(output))) + packages := []goPackage{} + for { + var goPackage goPackage + err := decoder.Decode(&goPackage) + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("failed to decode go list output: %v", err) + } + packages = append(packages, goPackage) + } + return packages +} + +// readGoModEdit runs `go mod edit -json` inside moduleDir and decodes the result. +func readGoModEdit(t *testing.T, moduleDir string) goModEditJSON { + t.Helper() + command := exec.Command("go", "mod", "edit", "-json") + command.Dir = moduleDir + output, err := command.Output() + if err != nil { + t.Fatalf("go mod edit -json failed in %s: %v", moduleDir, err) + } + var result goModEditJSON + if err := json.Unmarshal(output, &result); err != nil { + t.Fatalf("failed to decode go mod edit output for %s: %v", moduleDir, err) + } + return result +} + +// readGoWorkEdit runs `go work edit -json` inside the repository root and decodes the result. +func readGoWorkEdit(t *testing.T, repositoryRoot string) goWorkEditJSON { + t.Helper() + command := exec.Command("go", "work", "edit", "-json") + command.Dir = repositoryRoot + output, err := command.Output() + if err != nil { + t.Fatalf("go work edit -json failed: %v", err) + } + var result goWorkEditJSON + if err := json.Unmarshal(output, &result); err != nil { + t.Fatalf("failed to decode go work edit output: %v", err) + } + return result +} + +// normalizeWorkDiskPath strips the leading "./" that `go work` emits so paths compare against layout contract values. +func normalizeWorkDiskPath(diskPath string) string { + return strings.TrimPrefix(diskPath, "./") +} + +// stringSlicesEqual reports whether two sorted string slices are element-wise equal. +func stringSlicesEqual(left []string, right []string) bool { + if len(left) != len(right) { + return false + } + for index := range left { + if left[index] != right[index] { + return false + } + } + return true +} + +func readLayoutContract(t *testing.T, path string) layoutContract { + t.Helper() + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read layout contract: %v", err) + } + var contract layoutContract + if err := json.Unmarshal(content, &contract); err != nil { + t.Fatalf("failed to parse layout contract: %v", err) + } + return contract +} + +func assertPathExists(t *testing.T, path string) { + t.Helper() + if _, err := os.Stat(path); err != nil { + t.Fatalf("expected path to exist: %s", path) + } +} + +func assertPathDoesNotExist(t *testing.T, path string) { + t.Helper() + if _, err := os.Stat(path); err == nil { + t.Fatalf("expected path not to exist: %s", path) + } +} + +func assertTextContains(t *testing.T, path string, expected string) { + t.Helper() + content, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read %s: %v", path, err) + } + if !strings.Contains(string(content), expected) { + t.Fatalf("%s must contain %s", path, expected) + } +} + +// findRepositoryRoot walks up from the current working directory until it finds a `.git` entry. +// This works regardless of which module the test is running in. +func findRepositoryRoot(t *testing.T) string { + t.Helper() + currentPath, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + for { + if _, err := os.Stat(filepath.Join(currentPath, ".git")); err == nil { + return currentPath + } + parentPath := filepath.Dir(currentPath) + if parentPath == currentPath { + t.Fatal(".git not found while walking up from CWD") + } + currentPath = parentPath + } +} + +func countLines(path string) (int, error) { + content, err := os.ReadFile(path) + if err != nil { + return 0, err + } + if len(content) == 0 { + return 0, nil + } + lineCount := strings.Count(string(content), "\n") + if !strings.HasSuffix(string(content), "\n") { + lineCount++ + } + return lineCount, nil +} diff --git a/cli/internal/architecture/github_actions_security_test.go b/tools/release-automation/internal/architecture/github_actions_security_test.go similarity index 98% rename from cli/internal/architecture/github_actions_security_test.go rename to tools/release-automation/internal/architecture/github_actions_security_test.go index a3f589b621..803761fb6f 100644 --- a/cli/internal/architecture/github_actions_security_test.go +++ b/tools/release-automation/internal/architecture/github_actions_security_test.go @@ -14,7 +14,7 @@ var githubActionCommitRefPattern = regexp.MustCompile(`^[0-9a-fA-F]{40}$`) // Tests that remote GitHub Actions are pinned to immutable commit SHAs. func TestWorkflowActionsUseCommitPins(t *testing.T) { - repositoryRoot := findRepositoryRoot(t, findModuleRoot(t)) + repositoryRoot := findRepositoryRoot(t) violations := []string{} for _, workflowPath := range workflowFilePaths(t, repositoryRoot) { lines := readWorkflowLines(t, workflowPath) @@ -37,7 +37,7 @@ func TestWorkflowActionsUseCommitPins(t *testing.T) { // Tests that setup-go does not use cache in pull request workflows. func TestPullRequestWorkflowsDisableSetupGoCache(t *testing.T) { - repositoryRoot := findRepositoryRoot(t, findModuleRoot(t)) + repositoryRoot := findRepositoryRoot(t) violations := []string{} for _, workflowPath := range workflowFilePaths(t, repositoryRoot) { lines := readWorkflowLines(t, workflowPath) @@ -63,7 +63,7 @@ func TestPullRequestWorkflowsDisableSetupGoCache(t *testing.T) { // Tests that pull request workflow cache actions are guarded behind trusted Unity secrets. func TestPullRequestWorkflowCacheActionsRequireTrustedUnitySecrets(t *testing.T) { - repositoryRoot := findRepositoryRoot(t, findModuleRoot(t)) + repositoryRoot := findRepositoryRoot(t) violations := []string{} for _, workflowPath := range workflowFilePaths(t, repositoryRoot) { lines := readWorkflowLines(t, workflowPath) diff --git a/tools/release-automation/internal/automation/contract_file_at_ref.go b/tools/release-automation/internal/automation/contract_file_at_ref.go new file mode 100644 index 0000000000..6d0fa48771 --- /dev/null +++ b/tools/release-automation/internal/automation/contract_file_at_ref.go @@ -0,0 +1,88 @@ +package automation + +import ( + "bytes" + "context" + "fmt" + "os/exec" + "path/filepath" + "strings" +) + +func runnerContractMissingAtReleaseMessage(releaseTag string) string { + return fmt.Sprintf("project runner release %s does not provide %s or %s", releaseTag, cliContractFile, legacyRunnerContractFile) +} + +// runnerContractFileAtRef reads the CLI/runner IPC contract file at a git ref. +func runnerContractFileAtRef(ctx context.Context, repoRoot string, ref string) (string, error) { + return contractFileAtRefWithLegacyFallback(ctx, repoRoot, ref, cliContractFile, legacyRunnerContractFile) +} + +// contractFileAtRefWithLegacyFallback reads a release contract at a git ref. +// Release tags published before the cli/ directory split still provide their +// contracts at the pre-split paths, so this falls back to the legacy path when +// the primary path is missing at the given ref. +func contractFileAtRefWithLegacyFallback( + ctx context.Context, + repoRoot string, + ref string, + primaryFile string, + legacyFile string, +) (string, error) { + content, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, ref, primaryFile) + if err == nil { + return content, nil + } + if !isMissingFileAtRefError(err, primaryFile) { + return "", err + } + return protocolMinimumVersionFileAtRef(ctx, repoRoot, ref, legacyFile) +} + +// isMissingFileAtRefError reports whether err came from `git show ref:file` +// failing because file does not exist at ref, as opposed to any other git +// failure (auth, network, etc.) that must not be silently swallowed. +// Current git prints the lowercase "path ..." forms; the capitalized +// "Path ... does not exist in" form is kept for older git versions. +func isMissingFileAtRefError(err error, file string) bool { + message := err.Error() + quotedPath := "'" + file + "'" + return strings.Contains(message, "path "+quotedPath+" exists on disk, but not in") || + strings.Contains(message, "path "+quotedPath+" does not exist in") || + strings.Contains(message, "Path "+quotedPath+" does not exist in") +} + +func protocolMinimumVersionFileAtRef( + ctx context.Context, + repoRoot string, + ref string, + file string, +) (string, error) { + return runProtocolMinimumVersionOutput( + ctx, + repoRoot, + "git", + "-C", + repoRoot, + "show", + ref+":"+file) +} + +func runProtocolMinimumVersionOutput( + ctx context.Context, + workDir string, + name string, + args ...string, +) (string, error) { + command := exec.CommandContext(ctx, name, args...) + command.Dir = filepath.Clean(workDir) + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} + command.Stdout = &stdout + command.Stderr = &stderr + err := command.Run() + if err != nil { + return "", fmt.Errorf("%s %s failed: %w\n%s%s", name, strings.Join(args, " "), err, stderr.String(), stdout.String()) + } + return stdout.String(), nil +} diff --git a/tools/release-automation/internal/automation/contract_file_at_ref_test.go b/tools/release-automation/internal/automation/contract_file_at_ref_test.go new file mode 100644 index 0000000000..9959db7928 --- /dev/null +++ b/tools/release-automation/internal/automation/contract_file_at_ref_test.go @@ -0,0 +1,46 @@ +package automation + +import ( + "errors" + "testing" +) + +// Verifies the lowercase form git currently emits when the file exists nowhere is treated as missing. +func TestIsMissingFileAtRefErrorMatchesLowercaseDoesNotExist(t *testing.T) { + err := errors.New("git show failed: fatal: path 'common/clicontract/contract.json' does not exist in 'uloop-project-runner-v3.0.0-beta.40'") + if !isMissingFileAtRefError(err, "common/clicontract/contract.json") { + t.Fatal("expected lowercase does-not-exist git error to be treated as a missing file") + } +} + +// Verifies the capitalized does-not-exist form emitted by older git versions stays covered. +func TestIsMissingFileAtRefErrorMatchesCapitalizedDoesNotExist(t *testing.T) { + err := errors.New("git show failed: fatal: Path 'common/clicontract/contract.json' does not exist in 'uloop-project-runner-v3.0.0-beta.40'") + if !isMissingFileAtRefError(err, "common/clicontract/contract.json") { + t.Fatal("expected capitalized does-not-exist git error to be treated as a missing file") + } +} + +// Verifies the exists-on-disk-but-not-in-ref form is treated as missing at the ref. +func TestIsMissingFileAtRefErrorMatchesExistsOnDiskForm(t *testing.T) { + err := errors.New("git show failed: fatal: path 'common/clicontract/contract.json' exists on disk, but not in 'uloop-project-runner-v3.0.0-beta.40'") + if !isMissingFileAtRefError(err, "common/clicontract/contract.json") { + t.Fatal("expected exists-on-disk git error to be treated as a missing file") + } +} + +// Verifies unrelated git failures are never classified as a missing file, so they propagate. +func TestIsMissingFileAtRefErrorRejectsUnrelatedErrors(t *testing.T) { + err := errors.New("git show failed: fatal: unable to read tree") + if isMissingFileAtRefError(err, "common/clicontract/contract.json") { + t.Fatal("expected unrelated git error to propagate instead of being treated as missing") + } +} + +// Verifies a missing-file error for a different path is not attributed to the requested file. +func TestIsMissingFileAtRefErrorRejectsOtherFilePaths(t *testing.T) { + err := errors.New("git show failed: fatal: path 'other/file.json' does not exist in 'HEAD'") + if isMissingFileAtRefError(err, "common/clicontract/contract.json") { + t.Fatal("expected missing-file error for another path to propagate") + } +} diff --git a/cli/internal/automation/dispatcher_minimum_version_guard.go b/tools/release-automation/internal/automation/dispatcher_minimum_version_guard.go similarity index 94% rename from cli/internal/automation/dispatcher_minimum_version_guard.go rename to tools/release-automation/internal/automation/dispatcher_minimum_version_guard.go index d3afe64046..a2c3969300 100644 --- a/cli/internal/automation/dispatcher_minimum_version_guard.go +++ b/tools/release-automation/internal/automation/dispatcher_minimum_version_guard.go @@ -13,8 +13,10 @@ import ( ) const ( - cliContractFile = "cli/contract.json" - dispatcherContractFile = "cli/dispatcher-contract.json" + cliContractFile = "common/clicontract/contract.json" + legacyRunnerContractFile = "cli/contract.json" + dispatcherContractFile = "dispatcher/dispatcher-contract.json" + legacyDispatcherContractFile = "cli/dispatcher-contract.json" dispatcherReleaseTagPrefix = "dispatcher-v" unityPackageCliPinFile = "Packages/src/project-runner-pin.json" unityProjectCliPinFile = ".uloop/project-runner-pin.json" @@ -265,13 +267,18 @@ func verifyDispatcherMinimumVersionAtRef( } releaseTag := dispatcherReleaseTagPrefix + values.MinimumDispatcherVersion - contractContent, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, releaseTag, dispatcherContractFile) + contractContent, err := dispatcherContractFileAtRef(ctx, repoRoot, releaseTag) if err != nil { - return fmt.Errorf("dispatcher release %s does not provide %s", releaseTag, dispatcherContractFile) + return fmt.Errorf("dispatcher release %s does not provide %s or %s", releaseTag, dispatcherContractFile, legacyDispatcherContractFile) } return verifyMinimumCliReleaseDispatcherContract(values, []byte(contractContent)) } +// dispatcherContractFileAtRef reads the dispatcher release contract at a git ref. +func dispatcherContractFileAtRef(ctx context.Context, repoRoot string, ref string) (string, error) { + return contractFileAtRefWithLegacyFallback(ctx, repoRoot, ref, dispatcherContractFile, legacyDispatcherContractFile) +} + func verifyCurrentDispatcherMinimumVersion(values dispatcherMinimumVersionValues) error { if values.CurrentDispatcherContractVersion < minimumDispatcherContractVersion { return dispatcherContractVersionTooLowError(dispatcherContractFile, values.CurrentDispatcherContractVersion) diff --git a/cli/internal/automation/dispatcher_minimum_version_guard_test.go b/tools/release-automation/internal/automation/dispatcher_minimum_version_guard_test.go similarity index 83% rename from cli/internal/automation/dispatcher_minimum_version_guard_test.go rename to tools/release-automation/internal/automation/dispatcher_minimum_version_guard_test.go index 47b11bc87a..d5a4ec6e1b 100644 --- a/cli/internal/automation/dispatcher_minimum_version_guard_test.go +++ b/tools/release-automation/internal/automation/dispatcher_minimum_version_guard_test.go @@ -24,7 +24,7 @@ func TestRunDispatcherMinimumVersionCheck_WhenMinimumReleaseLacksDispatcherContr t.Fatalf("expected exit code 1, got %d\nstdout: %s", result.exitCode, result.stdout) } assertDispatcherMinimumVersionLogContains(t, result.stderr, "does not define dispatcherContractVersion") - assertDispatcherMinimumVersionLogContains(t, result.gitLog, "dispatcher-v1.0.0:cli/dispatcher-contract.json") + assertDispatcherMinimumVersionLogContains(t, result.gitLog, "dispatcher-v1.0.0:dispatcher/dispatcher-contract.json") } // Verifies release PRs pass when the current dispatcher release itself is the minimum dispatcher version. @@ -40,7 +40,26 @@ func TestRunDispatcherMinimumVersionCheck_WhenMinimumIsCurrentRelease_Passes(t * t.Fatalf("expected exit code 0, got %d\nstderr: %s", result.exitCode, result.stderr) } assertDispatcherMinimumVersionLogContains(t, result.stdout, "Dispatcher minimum version guard passed.") - assertDispatcherMinimumVersionLogDoesNotContain(t, result.gitLog, "dispatcher-v1.0.0:cli/dispatcher-contract.json") + assertDispatcherMinimumVersionLogDoesNotContain(t, result.gitLog, "dispatcher-v1.0.0:dispatcher/dispatcher-contract.json") +} + +// Verifies dispatcher releases published before the cli/ directory split are still readable +// through the legacy dispatcher-contract.json path when the new path is missing at the tag. +func TestRunDispatcherMinimumVersionCheck_WhenMinimumReleasePredatesDirectorySplit_FallsBackToLegacyPath(t *testing.T) { + result := runDispatcherMinimumVersionCheckCase(t, dispatcherMinimumVersionCase{ + currentProjectRunnerVersion: "3.0.0-beta.40", + currentDispatcherVersion: "1.0.1", + currentDispatcherContractVersion: 1, + minimumDispatcherVersion: "1.0.0", + legacyReleaseContract: `{"schemaVersion":1,"dispatcherVersion":"1.0.0","dispatcherContractVersion":1}`, + }) + + if result.exitCode != 0 { + t.Fatalf("expected exit code 0, got %d\nstderr: %s", result.exitCode, result.stderr) + } + assertDispatcherMinimumVersionLogContains(t, result.stdout, "Dispatcher minimum version guard passed.") + assertDispatcherMinimumVersionLogContains(t, result.gitLog, "dispatcher-v1.0.0:dispatcher/dispatcher-contract.json") + assertDispatcherMinimumVersionLogContains(t, result.gitLog, "dispatcher-v1.0.0:cli/dispatcher-contract.json") } // Verifies committed pin files cannot drift from the C# minimum dispatcher version. @@ -81,6 +100,7 @@ type dispatcherMinimumVersionCase struct { minimumDispatcherVersion string projectPinMinimumDispatcherVersion string releaseContract string + legacyReleaseContract string } type dispatcherMinimumVersionRunResult struct { @@ -112,6 +132,11 @@ func runDispatcherMinimumVersionCheckCase(t *testing.T, testCase dispatcherMinim writeFile(t, releaseContractPath, testCase.releaseContract) t.Setenv("GIT_RELEASE_CONTRACT", releaseContractPath) } + if testCase.legacyReleaseContract != "" { + legacyReleaseContractPath := filepath.Join(workDir, "legacy-release-contract.json") + writeFile(t, legacyReleaseContractPath, testCase.legacyReleaseContract) + t.Setenv("GIT_LEGACY_RELEASE_CONTRACT", legacyReleaseContractPath) + } stdout := bytes.Buffer{} stderr := bytes.Buffer{} @@ -216,9 +241,17 @@ fi if [ "$1" = "show" ]; then case "$2" in - dispatcher-v*:cli/dispatcher-contract.json) + dispatcher-v*:dispatcher/dispatcher-contract.json) if [ -n "${GIT_RELEASE_CONTRACT:-}" ]; then cat "$GIT_RELEASE_CONTRACT" + else + echo "fatal: path 'dispatcher/dispatcher-contract.json' exists on disk, but not in '$2'" >&2 + exit 1 + fi + ;; + dispatcher-v*:cli/dispatcher-contract.json) + if [ -n "${GIT_LEGACY_RELEASE_CONTRACT:-}" ]; then + cat "$GIT_LEGACY_RELEASE_CONTRACT" else echo "release not found" >&2 exit 1 diff --git a/cli/internal/automation/dispatcher_version_bump_guard.go b/tools/release-automation/internal/automation/dispatcher_version_bump_guard.go similarity index 87% rename from cli/internal/automation/dispatcher_version_bump_guard.go rename to tools/release-automation/internal/automation/dispatcher_version_bump_guard.go index c0e4a723ad..0283049377 100644 --- a/cli/internal/automation/dispatcher_version_bump_guard.go +++ b/tools/release-automation/internal/automation/dispatcher_version_bump_guard.go @@ -10,18 +10,20 @@ import ( "strconv" "strings" - sharedversion "github.com/hatayama/unity-cli-loop/cli/internal/version" + sharedversion "github.com/hatayama/unity-cli-loop/common/version" ) var dispatcherReleaseInputPatterns = []string{ - "cli/cmd/dispatcher/main.go", - "cli/contract.go", + "dispatcher/cmd/dispatcher/main.go", + "dispatcher/contract.go", dispatcherContractFile, - "cli/internal/clicore/*.go", - "cli/internal/dispatcher/*.go", - "cli/internal/install/*.go", - "cli/internal/uninstall/*.go", - "cli/internal/update/*.go", + // The dispatcher binary depends on every common package (clicontract, clicore, + // project, skills, tools, unityipc, version), so the whole module is a release input. + "common/*/*.go", + "dispatcher/internal/dispatcher/*.go", + "dispatcher/internal/install/*.go", + "dispatcher/internal/uninstall/*.go", + "dispatcher/internal/update/*.go", "scripts/install.ps1", "scripts/install.sh", } @@ -96,7 +98,11 @@ func AnalyzeDispatcherVersionBumpGuardForRefs( return DispatcherVersionBumpGuardResult{}, fmt.Errorf("failed to read head %s: %w", dispatcherContractFile, err) } - baseContent, baseContentErr := protocolMinimumVersionFileAtRef(ctx, repoRoot, config.BaseRef, dispatcherContractFile) + // The base ref may predate the directory split, where the contract lived at + // the legacy cli/ path. Without the fallback every pre-split base would look + // like an initial contract introduction and the bump requirement would be + // silently skipped. + baseContent, baseContentErr := dispatcherContractFileAtRef(ctx, repoRoot, config.BaseRef) baseValues, err := parseDispatcherVersionBumpBaseValues(baseContent, baseContentErr) if err != nil { return DispatcherVersionBumpGuardResult{}, err @@ -124,10 +130,10 @@ func parseDispatcherVersionBumpBaseValues( } func isMissingDispatcherContractAtRefError(err error) bool { - message := err.Error() - quotedPath := "'" + dispatcherContractFile + "'" - return strings.Contains(message, "path "+quotedPath+" exists on disk, but not in") || - strings.Contains(message, "Path "+quotedPath+" does not exist in") + // dispatcherContractFileAtRef falls back to the legacy path, so a base ref + // without any dispatcher contract surfaces as the legacy file missing. + return isMissingFileAtRefError(err, dispatcherContractFile) || + isMissingFileAtRefError(err, legacyDispatcherContractFile) } func AnalyzeDispatcherVersionBumpGuard( @@ -204,7 +210,7 @@ func FormatDispatcherVersionBumpWarning(result DispatcherVersionBumpGuardResult) builder.WriteString(changedInput) builder.WriteString("`\n") } - builder.WriteString("\nUpdate `cli/dispatcher-contract.json` `dispatcherVersion` before merging dispatcher release changes.") + builder.WriteString("\nUpdate `dispatcher/dispatcher-contract.json` `dispatcherVersion` before merging dispatcher release changes.") return builder.String() } diff --git a/cli/internal/automation/dispatcher_version_bump_guard_test.go b/tools/release-automation/internal/automation/dispatcher_version_bump_guard_test.go similarity index 77% rename from cli/internal/automation/dispatcher_version_bump_guard_test.go rename to tools/release-automation/internal/automation/dispatcher_version_bump_guard_test.go index 2f6df64f48..084fc34325 100644 --- a/cli/internal/automation/dispatcher_version_bump_guard_test.go +++ b/tools/release-automation/internal/automation/dispatcher_version_bump_guard_test.go @@ -9,7 +9,7 @@ import ( // Verifies project-runner-only changes do not require a dispatcher release bump. func TestDispatcherVersionBumpGuardPassesWhenDispatcherInputsAreUnchanged(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/internal/projectrunner/compile_wait.go"}, + []string{"project-runner/internal/projectrunner/compile_wait.go"}, DispatcherVersionBumpValues{ HasContract: true, DispatcherVersion: "1.0.0", @@ -49,7 +49,7 @@ func TestDispatcherVersionBumpGuardPassesWhenOnlyReleaseAutomationChanges(t *tes // Verifies dispatcher release inputs require a new dispatcherVersion. func TestDispatcherVersionBumpGuardRequiresDispatcherVersionIncrease(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/internal/dispatcher/run_dispatcher.go"}, + []string{"dispatcher/internal/dispatcher/run_dispatcher.go"}, DispatcherVersionBumpValues{ HasContract: true, DispatcherVersion: "1.0.0", @@ -72,7 +72,7 @@ func TestDispatcherVersionBumpGuardRequiresDispatcherVersionIncrease(t *testing. // Verifies changes under the dispatcher entrypoint package require a new dispatcherVersion. func TestDispatcherVersionBumpGuardCoversDispatcherInternalPackage(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/internal/dispatcher/dispatcher.go"}, + []string{"dispatcher/internal/dispatcher/dispatcher.go"}, DispatcherVersionBumpValues{ HasContract: true, DispatcherVersion: "1.0.0", @@ -92,7 +92,7 @@ func TestDispatcherVersionBumpGuardCoversDispatcherInternalPackage(t *testing.T) // Verifies shared clicore code compiled into the dispatcher binary counts as a dispatcher release input. func TestDispatcherVersionBumpGuardCoversClicorePackage(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/internal/clicore/output.go"}, + []string{"common/clicore/output.go"}, DispatcherVersionBumpValues{ HasContract: true, DispatcherVersion: "1.0.0", @@ -109,10 +109,30 @@ func TestDispatcherVersionBumpGuardCoversClicorePackage(t *testing.T) { } } +// Verifies every common package the dispatcher binary depends on is covered, not just clicore. +func TestDispatcherVersionBumpGuardCoversOtherCommonPackages(t *testing.T) { + result := AnalyzeDispatcherVersionBumpGuard( + []string{"common/version/compare.go"}, + DispatcherVersionBumpValues{ + HasContract: true, + DispatcherVersion: "1.0.0", + DispatcherContractVersion: 1, + }, + DispatcherVersionBumpValues{ + HasContract: true, + DispatcherVersion: "1.0.0", + DispatcherContractVersion: 1, + }) + + if !dispatcherVersionBumpGuardNeedsAction(result) { + t.Fatal("expected common package changes without a version increase to fail") + } +} + // Verifies the first dispatcher contract can be introduced without comparing against a missing base. func TestDispatcherVersionBumpGuardAcceptsInitialDispatcherContract(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/dispatcher-contract.json"}, + []string{"dispatcher/dispatcher-contract.json"}, DispatcherVersionBumpValues{}, DispatcherVersionBumpValues{ HasContract: true, @@ -129,12 +149,22 @@ func TestDispatcherVersionBumpGuardAcceptsInitialDispatcherContract(t *testing.T func TestDispatcherVersionBumpGuardAcceptsMissingBaseContractReadError(t *testing.T) { _, err := parseDispatcherVersionBumpBaseValues( "", - errors.New("git show failed: fatal: path 'cli/dispatcher-contract.json' exists on disk, but not in 'origin/v3-beta'")) + errors.New("git show failed: fatal: path 'dispatcher/dispatcher-contract.json' exists on disk, but not in 'origin/v3-beta'")) if err != nil { t.Fatalf("expected missing base contract to be accepted: %v", err) } } +// Verifies a base ref missing the contract at both the split and the legacy path is treated as initial introduction. +func TestDispatcherVersionBumpGuardAcceptsMissingLegacyBaseContractReadError(t *testing.T) { + _, err := parseDispatcherVersionBumpBaseValues( + "", + errors.New("git show failed: fatal: path 'cli/dispatcher-contract.json' exists on disk, but not in 'origin/v3-beta'")) + if err != nil { + t.Fatalf("expected missing legacy base contract to be accepted: %v", err) + } +} + // Verifies base contract read failures other than missing files do not get silently ignored. func TestDispatcherVersionBumpGuardRejectsUnexpectedBaseContractReadError(t *testing.T) { _, err := parseDispatcherVersionBumpBaseValues("", errors.New("git show failed: gh auth failed")) @@ -142,7 +172,7 @@ func TestDispatcherVersionBumpGuardRejectsUnexpectedBaseContractReadError(t *tes if err == nil { t.Fatal("expected unexpected base contract read error") } - if !strings.Contains(err.Error(), "failed to read base cli/dispatcher-contract.json") { + if !strings.Contains(err.Error(), "failed to read base dispatcher/dispatcher-contract.json") { t.Fatalf("unexpected error: %v", err) } } @@ -150,7 +180,7 @@ func TestDispatcherVersionBumpGuardRejectsUnexpectedBaseContractReadError(t *tes // Verifies dispatcher contract generations cannot move backwards. func TestDispatcherVersionBumpGuardRejectsDispatcherContractVersionDecrease(t *testing.T) { result := AnalyzeDispatcherVersionBumpGuard( - []string{"cli/internal/dispatcher/run_dispatcher.go"}, + []string{"dispatcher/internal/dispatcher/run_dispatcher.go"}, DispatcherVersionBumpValues{ HasContract: true, DispatcherVersion: "1.0.0", diff --git a/cli/internal/automation/ipc_protocol_reminder.go b/tools/release-automation/internal/automation/ipc_protocol_reminder.go similarity index 94% rename from cli/internal/automation/ipc_protocol_reminder.go rename to tools/release-automation/internal/automation/ipc_protocol_reminder.go index 5c8de2997a..d241adad0d 100644 --- a/cli/internal/automation/ipc_protocol_reminder.go +++ b/tools/release-automation/internal/automation/ipc_protocol_reminder.go @@ -11,11 +11,11 @@ import ( ) var ipcProtocolReminderPatterns = []string{ - "cli/contract.json", - "cli/contract.go", - "cli/layout-contract.json", - "cli/internal/unityipc/**", - "cli/internal/tools/**", + "common/clicontract/contract.json", + "common/clicontract/contract.go", + "layout-contract.json", + "common/unityipc/**", + "common/tools/**", "Packages/src/Editor/CompositionRoot/UnityCliLoopFirstPartyServerLifecycleBinding.cs", "Packages/src/Editor/Domain/CliConstants.cs", "Packages/src/Editor/Infrastructure/Api/**", @@ -25,7 +25,7 @@ var ipcProtocolReminderPatterns = []string{ } var ipcProtocolDeclarationPaths = []string{ - "cli/contract.json", + "common/clicontract/contract.json", "Packages/src/Editor/Domain/CliConstants.cs", } @@ -141,7 +141,7 @@ func AppendIPCProtocolReminderSummary(summaryPath string, result IPCProtocolRemi content.WriteString("\n") } if result.NeedsProtocolBumpReview { - content.WriteString("If this PR breaks interoperability with the previous protocol generation, bump both `cli/contract.json` `protocolVersion` and `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION` in the same PR.\n\n") + content.WriteString("If this PR breaks interoperability with the previous protocol generation, bump both `common/clicontract/contract.json` `protocolVersion` and `CliConstants.REQUIRED_CLI_PROTOCOL_VERSION` in the same PR.\n\n") } file, err := os.OpenFile(summaryPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o644) diff --git a/cli/internal/automation/ipc_protocol_reminder_test.go b/tools/release-automation/internal/automation/ipc_protocol_reminder_test.go similarity index 96% rename from cli/internal/automation/ipc_protocol_reminder_test.go rename to tools/release-automation/internal/automation/ipc_protocol_reminder_test.go index bcfe471834..6d58368e75 100644 --- a/cli/internal/automation/ipc_protocol_reminder_test.go +++ b/tools/release-automation/internal/automation/ipc_protocol_reminder_test.go @@ -25,7 +25,7 @@ func TestAnalyzeIPCProtocolReminder_WhenNoIPCFilesChanged_DoesNotRemind(t *testi func TestAnalyzeIPCProtocolReminder_WhenIPCFilesChangedWithoutProtocolDeclarations_Reminds(t *testing.T) { // Verifies IPC-facing changes surface a non-blocking protocol bump review reminder. result := AnalyzeIPCProtocolReminder([]string{ - "cli/internal/unityipc/client.go", + "common/unityipc/client.go", "Packages/src/Editor/Infrastructure/Api/JsonRpcProcessor.cs", }) @@ -40,8 +40,8 @@ func TestAnalyzeIPCProtocolReminder_WhenIPCFilesChangedWithoutProtocolDeclaratio func TestAnalyzeIPCProtocolReminder_WhenProtocolDeclarationsChanged_DoesNotRemind(t *testing.T) { // Verifies explicit protocol declaration edits satisfy the reminder check. result := AnalyzeIPCProtocolReminder([]string{ - "cli/internal/unityipc/client.go", - "cli/contract.json", + "common/unityipc/client.go", + "common/clicontract/contract.json", "Packages/src/Editor/Domain/CliConstants.cs", }) diff --git a/cli/internal/automation/protocol_minimum_version_comment.go b/tools/release-automation/internal/automation/protocol_minimum_version_comment.go similarity index 100% rename from cli/internal/automation/protocol_minimum_version_comment.go rename to tools/release-automation/internal/automation/protocol_minimum_version_comment.go diff --git a/cli/internal/automation/protocol_minimum_version_guard.go b/tools/release-automation/internal/automation/protocol_minimum_version_guard.go similarity index 92% rename from cli/internal/automation/protocol_minimum_version_guard.go rename to tools/release-automation/internal/automation/protocol_minimum_version_guard.go index ae80af5bc0..f310080869 100644 --- a/cli/internal/automation/protocol_minimum_version_guard.go +++ b/tools/release-automation/internal/automation/protocol_minimum_version_guard.go @@ -1,13 +1,12 @@ package automation import ( - "bytes" "context" "encoding/json" + "errors" "fmt" "io" "os" - "os/exec" "path/filepath" "strconv" "strings" @@ -250,9 +249,9 @@ func verifyMinimumCliReleaseProtocolAtRef( Tag: minimumProjectRunnerReleaseTag(values.MinimumProjectRunnerVersion), RequiredAssets: requiredMinimumProjectRunnerAssets, } - contractContent, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, release.Tag, "cli/contract.json") + contractContent, err := runnerContractFileAtRef(ctx, repoRoot, release.Tag) if err != nil { - return "", fmt.Errorf("project runner release %s does not provide cli/contract.json", release.Tag) + return "", errors.New(runnerContractMissingAtReleaseMessage(release.Tag)) } if err := verifyMinimumProjectRunnerReleaseProtocol(release.Tag, values, []byte(contractContent)); err != nil { return "", err @@ -381,7 +380,7 @@ func protocolMinimumVersionBootstrapAllowsUnpublishedProjectRunner( } func protocolMinimumVersionReleaseContractIsMissing(err error) bool { - return strings.Contains(err.Error(), "does not provide cli/contract.json") + return strings.Contains(err.Error(), fmt.Sprintf("does not provide %s or %s", cliContractFile, legacyRunnerContractFile)) } func protocolMinimumVersionValueLabel(values ProtocolMinimumVersionValues) string { @@ -426,7 +425,7 @@ func protocolMinimumProjectRunnerVersionAtRef( repoRoot string, ref string, ) (string, error) { - content, err := protocolMinimumVersionFileAtRef(ctx, repoRoot, ref, cliContractFile) + content, err := runnerContractFileAtRef(ctx, repoRoot, ref) if err != nil { return "", err } @@ -441,41 +440,6 @@ func protocolMinimumProjectRunnerVersionAtRef( return contract.ProjectRunnerVersion, nil } -func protocolMinimumVersionFileAtRef( - ctx context.Context, - repoRoot string, - ref string, - file string, -) (string, error) { - return runProtocolMinimumVersionOutput( - ctx, - repoRoot, - "git", - "-C", - repoRoot, - "show", - ref+":"+file) -} - -func runProtocolMinimumVersionOutput( - ctx context.Context, - workDir string, - name string, - args ...string, -) (string, error) { - command := exec.CommandContext(ctx, name, args...) - command.Dir = filepath.Clean(workDir) - stdout := bytes.Buffer{} - stderr := bytes.Buffer{} - command.Stdout = &stdout - command.Stderr = &stderr - err := command.Run() - if err != nil { - return "", fmt.Errorf("%s %s failed: %w\n%s%s", name, strings.Join(args, " "), err, stderr.String(), stdout.String()) - } - return stdout.String(), nil -} - func writeProtocolMinimumVersionLine(writer io.Writer, values ...any) { // CI status output failures cannot be recovered after the command outcome is known. _, _ = fmt.Fprintln(writer, values...) diff --git a/cli/internal/automation/protocol_minimum_version_guard_test.go b/tools/release-automation/internal/automation/protocol_minimum_version_guard_test.go similarity index 92% rename from cli/internal/automation/protocol_minimum_version_guard_test.go rename to tools/release-automation/internal/automation/protocol_minimum_version_guard_test.go index ca5445692b..0fe4d99e62 100644 --- a/cli/internal/automation/protocol_minimum_version_guard_test.go +++ b/tools/release-automation/internal/automation/protocol_minimum_version_guard_test.go @@ -79,6 +79,23 @@ func TestRunProtocolMinimumVersionGuard_WhenMinimumReleaseMatches_Passes(t *test 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, "uloop-project-runner-v3.0.0-beta.33:common/clicontract/contract.json") +} + +func TestRunProtocolMinimumVersionGuard_WhenMinimumReleasePredatesDirectorySplit_FallsBackToLegacyContractPath(t *testing.T) { + // Verifies project runner releases published before the cli/ directory split are still + // readable through the legacy contract.json path when the new path is missing at the tag. + result := runProtocolMinimumVersionGuardCase(t, protocolMinimumVersionRefCase{ + baseContent: buildProtocolMinimumVersionConstants(1, "3.0.0-beta.32"), + headContent: buildProtocolMinimumVersionConstants(2, "3.0.0-beta.33"), + legacyReleaseContent: `{"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, "uloop-project-runner-v3.0.0-beta.33:common/clicontract/contract.json") assertProtocolMinimumVersionLogContains(t, result.gitLog, "uloop-project-runner-v3.0.0-beta.33:cli/contract.json") } @@ -349,7 +366,7 @@ func TestRunMinimumCliReleaseProtocolCheck_WhenMinimumVersionHasPrefix_Normalize 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, gitLogPath), "uloop-project-runner-v3.0.0-beta.33:common/clicontract/contract.json") assertProtocolMinimumVersionLogContains(t, readFile(t, ghLogPath), "release view uloop-project-runner-v3.0.0-beta.33") } @@ -402,11 +419,12 @@ func TestRunProtocolMinimumVersionComment_WhenMinimumReleaseProtocolDiffers_Upse } type protocolMinimumVersionRefCase struct { - baseContent string - headContent string - headContractContent string - releaseContent string - releaseView string + baseContent string + headContent string + headContractContent string + releaseContent string + legacyReleaseContent string + releaseView string } type protocolMinimumVersionGuardRunResult struct { @@ -533,6 +551,11 @@ func prepareProtocolMinimumVersionGitContents(t *testing.T, workDir string, test writeFile(t, releaseContentPath, testCase.releaseContent) t.Setenv("GIT_RELEASE_CONTENT", releaseContentPath) } + if testCase.legacyReleaseContent != "" { + legacyReleaseContentPath := filepath.Join(workDir, "legacy-release-contract.json") + writeFile(t, legacyReleaseContentPath, testCase.legacyReleaseContent) + t.Setenv("GIT_LEGACY_RELEASE_CONTENT", legacyReleaseContentPath) + } t.Setenv("GIT_BASE_CONTENT", baseContentPath) t.Setenv("GIT_HEAD_CONTENT", headContentPath) } @@ -579,7 +602,7 @@ fi if [ "$1" = "show" ]; then case "$2" in origin/v3-beta:*) cat "$GIT_BASE_CONTENT" ;; - protocol-pr-head:cli/contract.json) + protocol-pr-head:common/clicontract/contract.json) if [ -n "${GIT_HEAD_CONTRACT_CONTENT:-}" ]; then cat "$GIT_HEAD_CONTRACT_CONTENT" else @@ -588,9 +611,17 @@ fi ;; protocol-pr-head:*) cat "$GIT_HEAD_CONTENT" ;; protocol-release:*) cat "$GIT_HEAD_CONTENT" ;; - uloop-project-runner-v*:cli/contract.json) + uloop-project-runner-v*:common/clicontract/contract.json) if [ -n "${GIT_RELEASE_CONTENT:-}" ]; then cat "$GIT_RELEASE_CONTENT" + else + echo "fatal: path 'common/clicontract/contract.json' exists on disk, but not in '$2'" >&2 + exit 1 + fi + ;; + uloop-project-runner-v*:cli/contract.json) + if [ -n "${GIT_LEGACY_RELEASE_CONTENT:-}" ]; then + cat "$GIT_LEGACY_RELEASE_CONTENT" else echo "release not found" >&2 exit 1 diff --git a/cli/internal/automation/protocol_minimum_version_parse.go b/tools/release-automation/internal/automation/protocol_minimum_version_parse.go similarity index 97% rename from cli/internal/automation/protocol_minimum_version_parse.go rename to tools/release-automation/internal/automation/protocol_minimum_version_parse.go index b9eeaa5c9b..298aa351c1 100644 --- a/cli/internal/automation/protocol_minimum_version_parse.go +++ b/tools/release-automation/internal/automation/protocol_minimum_version_parse.go @@ -5,7 +5,7 @@ import ( "regexp" "strconv" - sharedversion "github.com/hatayama/unity-cli-loop/cli/internal/version" + sharedversion "github.com/hatayama/unity-cli-loop/common/version" ) var ( diff --git a/cli/internal/automation/release_pr_body.go b/tools/release-automation/internal/automation/release_pr_body.go similarity index 100% rename from cli/internal/automation/release_pr_body.go rename to tools/release-automation/internal/automation/release_pr_body.go diff --git a/cli/internal/automation/release_pr_checks.go b/tools/release-automation/internal/automation/release_pr_checks.go similarity index 100% rename from cli/internal/automation/release_pr_checks.go rename to tools/release-automation/internal/automation/release_pr_checks.go diff --git a/cli/internal/automation/release_pr_checks_test.go b/tools/release-automation/internal/automation/release_pr_checks_test.go similarity index 99% rename from cli/internal/automation/release_pr_checks_test.go rename to tools/release-automation/internal/automation/release_pr_checks_test.go index 5b770c0331..48058693d5 100644 --- a/cli/internal/automation/release_pr_checks_test.go +++ b/tools/release-automation/internal/automation/release_pr_checks_test.go @@ -466,7 +466,7 @@ fi if [ "$1" = "show" ]; then case "$2" in - uloop-project-runner-v*:cli/contract.json) + uloop-project-runner-v*:common/clicontract/contract.json) if [ -n "${GIT_RELEASE_CONTRACT:-}" ]; then cat "$GIT_RELEASE_CONTRACT" else @@ -568,7 +568,7 @@ if "%~1"=="fetch" if "%~2"=="origin" exit /b 0 if "%~1"=="switch" if "%~2"=="--detach" if "%~3"=="FETCH_HEAD" exit /b 0 if "%~1"=="show" ( - echo %~2| findstr /r "^uloop-project-runner-v.*:cli/contract.json$" >nul + echo %~2| findstr /r "^uloop-project-runner-v.*:common/clicontract/contract.json$" >nul if not errorlevel 1 ( if not "%GIT_RELEASE_CONTRACT%"=="" ( type "%GIT_RELEASE_CONTRACT%"