com.robotsquid.squidhub 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/Editor/SquidHubBuildProcessor.cs +36 -0
- package/Editor/SquidHubBuildScan.cs +23 -0
- package/Editor/SquidHubBuildWindow.cs +291 -0
- package/Editor/SquidHubBuildWindow.cs.meta +11 -0
- package/Editor/SquidHubJson.cs +346 -0
- package/Editor/SquidHubJson.cs.meta +11 -0
- package/Editor/SquidHubManifest.cs +355 -0
- package/Editor/SquidHubManifest.cs.meta +11 -0
- package/Editor/SquidHubManifestWindow.cs +292 -0
- package/Editor/SquidHubManifestWindow.cs.meta +11 -0
- package/Editor/SquidHubPump.cs +53 -0
- package/Editor/SquidHubPump.cs.meta +11 -0
- package/Editor/SquidHubUploadSettings.cs +40 -0
- package/Editor/SquidHubUploader.cs +159 -38
- package/README.md +45 -12
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Running a coroutine outside a Unity player loop.
|
|
2
|
+
//
|
|
3
|
+
// ⚠️ Pure System.* — no UnityEditor — so the nesting rule below is covered by
|
|
4
|
+
// tests under plain dotnet. It exists because getting this wrong produced a
|
|
5
|
+
// completely silent failure: the upload menu item ran, reported nothing, and
|
|
6
|
+
// uploaded nothing.
|
|
7
|
+
using System;
|
|
8
|
+
using System.Collections;
|
|
9
|
+
using System.Collections.Generic;
|
|
10
|
+
|
|
11
|
+
namespace SquidHub.Editor
|
|
12
|
+
{
|
|
13
|
+
/// <summary>
|
|
14
|
+
/// A coroutine driver that understands NESTED iterators.
|
|
15
|
+
///
|
|
16
|
+
/// ⚠️ `yield return SomeOtherIEnumerator()` is the whole reason this is not
|
|
17
|
+
/// three lines. Unity's own runner descends into a yielded IEnumerator and
|
|
18
|
+
/// runs it to completion before resuming the parent. A naive driver that
|
|
19
|
+
/// only calls MoveNext() on the outer routine treats the nested one as an
|
|
20
|
+
/// ordinary value, discards it, and carries straight on — so the nested work
|
|
21
|
+
/// NEVER RUNS and nothing anywhere reports a problem.
|
|
22
|
+
/// </summary>
|
|
23
|
+
internal sealed class SquidHubPump
|
|
24
|
+
{
|
|
25
|
+
private readonly Stack<IEnumerator> _stack = new Stack<IEnumerator>();
|
|
26
|
+
|
|
27
|
+
public SquidHubPump(IEnumerator routine)
|
|
28
|
+
{
|
|
29
|
+
_stack.Push(routine);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public bool Done => _stack.Count == 0;
|
|
33
|
+
|
|
34
|
+
/// <summary>Advances by one step. Returns false once everything has finished.</summary>
|
|
35
|
+
public bool Step()
|
|
36
|
+
{
|
|
37
|
+
if (_stack.Count == 0) return false;
|
|
38
|
+
|
|
39
|
+
var current = _stack.Peek();
|
|
40
|
+
if (!current.MoveNext())
|
|
41
|
+
{
|
|
42
|
+
// ⚠️ Pop and return true, not false. The PARENT still has work
|
|
43
|
+
// left; reporting "done" because a child finished would abandon
|
|
44
|
+
// the rest of the upload silently.
|
|
45
|
+
_stack.Pop();
|
|
46
|
+
return _stack.Count > 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (current.Current is IEnumerator nested) _stack.Push(nested);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -36,7 +36,47 @@ namespace SquidHub.Editor
|
|
|
36
36
|
set => EditorPrefs.SetString(Scope + "buildFolder", value);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/// <summary>
|
|
40
|
+
/// The build profile `Build and Upload` uses, as an asset path.
|
|
41
|
+
///
|
|
42
|
+
/// ⚠️ A PATH, not a cached object reference. EditorPrefs cannot hold one,
|
|
43
|
+
/// and a GUID would survive the profile being deleted as a dangling id
|
|
44
|
+
/// that resolves to nothing with no way to say what is missing.
|
|
45
|
+
/// </summary>
|
|
46
|
+
public static string BuildProfilePath
|
|
47
|
+
{
|
|
48
|
+
get => EditorPrefs.GetString(Scope + "buildProfile", "");
|
|
49
|
+
set => EditorPrefs.SetString(Scope + "buildProfile", value ?? "");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// <summary>
|
|
53
|
+
/// Publish the build to players the moment it uploads.
|
|
54
|
+
///
|
|
55
|
+
/// ⚠️ ON by default, and it only ever applies on the DEV environment.
|
|
56
|
+
/// The server refuses a key-authorised publish on prod — promoting a
|
|
57
|
+
/// build for every player is an owner decision made in the console, and
|
|
58
|
+
/// an upload key is not a person. See `setChannelWithKey`.
|
|
59
|
+
/// </summary>
|
|
60
|
+
public static bool PublishAfterUpload
|
|
61
|
+
{
|
|
62
|
+
get => EditorPrefs.GetBool(Scope + "publishAfterUpload", true);
|
|
63
|
+
set => EditorPrefs.SetBool(Scope + "publishAfterUpload", value);
|
|
64
|
+
}
|
|
65
|
+
|
|
39
66
|
public static bool HasKey => Key.StartsWith("shu_");
|
|
67
|
+
|
|
68
|
+
/// <summary>
|
|
69
|
+
/// `manifest.json` beside `Assets/`, which is what `squidhub-release
|
|
70
|
+
/// build --manifest manifest.json` already reads.
|
|
71
|
+
///
|
|
72
|
+
/// ⚠️ ONE source of truth, deliberately. Keeping the declaration in a
|
|
73
|
+
/// Unity-only asset would give the Editor and the CLI two answers to
|
|
74
|
+
/// "what does this game declare", and they would disagree on the day it
|
|
75
|
+
/// mattered. It is a normal project file and belongs in the repository.
|
|
76
|
+
/// </summary>
|
|
77
|
+
public static string ManifestPath =>
|
|
78
|
+
System.IO.Path.Combine(
|
|
79
|
+
System.IO.Path.GetDirectoryName(Application.dataPath) ?? "", "manifest.json");
|
|
40
80
|
}
|
|
41
81
|
|
|
42
82
|
internal sealed class SquidHubUploadSettingsWindow : EditorWindow
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
using System;
|
|
12
12
|
using System.Collections;
|
|
13
13
|
using System.IO;
|
|
14
|
+
using System.Linq;
|
|
14
15
|
using System.Text;
|
|
15
16
|
using System.Text.RegularExpressions;
|
|
16
17
|
using UnityEditor;
|
|
@@ -21,25 +22,44 @@ namespace SquidHub.Editor
|
|
|
21
22
|
{
|
|
22
23
|
internal static class SquidHubUploader
|
|
23
24
|
{
|
|
24
|
-
private static
|
|
25
|
+
private static SquidHubPump _pump;
|
|
25
26
|
|
|
26
27
|
[MenuItem("SquidHub/Upload Build…", priority = 11)]
|
|
27
28
|
private static void UploadBuild()
|
|
28
29
|
{
|
|
29
|
-
if (!
|
|
30
|
-
{
|
|
31
|
-
EditorUtility.DisplayDialog("SquidHub",
|
|
32
|
-
"No upload key set. Create one in the publisher console under your game → " +
|
|
33
|
-
"Upload keys, then paste it into SquidHub → Upload Settings.", "OK");
|
|
34
|
-
SquidHubUploadSettingsWindow.GetWindow<SquidHubUploadSettingsWindow>();
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
30
|
+
if (!EnsureKey()) return;
|
|
37
31
|
|
|
38
32
|
var start = SquidHubUploadSettings.BuildFolder;
|
|
39
33
|
var folder = EditorUtility.OpenFolderPanel(
|
|
40
34
|
"Select the WebGL build folder (containing index.html)",
|
|
41
35
|
string.IsNullOrEmpty(start) ? "" : start, "");
|
|
42
36
|
if (string.IsNullOrEmpty(folder)) return;
|
|
37
|
+
|
|
38
|
+
Begin(folder);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// <summary>Nothing can proceed without a key, and saying so early costs nothing.</summary>
|
|
42
|
+
internal static bool EnsureKey()
|
|
43
|
+
{
|
|
44
|
+
if (SquidHubUploadSettings.HasKey) return true;
|
|
45
|
+
|
|
46
|
+
EditorUtility.DisplayDialog("SquidHub",
|
|
47
|
+
"No upload key set. Create one in the publisher console under your game → " +
|
|
48
|
+
"Upload keys, then paste it into SquidHub → Upload Settings.", "OK");
|
|
49
|
+
SquidHubUploadSettingsWindow.GetWindow<SquidHubUploadSettingsWindow>();
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// <summary>
|
|
54
|
+
/// Scans a build folder, confirms, and uploads it.
|
|
55
|
+
///
|
|
56
|
+
/// ⚠️ The entry point `Build and Upload` uses too, so a build made here
|
|
57
|
+
/// and a folder picked by hand go through exactly the same refusals.
|
|
58
|
+
/// </summary>
|
|
59
|
+
internal static void Begin(string folder)
|
|
60
|
+
{
|
|
61
|
+
if (!EnsureKey()) return;
|
|
62
|
+
|
|
43
63
|
SquidHubUploadSettings.BuildFolder = folder;
|
|
44
64
|
|
|
45
65
|
BuildScan scan;
|
|
@@ -63,25 +83,44 @@ namespace SquidHub.Editor
|
|
|
63
83
|
return;
|
|
64
84
|
}
|
|
65
85
|
|
|
86
|
+
var publish = SquidHubUploadSettings.PublishAfterUpload;
|
|
87
|
+
|
|
66
88
|
if (!EditorUtility.DisplayDialog("SquidHub",
|
|
67
89
|
$"Upload {scan.Files.Count} files ({scan.TotalBytes / 1024f / 1024f:F1} MB)?" +
|
|
68
90
|
(scan.Skipped.Count > 0
|
|
69
91
|
? $"\n\n{scan.Skipped.Count} OS metadata file(s) will be skipped."
|
|
92
|
+
: "") +
|
|
93
|
+
// ⚠️ Said before the upload, not after. A build folder with no
|
|
94
|
+
// manifest.json declares nothing — save schema 1, no display
|
|
95
|
+
// constraints — and the publisher cannot tell from the result.
|
|
96
|
+
(scan.Files.Any(f => f.Path == "manifest.json")
|
|
97
|
+
? ""
|
|
98
|
+
: "\n\nThere is no manifest.json in this folder, so the build declares " +
|
|
99
|
+
"nothing: save schema 1 and no display constraints. Set one up in " +
|
|
100
|
+
"SquidHub → Game Manifest… and rebuild.") +
|
|
101
|
+
(publish
|
|
102
|
+
? "\n\nIt will be published to players if the server allows it — that is " +
|
|
103
|
+
"dev only. On prod, promote it in the console."
|
|
70
104
|
: ""),
|
|
71
105
|
"Upload", "Cancel"))
|
|
72
106
|
{
|
|
73
107
|
return;
|
|
74
108
|
}
|
|
75
109
|
|
|
76
|
-
Run(Upload(scan));
|
|
110
|
+
Run(Upload(scan, publish));
|
|
77
111
|
}
|
|
78
112
|
|
|
79
113
|
// ⚠️ Driven by EditorApplication.update rather than blocking. Sleeping
|
|
80
114
|
// the main thread would stop UnityWebRequest progressing at all, and the
|
|
81
115
|
// Editor would appear to hang for the length of the upload.
|
|
116
|
+
//
|
|
117
|
+
// ⚠️ SquidHubPump, not a bare MoveNext(). `yield return SomeIEnumerator()`
|
|
118
|
+
// must DESCEND into the nested routine — a driver that does not simply
|
|
119
|
+
// discards it and carries on, which made the entire upload run, report
|
|
120
|
+
// nothing and do nothing. That nesting rule is unit-tested.
|
|
82
121
|
private static void Run(IEnumerator routine)
|
|
83
122
|
{
|
|
84
|
-
|
|
123
|
+
_pump = new SquidHubPump(routine);
|
|
85
124
|
EditorApplication.update -= Pump;
|
|
86
125
|
EditorApplication.update += Pump;
|
|
87
126
|
}
|
|
@@ -90,41 +129,50 @@ namespace SquidHub.Editor
|
|
|
90
129
|
{
|
|
91
130
|
try
|
|
92
131
|
{
|
|
93
|
-
if (
|
|
132
|
+
if (_pump != null && _pump.Step()) return;
|
|
94
133
|
}
|
|
95
134
|
catch (Exception e)
|
|
96
135
|
{
|
|
97
136
|
EditorUtility.ClearProgressBar();
|
|
98
137
|
Debug.LogError("[SquidHub] upload failed: " + e);
|
|
138
|
+
EditorUtility.DisplayDialog("SquidHub — upload failed", e.Message, "OK");
|
|
99
139
|
}
|
|
100
|
-
|
|
140
|
+
_pump = null;
|
|
101
141
|
EditorApplication.update -= Pump;
|
|
102
142
|
EditorUtility.ClearProgressBar();
|
|
143
|
+
Debug.Log("[SquidHub] upload run finished.");
|
|
103
144
|
}
|
|
104
145
|
|
|
105
|
-
private static IEnumerator Upload(BuildScan scan)
|
|
146
|
+
private static IEnumerator Upload(BuildScan scan, bool publish)
|
|
106
147
|
{
|
|
107
148
|
var endpoint = SquidHubUploadSettings.Endpoint;
|
|
108
149
|
var key = SquidHubUploadSettings.Key;
|
|
109
150
|
|
|
151
|
+
Debug.Log($"[SquidHub] uploading {scan.Files.Count} file(s) to {endpoint}");
|
|
110
152
|
EditorUtility.DisplayProgressBar("SquidHub", "Registering build…", 0f);
|
|
111
153
|
|
|
112
154
|
var startBody = "{\"intent\":\"start\",\"files\":" + SquidHubBuildScan.FilesJson(scan.Files) + "}";
|
|
113
155
|
string startResponse = null;
|
|
114
156
|
yield return Post(endpoint + "/api/ingest", key, startBody, r => startResponse = r);
|
|
115
157
|
|
|
116
|
-
|
|
158
|
+
// ⚠️ Post reports its own failure and leaves this null. Returning
|
|
159
|
+
// quietly here is what made a failed start look like success.
|
|
160
|
+
if (startResponse == null)
|
|
161
|
+
{
|
|
162
|
+
Debug.LogError("[SquidHub] the start request returned nothing — upload abandoned.");
|
|
163
|
+
yield break;
|
|
164
|
+
}
|
|
117
165
|
|
|
118
|
-
if (
|
|
166
|
+
if (SquidHubBuildScan.JsonField(startResponse, "alreadyPublished") == "true")
|
|
119
167
|
{
|
|
120
168
|
EditorUtility.ClearProgressBar();
|
|
121
169
|
EditorUtility.DisplayDialog("SquidHub",
|
|
122
|
-
"These exact bytes are already published as " +
|
|
170
|
+
"These exact bytes are already published as " + SquidHubBuildScan.JsonField(startResponse, "buildHash") +
|
|
123
171
|
". Nothing to upload.", "OK");
|
|
124
172
|
yield break;
|
|
125
173
|
}
|
|
126
174
|
|
|
127
|
-
var jobId =
|
|
175
|
+
var jobId = SquidHubBuildScan.JsonField(startResponse, "jobId");
|
|
128
176
|
if (string.IsNullOrEmpty(jobId))
|
|
129
177
|
{
|
|
130
178
|
Fail("The server did not start a build job", startResponse);
|
|
@@ -167,18 +215,104 @@ namespace SquidHub.Editor
|
|
|
167
215
|
EditorUtility.ClearProgressBar();
|
|
168
216
|
if (doneResponse == null) yield break;
|
|
169
217
|
|
|
170
|
-
if (
|
|
218
|
+
if (SquidHubBuildScan.JsonField(doneResponse, "ok") != "true")
|
|
171
219
|
{
|
|
172
220
|
Fail("The server refused the build", doneResponse);
|
|
173
221
|
yield break;
|
|
174
222
|
}
|
|
175
223
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
224
|
+
var buildHash = SquidHubBuildScan.JsonField(doneResponse, "buildHash");
|
|
225
|
+
|
|
226
|
+
// ⚠️ Surfaced, not swallowed. The server warns rather than fails when
|
|
227
|
+
// a manifest change had no effect, and that warning is the only thing
|
|
228
|
+
// standing between a publisher and believing their edit shipped.
|
|
229
|
+
var warning = SquidHubBuildScan.JsonField(doneResponse, "detail");
|
|
230
|
+
if (warning != null) Debug.LogWarning("[SquidHub] " + warning);
|
|
231
|
+
|
|
232
|
+
if (!publish)
|
|
233
|
+
{
|
|
234
|
+
// ⚠️ Registered, NOT live. Promoting a build to players is a
|
|
235
|
+
// separate, deliberate act — saying "uploaded" without saying
|
|
236
|
+
// that invites someone to assume their players already have it.
|
|
237
|
+
EditorUtility.DisplayDialog("SquidHub",
|
|
238
|
+
"Registered " + buildHash + ".\n\n" +
|
|
239
|
+
"It is not live yet — promote it in the publisher console when you are ready.",
|
|
240
|
+
"OK");
|
|
241
|
+
yield break;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
yield return PublishStep(endpoint, key, buildHash);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// <summary>
|
|
248
|
+
/// Asks the server to make the build live.
|
|
249
|
+
///
|
|
250
|
+
/// ⚠️ THE SERVER DECIDES, and it refuses on prod. An upload key lives in
|
|
251
|
+
/// CI configs and Editor preferences; promoting a build for every player
|
|
252
|
+
/// is an owner decision made by a person in the console. A refusal here
|
|
253
|
+
/// is the system working, so it reports as information rather than as a
|
|
254
|
+
/// failure — the upload really did succeed.
|
|
255
|
+
/// </summary>
|
|
256
|
+
private static IEnumerator PublishStep(string endpoint, string key, string buildHash)
|
|
257
|
+
{
|
|
258
|
+
if (string.IsNullOrEmpty(buildHash))
|
|
259
|
+
{
|
|
260
|
+
Debug.LogError("[SquidHub] the server registered the build but named no buildHash, " +
|
|
261
|
+
"so it cannot be published. It is uploaded; promote it in the console.");
|
|
262
|
+
yield break;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
EditorUtility.DisplayProgressBar("SquidHub", "Publishing…", 0.995f);
|
|
266
|
+
|
|
267
|
+
string response = null;
|
|
268
|
+
yield return Post(endpoint + "/api/ingest",
|
|
269
|
+
key,
|
|
270
|
+
"{\"intent\":\"publish\",\"buildHash\":" + SquidHubBuildScan.JsonString(buildHash) + "}",
|
|
271
|
+
r => response = r);
|
|
272
|
+
|
|
273
|
+
EditorUtility.ClearProgressBar();
|
|
274
|
+
|
|
275
|
+
// ⚠️ Post already reported the failure. Reaching here with null means
|
|
276
|
+
// the publish did not happen, and the build IS still uploaded — which
|
|
277
|
+
// is the one thing the publisher needs to be told.
|
|
278
|
+
if (response == null)
|
|
279
|
+
{
|
|
280
|
+
Debug.LogWarning("[SquidHub] " + buildHash + " is uploaded but was NOT published.");
|
|
281
|
+
yield break;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (SquidHubBuildScan.JsonField(response, "ok") == "true")
|
|
285
|
+
{
|
|
286
|
+
var stranded = SquidHubBuildScan.JsonField(response, "strandedPlayers");
|
|
287
|
+
Debug.Log("[SquidHub] published " + buildHash + ".");
|
|
288
|
+
EditorUtility.DisplayDialog("SquidHub",
|
|
289
|
+
"Published " + buildHash + ". Players get it on their next launch." +
|
|
290
|
+
(stranded != null && stranded != "0"
|
|
291
|
+
? "\n\n⚠️ " + stranded + " player(s) have a save this build's schema cannot read."
|
|
292
|
+
: ""),
|
|
293
|
+
"OK");
|
|
294
|
+
yield break;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
var code = SquidHubBuildScan.JsonField(response, "code");
|
|
298
|
+
var message = SquidHubBuildScan.JsonField(response, "message") ?? response;
|
|
299
|
+
|
|
300
|
+
if (code == "MANUAL_ON_PROD")
|
|
301
|
+
{
|
|
302
|
+
Debug.Log("[SquidHub] uploaded " + buildHash + "; publishing on prod is manual.");
|
|
303
|
+
EditorUtility.DisplayDialog("SquidHub",
|
|
304
|
+
"Uploaded " + buildHash + ".\n\n" +
|
|
305
|
+
"It was not published: an upload key can publish on dev, never on prod. " +
|
|
306
|
+
"Promote it in the publisher console when you are ready.",
|
|
307
|
+
"OK");
|
|
308
|
+
yield break;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
Debug.LogWarning("[SquidHub] uploaded " + buildHash + " but the publish was refused: " +
|
|
312
|
+
code + ": " + message);
|
|
313
|
+
EditorUtility.DisplayDialog("SquidHub — uploaded, not published",
|
|
314
|
+
"The build uploaded and is registered as " + buildHash + ", but publishing it was " +
|
|
315
|
+
"refused:\n\n" + message + "\n\nPromote it in the publisher console.",
|
|
182
316
|
"OK");
|
|
183
317
|
}
|
|
184
318
|
|
|
@@ -214,18 +348,5 @@ namespace SquidHub.Editor
|
|
|
214
348
|
EditorUtility.DisplayDialog("SquidHub — " + title, detail, "OK");
|
|
215
349
|
}
|
|
216
350
|
|
|
217
|
-
/// <summary>
|
|
218
|
-
/// ⚠️ A regex, not a JSON parser, and only for the four flat fields this
|
|
219
|
-
/// flow reads. Shipping a parser in a package that already asks the
|
|
220
|
-
/// project to bring its own would be a second, worse one to maintain.
|
|
221
|
-
/// </summary>
|
|
222
|
-
private static string Field(string json, string name)
|
|
223
|
-
{
|
|
224
|
-
var match = Regex.Match(json, "\"" + Regex.Escape(name) + "\"\\s*:\\s*(\"([^\"]*)\"|true|false|[0-9]+)");
|
|
225
|
-
if (!match.Success) return null;
|
|
226
|
-
return match.Groups[2].Success && match.Value.Contains("\"" + match.Groups[2].Value + "\"")
|
|
227
|
-
? match.Groups[2].Value
|
|
228
|
-
: match.Groups[1].Value;
|
|
229
|
-
}
|
|
230
351
|
}
|
|
231
352
|
}
|
package/README.md
CHANGED
|
@@ -82,23 +82,55 @@ Then push the folder:
|
|
|
82
82
|
node src/cli.mjs push --game <slug> --dir <BuildFolder> --env=dev --verify
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
## Declaring what your game is
|
|
86
|
+
|
|
87
|
+
`SquidHub → Game Manifest…` edits `manifest.json` beside `Assets/` — the game's
|
|
88
|
+
id and title, which capabilities it uses, its save schema version, the
|
|
89
|
+
orientations and aspect window it wants, and whether it makes sound.
|
|
90
|
+
|
|
91
|
+
It is the same file `squidhub-release build --manifest manifest.json` reads, so
|
|
92
|
+
the Editor and the CLI cannot disagree about what your game declares. It belongs
|
|
93
|
+
in your repository.
|
|
94
|
+
|
|
95
|
+
The build writes it into the output folder for you. **That is the only thing
|
|
96
|
+
that makes a declaration take effect** — the server reads `manifest.json` out of
|
|
97
|
+
the uploaded bytes and ignores anything a client posts alongside the upload, so
|
|
98
|
+
a build folder without one declares nothing at all: save schema 1, no display
|
|
99
|
+
constraints, and a rollback guard that cannot fire.
|
|
100
|
+
|
|
101
|
+
⚠️ **Ticking a capability does not grant it.** Capabilities are granted by the
|
|
102
|
+
platform at review. Declaring `commerce.spend` says your game uses it; until it
|
|
103
|
+
is granted the broker still answers `DENIED`, and your game has to handle that.
|
|
104
|
+
New games have `saves` and `analytics`.
|
|
105
|
+
|
|
106
|
+
⚠️ **Keys the dialog does not model are preserved.** `skus`, `achievements` and
|
|
107
|
+
`rewardTable` live in the same file and survive a save untouched.
|
|
108
|
+
|
|
85
109
|
## Uploading from Unity
|
|
86
110
|
|
|
87
111
|
`SquidHub → Upload Build…` does the whole publish — no console, no command line,
|
|
88
|
-
no repository checkout.
|
|
112
|
+
no repository checkout. `SquidHub → Build and Upload…` picks a **build profile**,
|
|
113
|
+
builds it and uploads the result in one action.
|
|
89
114
|
|
|
90
115
|
**Once:** in the publisher console open your game → **Upload keys** → create one,
|
|
91
116
|
then paste it into `SquidHub → Upload Settings`.
|
|
92
117
|
|
|
93
118
|
⚠️ **The key never enters your project.** It lives in EditorPrefs, per machine
|
|
94
|
-
and per project path, so it cannot reach your repository. It can
|
|
95
|
-
|
|
96
|
-
anything,
|
|
97
|
-
|
|
98
|
-
|
|
119
|
+
and per project path, so it cannot reach your repository. It can upload a build
|
|
120
|
+
for the one game it was minted for, and on **dev** publish that build — it
|
|
121
|
+
cannot read anything, touch another game, or publish on prod. If it leaks,
|
|
122
|
+
revoke it in the console.
|
|
123
|
+
|
|
124
|
+
### Publishing on upload
|
|
125
|
+
|
|
126
|
+
**Publish the build** is ticked by default in `SquidHub → Build and Upload…`.
|
|
99
127
|
|
|
100
|
-
⚠️ **
|
|
101
|
-
|
|
128
|
+
⚠️ **It works on dev only.** On prod the server refuses it and says so; the
|
|
129
|
+
upload still succeeds and the build arrives registered and not live. Promoting a
|
|
130
|
+
build for every player is an owner decision made by a person in the console, and
|
|
131
|
+
an upload key is not a person — it sits in CI configs and Editor preferences.
|
|
132
|
+
That asymmetry is deliberate: it is what keeps a leaked key from replacing the
|
|
133
|
+
live build for everyone.
|
|
102
134
|
|
|
103
135
|
The upload is refused before a byte moves if the build is precompressed or has
|
|
104
136
|
no `index.html` at its top level, and OS metadata is dropped rather than sent —
|
|
@@ -141,19 +173,20 @@ blocked at load and shows up as a console error, not as a missing style.
|
|
|
141
173
|
Everything must ship inside the build.
|
|
142
174
|
|
|
143
175
|
⚠️ **Do not write your own rotate prompt or call `screen.orientation.lock()`.**
|
|
144
|
-
Declare the orientations you support in
|
|
145
|
-
draws the prompt outside your frame. Two prompts fight, and yours is the one the
|
|
176
|
+
Declare the orientations you support in `SquidHub → Game Manifest…` and the
|
|
177
|
+
portal draws the prompt outside your frame. Two prompts fight, and yours is the one the
|
|
146
178
|
player sees first.
|
|
147
179
|
|
|
148
180
|
⚠️ **The mute button is the portal's, but the muting happens in your frame.** A
|
|
149
181
|
cross-origin iframe has no `muted` property, so the SDK interposes a gain node
|
|
150
182
|
on your `AudioContext`. It works without you doing anything — but only because
|
|
151
183
|
the bridge is injected ahead of the loader, which is why step 2 above is not
|
|
152
|
-
merely tidy.
|
|
184
|
+
merely tidy. Tick **Sound** in `SquidHub → Game Manifest…` or no button appears.
|
|
153
185
|
|
|
154
186
|
⚠️ **Editing `manifest.json` alone changes nothing.** It is excluded from the
|
|
155
187
|
build hash, so a manifest-only re-upload registers no new version and your edit
|
|
156
|
-
is discarded
|
|
188
|
+
is discarded — the server now says so in a warning rather than staying silent.
|
|
189
|
+
Change a real file and rebuild, or set an override in the console.
|
|
157
190
|
|
|
158
191
|
## ⚠️ Status
|
|
159
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.robotsquid.squidhub",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"displayName": "SquidHub",
|
|
5
5
|
"description": "Cloud saves, analytics and the coin economy for Unity WebGL games published on SquidHub. Installs the C# wrapper, the .jslib half and the page bridge, and rewrites the exported index.html so it survives the platform's CSP.",
|
|
6
6
|
"unity": "2021.3",
|