@myvillage/cli 1.64.6 → 1.64.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myvillage/cli",
3
- "version": "1.64.6",
3
+ "version": "1.64.7",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,6 +39,7 @@
39
39
  "open": "^10.0.3",
40
40
  "ora": "^8.0.1",
41
41
  "p-limit": "^5.0.0",
42
+ "tar": "^7.5.16",
42
43
  "update-notifier": "^7.0.0",
43
44
  "yaml": "^2.3.0",
44
45
  "zod": "^3.22.0"
@@ -214,9 +214,15 @@ async function createUnityGame(options = {}) {
214
214
  console.log();
215
215
  console.log(brand.green(` \u2713 Unity game "${answers.name}" created!`));
216
216
  console.log(brand.teal(` Unity: ${scaffold.unityVersion}`));
217
- console.log(brand.teal(` GameKit SDK: ${scaffold.sdkVersion}`));
217
+ console.log(brand.teal(` GameKit SDK: ${scaffold.sdkVersion}${scaffold.sdkEmbedded ? ' (bundled — no git needed)' : ''}`));
218
218
  console.log(brand.teal(` Orientation: ${orientation} (${orientation === 'landscape' ? 'web only' : 'muni mobile + web'})`));
219
219
  console.log();
220
+ if (!scaffold.sdkEmbedded) {
221
+ console.log(chalk.yellow(' ⚠ Could not download the GameKit SDK (offline?). The project falls back to a'));
222
+ console.log(chalk.yellow(' git-URL dependency, so opening it in Unity will require git to be installed.'));
223
+ console.log(chalk.yellow(' Re-run this command with a network connection to bundle the SDK locally instead.'));
224
+ console.log();
225
+ }
220
226
  console.log(brand.teal(' Next steps:'));
221
227
  console.log();
222
228
  console.log(` 1. ${brand.gold('Open this project in Unity Hub')} (Add \u2192 Add project from disk)`);
@@ -1,7 +1,9 @@
1
- import { mkdirSync, writeFileSync, copyFileSync, existsSync } from 'fs';
1
+ import { mkdirSync, writeFileSync, copyFileSync, existsSync, rmSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
+ import { tmpdir } from 'os';
3
4
  import { fileURLToPath } from 'url';
4
5
  import { randomUUID } from 'crypto';
6
+ import * as tar from 'tar';
5
7
 
6
8
  // Bundled starter cover art lives at src/assets/game-art/ (this file is at
7
9
  // src/utils/). Resolved from the module path so it also works once the CLI is
@@ -14,6 +16,14 @@ const GAMEKIT_SDK_VERSION = '1.1.0-alpha.4';
14
16
  const GAMEKIT_SDK_URL =
15
17
  `https://github.com/MyVillage-Project-Technologies/myvillage-gamekit.git#v${GAMEKIT_SDK_VERSION}`;
16
18
 
19
+ // Direct (no-auth, no-redirect) tarball of the pinned tag. We download and
20
+ // unpack this into Packages/com.myvillage.gamekit/ at scaffold time so the
21
+ // project opens as a *local embedded package* — Unity never has to shell out to
22
+ // `git` to resolve the SDK. This is the fix for the "No 'git' executable was
23
+ // found" Package Manager error developers hit with a git-URL dependency.
24
+ const GAMEKIT_SDK_TARBALL_URL =
25
+ `https://codeload.github.com/MyVillage-Project-Technologies/myvillage-gamekit/tar.gz/refs/tags/v${GAMEKIT_SDK_VERSION}`;
26
+
17
27
  // Fallback Unity Editor version used only if we can't fetch the SDK's
18
28
  // package.json at scaffold time. The SDK declares the recommended editor
19
29
  // version via `unity` + `unityRelease` fields — that's the source of truth.
@@ -41,6 +51,38 @@ async function resolveUnityVersion() {
41
51
  }
42
52
  }
43
53
 
54
+ // Download the pinned GameKit SDK tarball over HTTPS and unpack it into
55
+ // Packages/com.myvillage.gamekit/ so Unity treats it as a local embedded
56
+ // package — no `git` executable required to resolve it. Returns true on
57
+ // success; false (with the partial dir cleaned up) on any failure so the
58
+ // caller can fall back to the git-URL manifest entry and still scaffold.
59
+ async function embedGameKitSdk(targetDir) {
60
+ const pkgDir = join(targetDir, 'Packages', 'com.myvillage.gamekit');
61
+ const controller = new AbortController();
62
+ const timeout = setTimeout(() => controller.abort(), 20000);
63
+ let tmpFile;
64
+ try {
65
+ const res = await fetch(GAMEKIT_SDK_TARBALL_URL, { signal: controller.signal });
66
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
67
+ const buf = Buffer.from(await res.arrayBuffer());
68
+ tmpFile = join(tmpdir(), `gamekit-${randomUUID()}.tar.gz`);
69
+ writeFileSync(tmpFile, buf);
70
+ mkdirSync(pkgDir, { recursive: true });
71
+ // strip:1 drops GitHub's top-level "<owner>-<repo>-<sha>/" wrapper folder
72
+ // so package.json lands at the root of pkgDir.
73
+ await tar.extract({ file: tmpFile, cwd: pkgDir, strip: 1 });
74
+ // Sanity check: a valid UPM package has package.json at its root.
75
+ if (!existsSync(join(pkgDir, 'package.json'))) throw new Error('package.json missing after extract');
76
+ return true;
77
+ } catch {
78
+ rmSync(pkgDir, { recursive: true, force: true });
79
+ return false;
80
+ } finally {
81
+ clearTimeout(timeout);
82
+ if (tmpFile) rmSync(tmpFile, { force: true });
83
+ }
84
+ }
85
+
44
86
  // MyVillage brand colors
45
87
  const BRAND = {
46
88
  gold: '#FFD700',
@@ -1135,7 +1177,7 @@ function generateUnityAgents(name, className, orientation) {
1135
1177
  return [
1136
1178
  '# AGENTS.md — working on this MyVillage Unity game with an AI agent',
1137
1179
  '',
1138
- '**' + name + '** is a MyVillage Unity game built on the **MyVillage GameKit SDK** (a UPM package pinned in `Packages/manifest.json`). The mission is C#; it builds to AssetBundles the M-UNI host loads.',
1180
+ '**' + name + '** is a MyVillage Unity game built on the **MyVillage GameKit SDK** (bundled as a local UPM package in `Packages/com.myvillage.gamekit/` — no git needed to open the project). The mission is C#; it builds to AssetBundles the M-UNI host loads.',
1139
1181
  '',
1140
1182
  'When editing: change the mission logic, keep the **game contract** intact (`CompleteMission` / `FailMission`), and do not break the GameKit `MissionBase` lifecycle.',
1141
1183
  '',
@@ -1151,7 +1193,8 @@ function generateUnityAgents(name, className, orientation) {
1151
1193
  '|------|---------|',
1152
1194
  '| `Assets/Scripts/' + className + '.cs` | **Your mission** — the `MissionBase` subclass. Edit here. |',
1153
1195
  '| `Assets/Editor/MyVillageOrientation.cs` | Pins player orientation on first load. |',
1154
- '| `Packages/manifest.json` | Depends on the GameKit SDK (pinned version). Do not hand-edit the SDK. |',
1196
+ '| `Packages/com.myvillage.gamekit/` | The GameKit SDK, bundled as a local package. Do not hand-edit it. |',
1197
+ '| `Packages/manifest.json` | Unity registry packages (URP, TMP, Input System, …). |',
1155
1198
  '| `myvillage.json` | Game metadata the CLI/deploy reads (slug, gameType, orientation, assets). |',
1156
1199
  '| `assets/` | Cover art shipped on deploy (thumbnail/banner). |',
1157
1200
  '| `builds/{ios,android,webgl}/` | Drop-zone for built AssetBundles the CLI deploys. |',
@@ -1181,7 +1224,7 @@ function generateUnityAgents(name, className, orientation) {
1181
1224
  '',
1182
1225
  '## Gotchas',
1183
1226
  '',
1184
- '- Do not edit the GameKit SDK package — it is a pinned dependency. Use its `MissionBase` API.',
1227
+ '- Do not edit the GameKit SDK package — it is bundled in `Packages/com.myvillage.gamekit/`. Use its `MissionBase` API. To change SDK version, re-scaffold or replace that folder; do not patch it in place.',
1185
1228
  '- Visual Scripting reflection nodes (`InvokeMember`/`GetMember`/`SetMember`) are rejected by the deploy preflight. Write C# instead.',
1186
1229
  '',
1187
1230
  ].join('\n');
@@ -1199,12 +1242,14 @@ export async function createUnityGameProject(targetDir, options) {
1199
1242
  const isLandscape = orientation === 'landscape';
1200
1243
  const primaryGameType = isLandscape ? 'UNITY_WEBGL' : 'UNITY_NATIVE';
1201
1244
 
1202
- // Scaffold an openable Unity project that pulls com.myvillage.gamekit via UPM.
1245
+ // Scaffold an openable Unity project with com.myvillage.gamekit bundled in
1246
+ // as a local embedded package (downloaded over HTTPS, no git required).
1203
1247
  // Layout matches what Unity Hub expects:
1204
- // Packages/manifest.json - dependency on the GameKit SDK + core modules
1248
+ // Packages/com.myvillage.gamekit/ - the GameKit SDK, embedded locally
1249
+ // Packages/manifest.json - Unity registry deps (URP, TMP, …)
1205
1250
  // ProjectSettings/ProjectVersion.txt - tells Unity which editor version
1206
- // Assets/Scripts/<Name>.cs - starter MissionBase subclass
1207
- // builds/{ios,android,webgl}/ - drop-zone for builds the CLI deploys
1251
+ // Assets/Scripts/<Name>.cs - starter MissionBase subclass
1252
+ // builds/{ios,android,webgl}/ - drop-zone for builds the CLI deploys
1208
1253
 
1209
1254
  const dirs = [
1210
1255
  '',
@@ -1249,8 +1294,15 @@ export async function createUnityGameProject(targetDir, options) {
1249
1294
  lastDeployed: null,
1250
1295
  }, null, 2) + '\n');
1251
1296
 
1252
- // Unity project files.
1253
- writeFileSync(join(targetDir, 'Packages/manifest.json'), generateUnityPackagesManifest());
1297
+ // Bundle the GameKit SDK as a local package so the project opens without git.
1298
+ // If the download fails (e.g. offline), fall back to the git-URL dependency in
1299
+ // the manifest — that path still works for anyone who has git installed.
1300
+ const sdkEmbedded = await embedGameKitSdk(targetDir);
1301
+
1302
+ // Unity project files. When the SDK is embedded it must NOT also be listed as
1303
+ // a manifest dependency (Unity would try to re-resolve it); when it isn't, the
1304
+ // git-URL entry is the fallback resolution path.
1305
+ writeFileSync(join(targetDir, 'Packages/manifest.json'), generateUnityPackagesManifest(sdkEmbedded));
1254
1306
  writeFileSync(join(targetDir, 'ProjectSettings/ProjectVersion.txt'), generateUnityProjectVersion(unityVersion));
1255
1307
 
1256
1308
  // Starter mission. New devs subclass MissionBase here.
@@ -1271,7 +1323,7 @@ export async function createUnityGameProject(targetDir, options) {
1271
1323
  writeFileSync(join(targetDir, 'README.md'), generateUnityReadme(name, slug, className, orientation, unityVersion));
1272
1324
  writeFileSync(join(targetDir, 'AGENTS.md'), generateUnityAgents(name, className, orientation));
1273
1325
 
1274
- return { unityVersion, sdkVersion: GAMEKIT_SDK_VERSION, gameType: primaryGameType };
1326
+ return { unityVersion, sdkVersion: GAMEKIT_SDK_VERSION, gameType: primaryGameType, sdkEmbedded };
1275
1327
  }
1276
1328
 
1277
1329
  // Editor script: applies orientation to PlayerSettings on first load so a
@@ -1315,18 +1367,23 @@ public static class MyVillageOrientation
1315
1367
 
1316
1368
  // ── Unity (GameKit SDK) ─────────────────────────────────────────────
1317
1369
 
1318
- function generateUnityPackagesManifest() {
1319
- return JSON.stringify({
1320
- dependencies: {
1321
- 'com.myvillage.gamekit': GAMEKIT_SDK_URL,
1322
- 'com.unity.feature.development': '1.0.2',
1323
- 'com.unity.render-pipelines.universal': '17.2.0',
1324
- 'com.unity.textmeshpro': '3.0.6',
1325
- 'com.unity.inputsystem': '1.14.2',
1326
- 'com.unity.ide.rider': '3.0.38',
1327
- 'com.unity.ide.visualstudio': '2.0.22',
1328
- },
1329
- }, null, 2) + '\n';
1370
+ function generateUnityPackagesManifest(sdkEmbedded = true) {
1371
+ const dependencies = {};
1372
+ // Only declare the SDK as a git dependency when it could NOT be embedded
1373
+ // locally. When embedded under Packages/com.myvillage.gamekit/, Unity
1374
+ // auto-discovers it and listing it here would force a (git-based) re-resolve.
1375
+ if (!sdkEmbedded) {
1376
+ dependencies['com.myvillage.gamekit'] = GAMEKIT_SDK_URL;
1377
+ }
1378
+ Object.assign(dependencies, {
1379
+ 'com.unity.feature.development': '1.0.2',
1380
+ 'com.unity.render-pipelines.universal': '17.2.0',
1381
+ 'com.unity.textmeshpro': '3.0.6',
1382
+ 'com.unity.inputsystem': '1.14.2',
1383
+ 'com.unity.ide.rider': '3.0.38',
1384
+ 'com.unity.ide.visualstudio': '2.0.22',
1385
+ });
1386
+ return JSON.stringify({ dependencies }, null, 2) + '\n';
1330
1387
  }
1331
1388
 
1332
1389
  function generateUnityProjectVersion(unityVersion) {
@@ -1450,24 +1507,25 @@ A MyVillage game scaffolded with the MyVillage CLI and the MyVillage GameKit SDK
1450
1507
  - **Slug:** \`${slug}\`
1451
1508
  - **Game type:** \`${gameType}\`
1452
1509
  - **Orientation:** \`${orientation}\` — plays on ${reach}
1453
- - **SDK:** \`com.myvillage.gamekit\` (pinned in \`Packages/manifest.json\`)
1510
+ - **SDK:** \`com.myvillage.gamekit\` v${GAMEKIT_SDK_VERSION} (bundled in \`Packages/com.myvillage.gamekit/\` \u2014 no git required)
1454
1511
 
1455
1512
  ## What's in this project
1456
1513
 
1457
1514
  \`\`\`
1458
1515
  Assets/
1459
1516
  \u2514\u2500\u2500 Scripts/
1460
- \u2514\u2500\u2500 ${className}.cs \u2190 your mission, subclasses MissionBase
1517
+ \u2514\u2500\u2500 ${className}.cs \u2190 your mission, subclasses MissionBase
1461
1518
  Packages/
1462
- \u2514\u2500\u2500 manifest.json \u2190 fetches the GameKit SDK on first open
1519
+ \u251c\u2500\u2500 com.myvillage.gamekit/ \u2190 GameKit SDK, bundled locally
1520
+ \u2514\u2500\u2500 manifest.json \u2190 Unity registry packages (URP, TMP, \u2026)
1463
1521
  ProjectSettings/
1464
1522
  \u2514\u2500\u2500 ProjectVersion.txt
1465
- myvillage.json \u2190 CLI metadata (auto-updated by deploy)
1523
+ myvillage.json \u2190 CLI metadata (auto-updated by deploy)
1466
1524
  \`\`\`
1467
1525
 
1468
1526
  ## Quickstart \u2014 play your mission in 3 steps
1469
1527
 
1470
- 1. **Install Unity Hub** and Unity **${unityVersion}** if you don't have it. Open this folder in Unity Hub (Add \u2192 Add project from disk). Unity fetches the GameKit SDK automatically (check Package Manager \u2192 In Project).
1528
+ 1. **Install Unity Hub** and Unity **${unityVersion}** if you don't have it. Open this folder in Unity Hub (Add \u2192 Add project from disk). The GameKit SDK is already bundled in \`Packages/\`, so the project compiles on first open \u2014 you do **not** need git installed.
1471
1529
 
1472
1530
  2. **Click the menu \`MyVillage \u2192 Create Starter Mission Scene\`.** This creates a new scene with a "Mission" GameObject that already has \`MockMissionHost\` attached (the editor stand-in for the real M-UNI host) and your \`${className}\` script auto-attached. The scene is added to Build Settings and opened for you.
1473
1531