gent-cli 5.0.3 → 6.0.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/README.md +505 -172
- package/package.json +3 -2
- package/src/commands/branch.js +66 -2
- package/src/commands/clone.js +159 -49
- package/src/commands/init.js +50 -1
- package/src/commands/pull.js +152 -44
- package/src/commands/push.js +106 -53
- package/src/commands/register.js +27 -6
- package/src/commands/remote.js +14 -1
- package/src/commands/repos.js +103 -0
- package/src/commands/tag.js +72 -3
- package/src/index.js +18 -2
- package/src/utils/api-client.js +13 -0
- package/src/utils/constants.js +54 -9
- package/src/utils/hash-engine.js +24 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gent-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"description": "A modern, Git-like version control CLI with built-in cloud authentication and global user identity management.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node src/index.js",
|
|
11
|
-
"test": "
|
|
11
|
+
"test": "node --check src/index.js && node --check tests/remote-e2e.js",
|
|
12
|
+
"test:remote:e2e": "node tests/remote-e2e.js",
|
|
12
13
|
"demo": "bash demo.sh",
|
|
13
14
|
"link": "npm link",
|
|
14
15
|
"unlink": "npm unlink",
|
package/src/commands/branch.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Branch Command - List, create, or delete branches
|
|
3
|
-
* Manages repository branches
|
|
3
|
+
* Manages repository branches locally and syncs to remote API
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
8
9
|
const { getGentPath, readJSON, writeJSON } = require('../utils/fileSystem');
|
|
9
|
-
const { COMMITS_FILE } = require('../utils/constants');
|
|
10
|
+
const { COMMITS_FILE, CONFIG_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
|
|
11
|
+
const apiClient = require('../utils/api-client');
|
|
12
|
+
const authStorage = require('../utils/auth-storage');
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Manage branches
|
|
@@ -85,6 +88,9 @@ async function createBranch(name, repository, gentPath) {
|
|
|
85
88
|
|
|
86
89
|
console.log(chalk.green(`✓ Created branch '${name}'`));
|
|
87
90
|
console.log(chalk.gray(`Based on: ${repository.currentBranch}`));
|
|
91
|
+
|
|
92
|
+
// Sync to remote if authenticated and remote configured
|
|
93
|
+
await syncBranchCreate(name, currentCommit, gentPath);
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/**
|
|
@@ -110,6 +116,64 @@ async function deleteBranch(name, repository, gentPath) {
|
|
|
110
116
|
await writeJSON(path.join(gentPath, COMMITS_FILE), repository);
|
|
111
117
|
|
|
112
118
|
console.log(chalk.green(`✓ Deleted branch '${name}'`));
|
|
119
|
+
|
|
120
|
+
// Sync deletion to remote
|
|
121
|
+
await syncBranchDelete(name, gentPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Sync branch creation to remote API
|
|
126
|
+
*/
|
|
127
|
+
async function syncBranchCreate(name, commitSha, gentPath) {
|
|
128
|
+
try {
|
|
129
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
130
|
+
if (!isAuth) return;
|
|
131
|
+
|
|
132
|
+
const config = await readJSON(path.join(gentPath, CONFIG_FILE));
|
|
133
|
+
const remoteConfig = config.remotes && config.remotes.origin;
|
|
134
|
+
if (!remoteConfig) return;
|
|
135
|
+
|
|
136
|
+
const repoInfo = parseRemoteUrl(remoteConfig.url);
|
|
137
|
+
if (!repoInfo) return;
|
|
138
|
+
|
|
139
|
+
if (!commitSha) return; // No commit to point to
|
|
140
|
+
|
|
141
|
+
const url = buildRepoUrl(API_ENDPOINTS.REPO_BRANCHES_CREATE, repoInfo);
|
|
142
|
+
await apiClient.post(url, { name, commit_sha: commitSha });
|
|
143
|
+
console.log(chalk.gray(` ↑ Synced to remote`));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
// Non-fatal: branch created locally even if remote sync fails
|
|
146
|
+
if (error.response?.status === 400) {
|
|
147
|
+
console.log(chalk.gray(` ⚠ Remote sync skipped (branch may already exist remotely)`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Sync branch deletion to remote API
|
|
154
|
+
*/
|
|
155
|
+
async function syncBranchDelete(name, gentPath) {
|
|
156
|
+
try {
|
|
157
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
158
|
+
if (!isAuth) return;
|
|
159
|
+
|
|
160
|
+
const config = await readJSON(path.join(gentPath, CONFIG_FILE));
|
|
161
|
+
const remoteConfig = config.remotes && config.remotes.origin;
|
|
162
|
+
if (!remoteConfig) return;
|
|
163
|
+
|
|
164
|
+
const repoInfo = parseRemoteUrl(remoteConfig.url);
|
|
165
|
+
if (!repoInfo) return;
|
|
166
|
+
|
|
167
|
+
const url = buildRepoUrl(API_ENDPOINTS.REPO_BRANCH_DETAIL, { ...repoInfo, branch_name: name });
|
|
168
|
+
await apiClient.delete(url);
|
|
169
|
+
console.log(chalk.gray(` ↑ Deleted from remote`));
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (error.response?.status === 400) {
|
|
172
|
+
console.log(chalk.gray(` ⚠ Cannot delete default branch on remote`));
|
|
173
|
+
} else if (error.response?.status === 404) {
|
|
174
|
+
// Branch didn't exist remotely, that's fine
|
|
175
|
+
}
|
|
176
|
+
}
|
|
113
177
|
}
|
|
114
178
|
|
|
115
179
|
module.exports = branch;
|
package/src/commands/clone.js
CHANGED
|
@@ -11,25 +11,15 @@
|
|
|
11
11
|
* gent clone <url> → Clone into folder named after repo
|
|
12
12
|
* gent clone <url> <directory> → Clone into specific directory
|
|
13
13
|
*
|
|
14
|
-
* ALGORITHM:
|
|
15
|
-
* 1.
|
|
16
|
-
* 2.
|
|
17
|
-
* 3.
|
|
18
|
-
* 4.
|
|
19
|
-
* 5.
|
|
20
|
-
* 6.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* GET /api/repos/:id/clone/
|
|
24
|
-
* Returns:
|
|
25
|
-
* {
|
|
26
|
-
* name: "repo-name",
|
|
27
|
-
* commits: [...],
|
|
28
|
-
* objects: [ { hash, type, data: "<base64>" } ],
|
|
29
|
-
* branches: { "main": "<hash>", ... },
|
|
30
|
-
* currentBranch: "main",
|
|
31
|
-
* tags: { ... }
|
|
32
|
-
* }
|
|
14
|
+
* ALGORITHM (client-side, no /clone/ endpoint):
|
|
15
|
+
* 1. Parse URL to get owner_id + repo_name
|
|
16
|
+
* 2. GET repo details → name, description, default_branch
|
|
17
|
+
* 3. GET branches list → all branch names + SHAs
|
|
18
|
+
* 4. GET commits list → all commits
|
|
19
|
+
* 5. For each commit, fetch tree + blobs
|
|
20
|
+
* 6. Create .gent/ directory structure
|
|
21
|
+
* 7. Store all objects locally
|
|
22
|
+
* 8. Checkout HEAD (restore working tree from latest commit)
|
|
33
23
|
*
|
|
34
24
|
* ============================================================================
|
|
35
25
|
*/
|
|
@@ -39,13 +29,14 @@ const path = require('path');
|
|
|
39
29
|
const chalk = require('chalk');
|
|
40
30
|
const ora = require('ora');
|
|
41
31
|
const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
|
|
42
|
-
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE } = require('../utils/constants');
|
|
32
|
+
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE, API_ENDPOINTS, buildRepoUrl, parseRemoteUrl } = require('../utils/constants');
|
|
43
33
|
const apiClient = require('../utils/api-client');
|
|
44
|
-
const
|
|
34
|
+
const authStorage = require('../utils/auth-storage');
|
|
35
|
+
const { storeBlob, readBlobAsString, decodeRemoteBlobContent } = require('../utils/hash-engine');
|
|
45
36
|
|
|
46
37
|
/**
|
|
47
38
|
* Clone remote repository
|
|
48
|
-
* @param {String} url - Remote repository URL
|
|
39
|
+
* @param {String} url - Remote repository URL (e.g. /api/repos/1/my-repo)
|
|
49
40
|
* @param {String} directory - Optional target directory
|
|
50
41
|
* @param {Object} options
|
|
51
42
|
*/
|
|
@@ -58,11 +49,30 @@ async function clone(url, directory, options) {
|
|
|
58
49
|
const spinner = ora(`Cloning from ${url}...`).start();
|
|
59
50
|
|
|
60
51
|
try {
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
// Check auth
|
|
53
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
54
|
+
if (!isAuth) {
|
|
55
|
+
spinner.fail(chalk.red('Not authenticated'));
|
|
56
|
+
console.log(chalk.yellow('Run "gent login" first'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Parse URL
|
|
61
|
+
const repoInfo = parseRemoteUrl(url);
|
|
62
|
+
if (!repoInfo) {
|
|
63
|
+
spinner.fail(chalk.red('Invalid repository URL'));
|
|
64
|
+
console.log(chalk.yellow('Expected format: /api/repos/{owner_id}/{repo_name}'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
// 1. Get repo details
|
|
69
|
+
spinner.text = 'Fetching repository info...';
|
|
70
|
+
const repoDetail = await apiClient.get(
|
|
71
|
+
buildRepoUrl(API_ENDPOINTS.REPO_DETAIL, repoInfo)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const repoName = repoDetail.name || repoInfo.repo_name;
|
|
75
|
+
const defaultBranch = repoDetail.default_branch || 'main';
|
|
66
76
|
const targetDir = directory || repoName;
|
|
67
77
|
const targetPath = path.resolve(process.cwd(), targetDir);
|
|
68
78
|
|
|
@@ -74,6 +84,39 @@ async function clone(url, directory, options) {
|
|
|
74
84
|
}
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
// 2. Get branches
|
|
88
|
+
spinner.text = 'Fetching branches...';
|
|
89
|
+
let remoteBranches = [];
|
|
90
|
+
try {
|
|
91
|
+
remoteBranches = await apiClient.get(
|
|
92
|
+
buildRepoUrl(API_ENDPOINTS.REPO_BRANCHES, repoInfo)
|
|
93
|
+
);
|
|
94
|
+
} catch {
|
|
95
|
+
// No branches yet
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 3. Get all commits
|
|
99
|
+
spinner.text = 'Fetching commits...';
|
|
100
|
+
let remoteCommits = [];
|
|
101
|
+
try {
|
|
102
|
+
remoteCommits = await apiClient.get(
|
|
103
|
+
buildRepoUrl(API_ENDPOINTS.REPO_COMMITS, repoInfo)
|
|
104
|
+
);
|
|
105
|
+
} catch {
|
|
106
|
+
// No commits yet
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 4. Get tags
|
|
110
|
+
spinner.text = 'Fetching tags...';
|
|
111
|
+
let remoteTags = [];
|
|
112
|
+
try {
|
|
113
|
+
remoteTags = await apiClient.get(
|
|
114
|
+
buildRepoUrl(API_ENDPOINTS.REPO_TAGS, repoInfo)
|
|
115
|
+
);
|
|
116
|
+
} catch {
|
|
117
|
+
// No tags
|
|
118
|
+
}
|
|
119
|
+
|
|
77
120
|
// Create directory structure
|
|
78
121
|
spinner.text = 'Setting up repository...';
|
|
79
122
|
const gentPath = path.join(targetPath, GENT_DIR);
|
|
@@ -82,23 +125,91 @@ async function clone(url, directory, options) {
|
|
|
82
125
|
await ensureDir(path.join(gentPath, 'refs', 'heads'));
|
|
83
126
|
await ensureDir(path.join(gentPath, 'refs', 'tags'));
|
|
84
127
|
|
|
85
|
-
//
|
|
86
|
-
const
|
|
87
|
-
|
|
128
|
+
// 5. For each commit, fetch tree and blobs
|
|
129
|
+
const localCommits = [];
|
|
130
|
+
let objectCount = 0;
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < remoteCommits.length; i++) {
|
|
133
|
+
const commit = remoteCommits[i];
|
|
134
|
+
spinner.text = `Fetching objects (${i + 1}/${remoteCommits.length})...`;
|
|
88
135
|
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
let treeEntries = [];
|
|
137
|
+
if (commit.tree_sha) {
|
|
138
|
+
try {
|
|
139
|
+
const tree = await apiClient.get(
|
|
140
|
+
buildRepoUrl(API_ENDPOINTS.REPO_TREE_DETAIL, { ...repoInfo, sha: commit.tree_sha })
|
|
141
|
+
);
|
|
142
|
+
treeEntries = tree.entries || [];
|
|
143
|
+
} catch {
|
|
144
|
+
// Tree not available
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Fetch and store blobs
|
|
149
|
+
for (const entry of treeEntries) {
|
|
150
|
+
if (entry.type === 'blob' && entry.sha) {
|
|
151
|
+
try {
|
|
152
|
+
const blob = await apiClient.get(
|
|
153
|
+
buildRepoUrl(API_ENDPOINTS.REPO_BLOB_DETAIL, { ...repoInfo, sha: entry.sha })
|
|
154
|
+
);
|
|
155
|
+
if (blob.content) {
|
|
156
|
+
const buf = decodeRemoteBlobContent(blob.content, entry.sha);
|
|
157
|
+
await storeBlob(gentPath, buf);
|
|
158
|
+
objectCount++;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Blob fetch failed
|
|
162
|
+
}
|
|
163
|
+
}
|
|
93
164
|
}
|
|
165
|
+
|
|
166
|
+
// Convert to local commit format
|
|
167
|
+
localCommits.push({
|
|
168
|
+
hash: commit.sha,
|
|
169
|
+
message: commit.message,
|
|
170
|
+
author: { name: commit.author_name, email: commit.author_email },
|
|
171
|
+
timestamp: commit.committed_at,
|
|
172
|
+
parent: commit.parent_shas && commit.parent_shas[0] || null,
|
|
173
|
+
mergeParent: commit.parent_shas && commit.parent_shas[1] || null,
|
|
174
|
+
treeHash: commit.tree_sha,
|
|
175
|
+
tree: treeEntries.map(e => ({
|
|
176
|
+
mode: e.mode || '100644',
|
|
177
|
+
name: e.name,
|
|
178
|
+
hash: e.sha,
|
|
179
|
+
type: e.type || 'blob'
|
|
180
|
+
})),
|
|
181
|
+
files: treeEntries.map(e => ({ path: e.name, hash: e.sha })),
|
|
182
|
+
stats: {}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Build branches map
|
|
187
|
+
const branches = {};
|
|
188
|
+
for (const b of remoteBranches) {
|
|
189
|
+
branches[b.name] = b.commit_sha;
|
|
190
|
+
}
|
|
191
|
+
if (!branches[defaultBranch]) {
|
|
192
|
+
branches[defaultBranch] = null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Build tags map
|
|
196
|
+
const tagsMap = {};
|
|
197
|
+
for (const t of remoteTags) {
|
|
198
|
+
tagsMap[t.name] = {
|
|
199
|
+
hash: t.commit_sha,
|
|
200
|
+
message: t.message || '',
|
|
201
|
+
annotated: t.annotated || false,
|
|
202
|
+
tagger: { name: t.tagger_name || '', email: t.tagger_email || '' },
|
|
203
|
+
timestamp: t.created_at
|
|
204
|
+
};
|
|
94
205
|
}
|
|
95
206
|
|
|
96
207
|
// Write commits.json
|
|
97
208
|
const repoData = {
|
|
98
|
-
commits:
|
|
99
|
-
branches
|
|
100
|
-
currentBranch:
|
|
101
|
-
tags:
|
|
209
|
+
commits: localCommits,
|
|
210
|
+
branches,
|
|
211
|
+
currentBranch: defaultBranch,
|
|
212
|
+
tags: tagsMap
|
|
102
213
|
};
|
|
103
214
|
await writeJSON(path.join(gentPath, COMMITS_FILE), repoData);
|
|
104
215
|
|
|
@@ -107,7 +218,7 @@ async function clone(url, directory, options) {
|
|
|
107
218
|
user: { name: '', email: '' },
|
|
108
219
|
repository: {
|
|
109
220
|
name: repoName,
|
|
110
|
-
description:
|
|
221
|
+
description: repoDetail.description || '',
|
|
111
222
|
created: new Date().toISOString()
|
|
112
223
|
},
|
|
113
224
|
remotes: {
|
|
@@ -116,10 +227,9 @@ async function clone(url, directory, options) {
|
|
|
116
227
|
remoteRefs: {}
|
|
117
228
|
};
|
|
118
229
|
|
|
119
|
-
|
|
120
|
-
const headHash = repoData.branches[repoData.currentBranch];
|
|
230
|
+
const headHash = branches[defaultBranch];
|
|
121
231
|
if (headHash) {
|
|
122
|
-
config.remoteRefs[`origin/${
|
|
232
|
+
config.remoteRefs[`origin/${defaultBranch}`] = headHash;
|
|
123
233
|
}
|
|
124
234
|
|
|
125
235
|
await writeJSON(path.join(gentPath, CONFIG_FILE), config);
|
|
@@ -130,7 +240,7 @@ async function clone(url, directory, options) {
|
|
|
130
240
|
// Write HEAD file
|
|
131
241
|
await fs.writeFile(
|
|
132
242
|
path.join(gentPath, 'HEAD'),
|
|
133
|
-
`ref: refs/heads/${
|
|
243
|
+
`ref: refs/heads/${defaultBranch}\n`
|
|
134
244
|
);
|
|
135
245
|
|
|
136
246
|
// Create .gentignore
|
|
@@ -139,11 +249,9 @@ async function clone(url, directory, options) {
|
|
|
139
249
|
|
|
140
250
|
// Checkout working tree from HEAD commit
|
|
141
251
|
if (headHash) {
|
|
142
|
-
const headCommit =
|
|
252
|
+
const headCommit = localCommits.find(c => c.hash === headHash);
|
|
143
253
|
if (headCommit) {
|
|
144
|
-
const tree = headCommit.tree ||
|
|
145
|
-
name: f.path || f.name, hash: f.hash
|
|
146
|
-
}));
|
|
254
|
+
const tree = headCommit.tree || [];
|
|
147
255
|
|
|
148
256
|
spinner.text = 'Checking out files...';
|
|
149
257
|
let fileCount = 0;
|
|
@@ -160,7 +268,7 @@ async function clone(url, directory, options) {
|
|
|
160
268
|
}
|
|
161
269
|
|
|
162
270
|
spinner.succeed(chalk.green(`Cloned into '${targetDir}'`));
|
|
163
|
-
console.log(chalk.gray(` ${
|
|
271
|
+
console.log(chalk.gray(` ${localCommits.length} commit(s), ${objectCount} object(s), ${fileCount} file(s)`));
|
|
164
272
|
} else {
|
|
165
273
|
spinner.succeed(chalk.green(`Cloned into '${targetDir}' (empty)`));
|
|
166
274
|
}
|
|
@@ -172,8 +280,10 @@ async function clone(url, directory, options) {
|
|
|
172
280
|
spinner.fail(chalk.red('Clone failed'));
|
|
173
281
|
if (error.response?.status === 404) {
|
|
174
282
|
console.error(chalk.red('Repository not found'));
|
|
175
|
-
} else if (error.response?.
|
|
176
|
-
console.error(chalk.red(
|
|
283
|
+
} else if (error.response?.status === 401) {
|
|
284
|
+
console.error(chalk.red('Authentication failed — run "gent login"'));
|
|
285
|
+
} else if (error.response?.data) {
|
|
286
|
+
console.error(chalk.red(JSON.stringify(error.response.data)));
|
|
177
287
|
} else {
|
|
178
288
|
console.error(chalk.red('Error:'), error.message);
|
|
179
289
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -8,7 +8,8 @@ const path = require('path');
|
|
|
8
8
|
const chalk = require('chalk');
|
|
9
9
|
const { ensureDir, writeJSON, pathExists } = require('../utils/fileSystem');
|
|
10
10
|
const authStorage = require('../utils/auth-storage');
|
|
11
|
-
const
|
|
11
|
+
const apiClient = require('../utils/api-client');
|
|
12
|
+
const { GENT_DIR, CONFIG_FILE, STAGING_FILE, COMMITS_FILE, API_ENDPOINTS } = require('../utils/constants');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Initialize a new gent repository
|
|
@@ -109,6 +110,11 @@ node_modules/
|
|
|
109
110
|
console.log(chalk.gray(`Initialized empty Gent repository in ${gentPath}`));
|
|
110
111
|
}
|
|
111
112
|
|
|
113
|
+
// Create remote repository if --remote flag is set
|
|
114
|
+
if (options.remote) {
|
|
115
|
+
await createRemoteRepo(cwd, gentPath, config, options);
|
|
116
|
+
}
|
|
117
|
+
|
|
112
118
|
} catch (error) {
|
|
113
119
|
console.error(chalk.red('Failed to initialize repository'));
|
|
114
120
|
console.error(chalk.red('Error:'), error.message);
|
|
@@ -116,4 +122,47 @@ node_modules/
|
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Create a remote repository on the backend and link it
|
|
127
|
+
*/
|
|
128
|
+
async function createRemoteRepo(cwd, gentPath, config, options) {
|
|
129
|
+
try {
|
|
130
|
+
const isAuth = await authStorage.isAuthenticated();
|
|
131
|
+
if (!isAuth) {
|
|
132
|
+
console.log(chalk.yellow('Not authenticated — skipping remote creation'));
|
|
133
|
+
console.log(chalk.yellow('Run "gent login" then "gent repos --create <name>"'));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const repoName = typeof options.remote === 'string' ? options.remote : path.basename(cwd);
|
|
138
|
+
|
|
139
|
+
console.log(chalk.gray(`Creating remote repository '${repoName}'...`));
|
|
140
|
+
|
|
141
|
+
const payload = {
|
|
142
|
+
name: repoName,
|
|
143
|
+
description: config.repository.description || '',
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const data = await apiClient.post(API_ENDPOINTS.REPOS_CREATE, payload);
|
|
147
|
+
const repo = data.repository || data;
|
|
148
|
+
|
|
149
|
+
// Update local config with remote
|
|
150
|
+
const configPath = path.join(gentPath, CONFIG_FILE);
|
|
151
|
+
const localConfig = await require('../utils/fileSystem').readJSON(configPath);
|
|
152
|
+
localConfig.remotes = localConfig.remotes || {};
|
|
153
|
+
localConfig.remotes.origin = { url: `/api/repos/${repo.owner_id}/${repo.name}` };
|
|
154
|
+
await writeJSON(configPath, localConfig);
|
|
155
|
+
|
|
156
|
+
console.log(chalk.green(`✓ Remote repository created: /api/repos/${repo.owner_id}/${repo.name}`));
|
|
157
|
+
console.log(chalk.gray(` Remote 'origin' configured automatically`));
|
|
158
|
+
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error.response?.status === 400) {
|
|
161
|
+
console.log(chalk.yellow('Remote creation failed — repository name may already exist'));
|
|
162
|
+
} else {
|
|
163
|
+
console.log(chalk.yellow(`Remote creation failed: ${error.message}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
119
168
|
module.exports = init;
|