@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 +1 -1
- package/src/commands/deploy.js +6 -0
- package/src/utils/git-info.js +80 -0
package/package.json
CHANGED
package/src/commands/deploy.js
CHANGED
|
@@ -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
|
+
}
|