@myvillage/cli 2.18.0 → 2.19.0

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": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "description": "MyVillageOS CLI for community developers",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@ import { deployGame, getGameUploadUrl, confirmGameUpload, submitGameForReview, g
11
11
  import { preflightBundles } from '../utils/preflight.js';
12
12
  import { refreshGameToolkit, gameCallsComplete } from '../utils/game-toolkit.js';
13
13
  import { selectGameVillages } from '../utils/village-resolver.js';
14
+ import { readGitInfo } from '../utils/git-info.js';
14
15
 
15
16
  function readPackageJson(dir) {
16
17
  const pkgPath = join(dir, 'package.json');
@@ -328,6 +329,9 @@ async function deployThreeJsGame(projectDir, pkg, options = {}) {
328
329
  villageIds,
329
330
  ...(multiplayer !== undefined && { multiplayer }),
330
331
  };
332
+ // Team project: lets teammates deploy it and records the commit.
333
+ const git = readGitInfo(projectDir);
334
+ if (git) metadata.git = git;
331
335
  formData.append('metadata', JSON.stringify(metadata));
332
336
 
333
337
  for (const fullPath of filePaths) {
@@ -596,6 +600,8 @@ async function deployUnityPlatform(projectDir, mvConfig, platform, { gameIdOverr
596
600
  if (mvConfig.sdkVersion) metadata.sdkVersion = mvConfig.sdkVersion;
597
601
  if (platform === 'webgl') metadata.entryFile = 'index.html';
598
602
  if (Array.isArray(villageIds)) metadata.villageIds = villageIds;
603
+ const git = readGitInfo(projectDir);
604
+ if (git) metadata.git = git;
599
605
 
600
606
  formData.append('metadata', JSON.stringify(metadata));
601
607
 
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The team project a game was deployed from: the `origin` remote and the
3
+ * current commit, read straight from .git so villagers don't need git
4
+ * installed (Studio's team projects use isomorphic-git, which writes a
5
+ * standard .git directory).
6
+ *
7
+ * MyVillage uses this to let teammates deploy a team-owned game and to record
8
+ * which commit each deploy was built from. Returns null when the project
9
+ * isn't a git repo or anything is missing.
10
+ */
11
+
12
+ import { existsSync, readFileSync, statSync } from 'fs';
13
+ import { join } from 'path';
14
+
15
+ function readText(path) {
16
+ try {
17
+ return readFileSync(path, 'utf8');
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ /** `[remote "origin"] url = …` from .git/config. */
24
+ function originUrl(gitDir) {
25
+ const config = readText(join(gitDir, 'config'));
26
+ if (!config) return null;
27
+ let inOrigin = false;
28
+ for (const raw of config.split(/\r?\n/)) {
29
+ const line = raw.trim();
30
+ if (line.startsWith('[')) {
31
+ inOrigin = /^\[remote\s+"origin"\]$/.test(line);
32
+ continue;
33
+ }
34
+ const m = inOrigin && line.match(/^url\s*=\s*(.+)$/);
35
+ if (m) return m[1].trim();
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function resolveRef(gitDir, ref) {
41
+ const loose = readText(join(gitDir, ref));
42
+ if (loose) return loose.trim();
43
+ const packed = readText(join(gitDir, 'packed-refs'));
44
+ if (!packed) return null;
45
+ for (const line of packed.split(/\r?\n/)) {
46
+ const [sha, name] = line.trim().split(' ');
47
+ if (name === ref) return sha;
48
+ }
49
+ return null;
50
+ }
51
+
52
+ function headSha(gitDir) {
53
+ const head = readText(join(gitDir, 'HEAD'))?.trim();
54
+ if (!head) return null;
55
+ const sha = head.startsWith('ref: ') ? resolveRef(gitDir, head.slice(5).trim()) : head;
56
+ return sha && /^[0-9a-f]{40}$/i.test(sha) ? sha.toLowerCase() : null;
57
+ }
58
+
59
+ /** Never send credentials that may be embedded in a remote URL. */
60
+ function withoutCredentials(url) {
61
+ try {
62
+ const u = new URL(url);
63
+ u.username = '';
64
+ u.password = '';
65
+ return u.toString();
66
+ } catch {
67
+ return null; // scp-style (git@host:org/repo) — not a Village Git remote
68
+ }
69
+ }
70
+
71
+ /** { remoteUrl, commitSha } for the project's team repo, or null. */
72
+ export function readGitInfo(projectDir) {
73
+ const gitDir = join(projectDir, '.git');
74
+ if (!existsSync(gitDir) || !statSync(gitDir).isDirectory()) return null;
75
+ const url = originUrl(gitDir);
76
+ const remoteUrl = url && withoutCredentials(url);
77
+ const commitSha = headSha(gitDir);
78
+ if (!remoteUrl || !commitSha) return null;
79
+ return { remoteUrl, commitSha };
80
+ }