Skip to content

Commit d8e37db

Browse files
Allow2CEOruvnet
andcommitted
Add CI/CD workflows for Unity SDK
- ci.yml: .NET 8 compilation check with Unity engine stubs, package.json validation - release.yml: GitHub Release on v* tags with UPM install instructions and OpenUPM notification - version-check.yml: PR gate ensuring package.json version bump when Runtime/ files change Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 1d5f197 commit d8e37db

File tree

3 files changed

+719
-0
lines changed

3 files changed

+719
-0
lines changed

.github/workflows/ci.yml

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [master]
6+
pull_request:
7+
branches: [master]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build-test:
14+
name: Build & Test (C# compilation check)
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Setup .NET 8
20+
uses: actions/setup-dotnet@v4
21+
with:
22+
dotnet-version: '8.0.x'
23+
24+
- name: Create Unity stub project for compilation
25+
run: |
26+
mkdir -p .ci-build
27+
28+
# Create a .csproj that references all Runtime .cs files
29+
# with Unity Engine stubs so pure C# logic compiles
30+
cat > .ci-build/Allow2.SDK.CI.csproj << 'CSPROJ'
31+
<Project Sdk="Microsoft.NET.Sdk">
32+
<PropertyGroup>
33+
<TargetFramework>net8.0</TargetFramework>
34+
<Nullable>disable</Nullable>
35+
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
36+
<!-- Suppress warnings about Unity stubs -->
37+
<NoWarn>CS0649;CS0414;CS0169;CS0067</NoWarn>
38+
</PropertyGroup>
39+
<ItemGroup>
40+
<Compile Include="../com.allow2.sdk/Runtime/**/*.cs" />
41+
<Compile Include="UnityStubs.cs" />
42+
</ItemGroup>
43+
</Project>
44+
CSPROJ
45+
46+
# Create minimal Unity Engine stubs so the code compiles
47+
cat > .ci-build/UnityStubs.cs << 'STUBS'
48+
// Minimal Unity Engine stubs for CI compilation checks.
49+
// These provide just enough type surface for the SDK's pure C#
50+
// to compile outside of the Unity Editor.
51+
52+
using System;
53+
using System.Collections;
54+
using System.Text;
55+
56+
namespace UnityEngine
57+
{
58+
public class Object { }
59+
60+
public class MonoBehaviour : Behaviour
61+
{
62+
public Coroutine StartCoroutine(IEnumerator routine) => new Coroutine();
63+
public void StopCoroutine(Coroutine routine) { }
64+
}
65+
66+
public class Behaviour : Component
67+
{
68+
public bool enabled { get; set; }
69+
}
70+
71+
public class Component : Object
72+
{
73+
public GameObject gameObject => null;
74+
public Transform transform => null;
75+
}
76+
77+
public class GameObject : Object
78+
{
79+
public string name { get; set; }
80+
public T AddComponent<T>() where T : Component => default;
81+
public T GetComponent<T>() => default;
82+
public static void DontDestroyOnLoad(Object target) { }
83+
public GameObject(string name) { }
84+
}
85+
86+
public class Transform : Component { }
87+
88+
public class ScriptableObject : Object { }
89+
90+
public class Coroutine { }
91+
92+
public class WaitForSeconds
93+
{
94+
public WaitForSeconds(float seconds) { }
95+
}
96+
97+
public class WaitForSecondsRealtime : CustomYieldInstruction
98+
{
99+
public WaitForSecondsRealtime(float seconds) { }
100+
public override bool keepWaiting => false;
101+
}
102+
103+
public abstract class CustomYieldInstruction : IEnumerator
104+
{
105+
public abstract bool keepWaiting { get; }
106+
public object Current => null;
107+
public bool MoveNext() => keepWaiting;
108+
public void Reset() { }
109+
}
110+
111+
public static class Debug
112+
{
113+
public static void Log(object message) { }
114+
public static void LogWarning(object message) { }
115+
public static void LogError(object message) { }
116+
}
117+
118+
public static class PlayerPrefs
119+
{
120+
public static string GetString(string key, string defaultValue = "") => defaultValue;
121+
public static void SetString(string key, string value) { }
122+
public static int GetInt(string key, int defaultValue = 0) => defaultValue;
123+
public static void SetInt(string key, int value) { }
124+
public static void DeleteKey(string key) { }
125+
public static void Save() { }
126+
public static bool HasKey(string key) => false;
127+
}
128+
129+
public static class JsonUtility
130+
{
131+
public static string ToJson(object obj) => "{}";
132+
public static T FromJson<T>(string json) => default;
133+
}
134+
135+
public static class Application
136+
{
137+
public static RuntimePlatform platform => RuntimePlatform.LinuxPlayer;
138+
public static string productName => "CI";
139+
public static string version => "0.0.0";
140+
public static string unityVersion => "2021.3.0f1";
141+
}
142+
143+
public enum RuntimePlatform
144+
{
145+
LinuxPlayer, WindowsPlayer, OSXPlayer, Android, IPhonePlayer, WebGLPlayer
146+
}
147+
148+
[AttributeUsage(AttributeTargets.Field)]
149+
public class SerializeFieldAttribute : Attribute { }
150+
151+
[AttributeUsage(AttributeTargets.Field)]
152+
public class HeaderAttribute : Attribute
153+
{
154+
public HeaderAttribute(string header) { }
155+
}
156+
157+
[AttributeUsage(AttributeTargets.Field)]
158+
public class TooltipAttribute : Attribute
159+
{
160+
public TooltipAttribute(string tooltip) { }
161+
}
162+
163+
[AttributeUsage(AttributeTargets.Field)]
164+
public class SpaceAttribute : Attribute
165+
{
166+
public SpaceAttribute() { }
167+
public SpaceAttribute(float height) { }
168+
}
169+
170+
[AttributeUsage(AttributeTargets.Field)]
171+
public class TextAreaAttribute : Attribute
172+
{
173+
public TextAreaAttribute() { }
174+
public TextAreaAttribute(int minLines, int maxLines) { }
175+
}
176+
177+
[AttributeUsage(AttributeTargets.Field)]
178+
public class RangeAttribute : Attribute
179+
{
180+
public RangeAttribute(float min, float max) { }
181+
}
182+
}
183+
184+
namespace UnityEngine.Events
185+
{
186+
public class UnityEvent
187+
{
188+
public void Invoke() { }
189+
public void AddListener(Action call) { }
190+
public void RemoveListener(Action call) { }
191+
}
192+
193+
public class UnityEvent<T0> : UnityEvent
194+
{
195+
public void Invoke(T0 arg0) { }
196+
public void AddListener(Action<T0> call) { }
197+
public void RemoveListener(Action<T0> call) { }
198+
}
199+
200+
public class UnityEvent<T0, T1> : UnityEvent
201+
{
202+
public void Invoke(T0 arg0, T1 arg1) { }
203+
public void AddListener(Action<T0, T1> call) { }
204+
public void RemoveListener(Action<T0, T1> call) { }
205+
}
206+
207+
public class UnityEvent<T0, T1, T2> : UnityEvent
208+
{
209+
public void Invoke(T0 arg0, T1 arg1, T2 arg2) { }
210+
public void AddListener(Action<T0, T1, T2> call) { }
211+
public void RemoveListener(Action<T0, T1, T2> call) { }
212+
}
213+
}
214+
215+
namespace UnityEngine.Networking
216+
{
217+
public class UnityWebRequest : IDisposable
218+
{
219+
public string url { get; set; }
220+
public string method { get; set; }
221+
public long responseCode { get; }
222+
public Result result { get; }
223+
public DownloadHandler downloadHandler { get; set; }
224+
public UploadHandler uploadHandler { get; set; }
225+
226+
public UnityWebRequest(string url, string method) { }
227+
228+
public static UnityWebRequest Get(string uri) => new UnityWebRequest(uri, "GET");
229+
public static string EscapeURL(string s) => Uri.EscapeDataString(s);
230+
231+
public void SetRequestHeader(string name, string value) { }
232+
public UnityWebRequestAsyncOperation SendWebRequest() => new UnityWebRequestAsyncOperation();
233+
public void Dispose() { }
234+
235+
public enum Result { InProgress, Success, ConnectionError, ProtocolError, DataProcessingError }
236+
}
237+
238+
public class UnityWebRequestAsyncOperation : AsyncOperation { }
239+
240+
public class AsyncOperation : YieldInstruction
241+
{
242+
public bool isDone { get; }
243+
}
244+
245+
public class YieldInstruction { }
246+
247+
public class DownloadHandler : IDisposable
248+
{
249+
public virtual string text => "";
250+
public virtual byte[] data => Array.Empty<byte>();
251+
public void Dispose() { }
252+
}
253+
254+
public class DownloadHandlerBuffer : DownloadHandler
255+
{
256+
public DownloadHandlerBuffer() { }
257+
}
258+
259+
public class UploadHandler : IDisposable
260+
{
261+
public string contentType { get; set; }
262+
public void Dispose() { }
263+
}
264+
265+
public class UploadHandlerRaw : UploadHandler
266+
{
267+
public UploadHandlerRaw(byte[] data) { }
268+
}
269+
}
270+
STUBS
271+
272+
- name: Restore .NET packages
273+
run: dotnet restore .ci-build/Allow2.SDK.CI.csproj
274+
275+
- name: Build SDK
276+
run: dotnet build .ci-build/Allow2.SDK.CI.csproj --configuration Release --no-restore
277+
278+
- name: Run tests (if present)
279+
run: |
280+
if compgen -G "com.allow2.sdk/Tests/**/*.cs" > /dev/null 2>&1; then
281+
echo "Test files found — running tests"
282+
dotnet test .ci-build/Allow2.SDK.CI.csproj --no-build --configuration Release
283+
else
284+
echo "No test files found in com.allow2.sdk/Tests/ — skipping"
285+
fi
286+
287+
validate-package:
288+
name: Validate package.json
289+
runs-on: ubuntu-latest
290+
steps:
291+
- uses: actions/checkout@v4
292+
293+
- name: Validate package.json structure
294+
run: |
295+
PKG="com.allow2.sdk/package.json"
296+
297+
if [ ! -f "$PKG" ]; then
298+
echo "::error::package.json not found at $PKG"
299+
exit 1
300+
fi
301+
302+
# Validate it's valid JSON
303+
if ! python3 -c "import json; json.load(open('$PKG'))"; then
304+
echo "::error::package.json is not valid JSON"
305+
exit 1
306+
fi
307+
308+
# Check required fields
309+
for field in name version displayName description unity author license; do
310+
if ! python3 -c "
311+
import json, sys
312+
pkg = json.load(open('$PKG'))
313+
if '$field' not in pkg or not pkg['$field']:
314+
print(f'::error::Missing required field: $field')
315+
sys.exit(1)
316+
"; then
317+
exit 1
318+
fi
319+
done
320+
321+
# Verify package name matches expected
322+
NAME=$(python3 -c "import json; print(json.load(open('$PKG'))['name'])")
323+
if [ "$NAME" != "com.allow2.sdk" ]; then
324+
echo "::error::Package name should be 'com.allow2.sdk', got '$NAME'"
325+
exit 1
326+
fi
327+
328+
VERSION=$(python3 -c "import json; print(json.load(open('$PKG'))['version'])")
329+
echo "Package: $NAME v$VERSION"
330+
echo "package-version=$VERSION" >> "$GITHUB_OUTPUT"
331+
332+
- name: Check version matches tag (on tag push only)
333+
if: startsWith(github.ref, 'refs/tags/v')
334+
run: |
335+
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
336+
PKG_VERSION=$(python3 -c "import json; print(json.load(open('com.allow2.sdk/package.json'))['version'])")
337+
338+
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
339+
echo "::error::Tag version ($TAG_VERSION) does not match package.json version ($PKG_VERSION)"
340+
exit 1
341+
fi
342+
343+
echo "Version match confirmed: v$PKG_VERSION"

0 commit comments

Comments
 (0)