pinokiod 7.3.1 → 7.3.3
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/kernel/api/github/index.js +444 -0
- package/kernel/api/index.js +199 -11
- package/kernel/api/process/index.js +124 -44
- package/kernel/api/shell_run_template.js +273 -0
- package/kernel/api/uri/index.js +51 -0
- package/kernel/bin/git.js +9 -10
- package/kernel/bin/huggingface.js +1 -1
- package/kernel/bin/zip.js +9 -1
- package/kernel/connect/providers/github/README.md +5 -4
- package/kernel/environment.js +195 -92
- package/kernel/git.js +98 -19
- package/kernel/gitconfig_template +7 -0
- package/kernel/gpu/amd.js +72 -0
- package/kernel/gpu/apple.js +8 -0
- package/kernel/gpu/common.js +12 -0
- package/kernel/gpu/intel.js +47 -0
- package/kernel/gpu/nvidia.js +8 -0
- package/kernel/index.js +11 -1
- package/kernel/managed_skills.js +871 -0
- package/kernel/plugin.js +6 -58
- package/kernel/plugin_sources.js +316 -0
- package/kernel/resource_usage/gpu.js +349 -0
- package/kernel/resource_usage/index.js +322 -0
- package/kernel/resource_usage/macos_footprint.js +197 -0
- package/kernel/resource_usage/preferences.js +92 -0
- package/kernel/resource_usage/process_tree.js +303 -0
- package/kernel/scripts/git/create +4 -4
- package/kernel/scripts/git/fork +7 -8
- package/kernel/shell.js +23 -2
- package/kernel/shells.js +41 -0
- package/kernel/sysinfo.js +62 -9
- package/kernel/util.js +60 -0
- package/package.json +1 -1
- package/server/index.js +984 -156
- package/server/lib/app_log_report.js +543 -0
- package/server/lib/content_validation.js +55 -33
- package/server/lib/launcher_instruction_bootstrap.js +4 -96
- package/server/lib/terminal_session_helpers.js +0 -3
- package/server/public/common.js +77 -31
- package/server/public/create-launcher.js +4 -32
- package/server/public/logs.js +1428 -0
- package/server/public/nav.js +7 -0
- package/server/public/plugin-detail.js +93 -10
- package/server/public/privacy_filter_worker.js +391 -0
- package/server/public/style.css +1104 -154
- package/server/public/task-launcher.js +8 -29
- package/server/public/universal-launcher.css +8 -6
- package/server/public/universal-launcher.js +3 -27
- package/server/routes/apps.js +195 -1
- package/server/views/app.ejs +3041 -717
- package/server/views/autolaunch.ejs +917 -0
- package/server/views/bootstrap.ejs +7 -1
- package/server/views/d.ejs +408 -65
- package/server/views/editor.ejs +85 -19
- package/server/views/index.ejs +661 -111
- package/server/views/init/index.ejs +1 -1
- package/server/views/install.ejs +1 -1
- package/server/views/logs.ejs +164 -86
- package/server/views/net.ejs +7 -1
- package/server/views/partials/d_terminal_column.ejs +2 -2
- package/server/views/partials/d_terminal_options.ejs +0 -8
- package/server/views/partials/fs_status.ejs +47 -0
- package/server/views/partials/home_action_modal.ejs +86 -0
- package/server/views/partials/home_run_menu.ejs +87 -0
- package/server/views/partials/main_sidebar.ejs +2 -0
- package/server/views/partials/menu.ejs +1 -1
- package/server/views/plugin_detail.ejs +19 -4
- package/server/views/plugins.ejs +201 -3
- package/server/views/pre.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/shell.ejs +40 -18
- package/server/views/skills.ejs +506 -0
- package/server/views/terminal.ejs +45 -19
- package/spec/INSTRUCTION_SYNC.md +20 -10
- package/system/plugin/antigravity-cli/antigravity.png +0 -0
- package/system/plugin/antigravity-cli/common.js +155 -0
- package/system/plugin/antigravity-cli/install.js +272 -0
- package/system/plugin/antigravity-cli/pinokio.js +13 -0
- package/system/plugin/antigravity-cli-auto/antigravity.png +0 -0
- package/system/plugin/antigravity-cli-auto/pinokio.js +13 -0
- package/system/plugin/claude/claude.png +0 -0
- package/system/plugin/claude/pinokio.js +47 -0
- package/system/plugin/claude-auto/claude.png +0 -0
- package/system/plugin/claude-auto/pinokio.js +58 -0
- package/system/plugin/claude-desktop/icon.jpeg +0 -0
- package/system/plugin/claude-desktop/pinokio.js +23 -0
- package/system/plugin/codex/openai.webp +0 -0
- package/system/plugin/codex/pinokio.js +42 -0
- package/system/plugin/codex-auto/openai.webp +0 -0
- package/system/plugin/codex-auto/pinokio.js +49 -0
- package/system/plugin/codex-desktop/icon.png +0 -0
- package/system/plugin/codex-desktop/pinokio.js +23 -0
- package/system/plugin/crush/crush.png +0 -0
- package/system/plugin/crush/pinokio.js +15 -0
- package/system/plugin/cursor/cursor.jpeg +0 -0
- package/system/plugin/cursor/pinokio.js +23 -0
- package/system/plugin/qwen/pinokio.js +34 -0
- package/system/plugin/qwen/qwen.png +0 -0
- package/system/plugin/vscode/pinokio.js +20 -0
- package/system/plugin/vscode/vscode.png +0 -0
- package/system/plugin/windsurf/pinokio.js +23 -0
- package/system/plugin/windsurf/windsurf.png +0 -0
- package/test/antigravity-cli-plugin.test.js +185 -0
- package/test/app-api.test.js +239 -0
- package/test/app-log-report.test.js +67 -0
- package/test/environment-cache-preflight.test.js +98 -0
- package/test/git-bin.test.js +59 -0
- package/test/git-defaults.test.js +97 -0
- package/test/github-api.test.js +158 -0
- package/test/github-connection.test.js +117 -0
- package/test/huggingface-bin.test.js +25 -0
- package/test/managed-skills.test.js +351 -0
- package/test/plugin-action-functions.test.js +337 -0
- package/test/plugin-dev-iframe.test.js +17 -0
- package/test/plugin-sources.test.js +203 -0
- package/test/privacy-filter-worker-heuristics.test.js +69 -0
- package/test/process-wait.test.js +169 -0
- package/test/script-api.test.js +97 -0
- package/test/shell-api.test.js +134 -0
- package/test/shell-run-template.test.js +209 -0
- package/test/storage-api.test.js +137 -0
- package/test/uri-api.test.js +100 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const { execFile, spawn } = require('child_process')
|
|
3
|
+
const fetch = require('cross-fetch')
|
|
4
|
+
|
|
5
|
+
const NON_INTERACTIVE_GIT_ENV = {
|
|
6
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
7
|
+
GIT_ASKPASS: "",
|
|
8
|
+
SSH_ASKPASS: "",
|
|
9
|
+
GCM_INTERACTIVE: "never"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class Github {
|
|
13
|
+
static parseCredentialOutput(stdout) {
|
|
14
|
+
const credential = {}
|
|
15
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
16
|
+
const index = line.indexOf("=")
|
|
17
|
+
if (index <= 0) continue
|
|
18
|
+
credential[line.slice(0, index)] = line.slice(index + 1)
|
|
19
|
+
}
|
|
20
|
+
return credential
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static parseGithubRemote(remoteUrl) {
|
|
24
|
+
const raw = typeof remoteUrl === "string" ? remoteUrl.trim() : ""
|
|
25
|
+
if (!raw) return null
|
|
26
|
+
|
|
27
|
+
const fromPath = (value) => {
|
|
28
|
+
const segments = String(value || "")
|
|
29
|
+
.replace(/^\/+/, "")
|
|
30
|
+
.replace(/\.git$/i, "")
|
|
31
|
+
.split("/")
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
if (segments.length < 2) return null
|
|
34
|
+
return {
|
|
35
|
+
owner: segments[0],
|
|
36
|
+
repo: segments[1],
|
|
37
|
+
fullName: `${segments[0]}/${segments[1]}`
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (/^https?:\/\//i.test(raw)) {
|
|
42
|
+
try {
|
|
43
|
+
const parsed = new URL(raw)
|
|
44
|
+
if (parsed.hostname.toLowerCase() !== "github.com") return null
|
|
45
|
+
return fromPath(parsed.pathname)
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let match = raw.match(/^git@github\.com:(.+)$/i)
|
|
52
|
+
if (match) return fromPath(match[1])
|
|
53
|
+
|
|
54
|
+
match = raw.match(/^ssh:\/\/git@github\.com\/(.+)$/i)
|
|
55
|
+
if (match) return fromPath(match[1])
|
|
56
|
+
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static validateOwner(owner) {
|
|
61
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(owner)) {
|
|
62
|
+
throw new Error(`Invalid GitHub owner: ${owner}`)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static validateRepoName(name) {
|
|
67
|
+
if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
|
|
68
|
+
throw new Error(`Invalid GitHub repository name: ${name}`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static cleanParam(value) {
|
|
73
|
+
const raw = String(value == null ? "" : value).trim()
|
|
74
|
+
if (!raw || raw === "undefined" || raw === "null" || /^{{.+}}$/.test(raw)) {
|
|
75
|
+
return ""
|
|
76
|
+
}
|
|
77
|
+
return raw
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static parseRepoName(name, cwd) {
|
|
81
|
+
const fallback = cwd ? path.basename(path.resolve(cwd)) : ""
|
|
82
|
+
const raw = Github.cleanParam(name) || fallback
|
|
83
|
+
const normalized = raw
|
|
84
|
+
.replace(/^https?:\/\/github\.com\//i, "")
|
|
85
|
+
.replace(/\.git$/i, "")
|
|
86
|
+
.replace(/^\/+|\/+$/g, "")
|
|
87
|
+
|
|
88
|
+
if (!normalized) {
|
|
89
|
+
throw new Error("Repository name is required.")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const parts = normalized.split("/").filter(Boolean)
|
|
93
|
+
if (parts.length > 2) {
|
|
94
|
+
throw new Error(`Invalid GitHub repository name: ${raw}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const repo = parts.length === 2 ? parts[1] : parts[0]
|
|
98
|
+
const owner = parts.length === 2 ? parts[0] : null
|
|
99
|
+
|
|
100
|
+
if (owner) Github.validateOwner(owner)
|
|
101
|
+
Github.validateRepoName(repo)
|
|
102
|
+
|
|
103
|
+
return { owner, repo }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
static normalizeVisibility(visibility) {
|
|
107
|
+
const value = (Github.cleanParam(visibility) || "public").toLowerCase()
|
|
108
|
+
if (!["public", "private", "internal"].includes(value)) {
|
|
109
|
+
throw new Error(`Invalid GitHub repository visibility: ${visibility}`)
|
|
110
|
+
}
|
|
111
|
+
return value
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static buildCreateRepoRequest({ owner, repo, authenticatedUser, visibility }) {
|
|
115
|
+
const login = authenticatedUser && authenticatedUser.login ? String(authenticatedUser.login) : ""
|
|
116
|
+
const body = { name: repo }
|
|
117
|
+
|
|
118
|
+
if (visibility === "internal") {
|
|
119
|
+
if (!owner || owner.toLowerCase() === login.toLowerCase()) {
|
|
120
|
+
throw new Error("Internal repositories require an organization owner, for example org/repo.")
|
|
121
|
+
}
|
|
122
|
+
body.visibility = "internal"
|
|
123
|
+
} else {
|
|
124
|
+
body.private = visibility === "private"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (owner && owner.toLowerCase() !== login.toLowerCase()) {
|
|
128
|
+
return {
|
|
129
|
+
path: `/orgs/${encodeURIComponent(owner)}/repos`,
|
|
130
|
+
body
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
path: "/user/repos",
|
|
136
|
+
body
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
static repoEquals(a, b) {
|
|
141
|
+
return Boolean(
|
|
142
|
+
a && b
|
|
143
|
+
&& String(a.owner).toLowerCase() === String(b.owner).toLowerCase()
|
|
144
|
+
&& String(a.repo).toLowerCase() === String(b.repo).toLowerCase()
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static repoFromApi(repo) {
|
|
149
|
+
if (!repo || typeof repo !== "object") return null
|
|
150
|
+
const owner = repo.owner && repo.owner.login ? String(repo.owner.login) : ""
|
|
151
|
+
const name = repo.name ? String(repo.name) : ""
|
|
152
|
+
if (owner && name) {
|
|
153
|
+
return { owner, repo: name, fullName: `${owner}/${name}` }
|
|
154
|
+
}
|
|
155
|
+
if (repo.full_name) {
|
|
156
|
+
const parts = String(repo.full_name).split("/")
|
|
157
|
+
if (parts.length >= 2) {
|
|
158
|
+
return { owner: parts[0], repo: parts[1], fullName: `${parts[0]}/${parts[1]}` }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return Github.parseGithubRemote(repo.clone_url || repo.html_url || "")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
params(req) {
|
|
165
|
+
return req && req.params ? req.params : {}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
cwd(req, kernel) {
|
|
169
|
+
const params = this.params(req)
|
|
170
|
+
return path.resolve(params.cwd || params.path || req.cwd || (kernel && kernel.homedir) || process.cwd())
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
gitEnv(kernel, { nonInteractive = true } = {}) {
|
|
174
|
+
const env = kernel && kernel.envs ? { ...kernel.envs } : { ...process.env }
|
|
175
|
+
if (kernel && kernel.homedir && !env.GIT_CONFIG_GLOBAL) {
|
|
176
|
+
env.GIT_CONFIG_GLOBAL = path.resolve(kernel.homedir, "gitconfig")
|
|
177
|
+
}
|
|
178
|
+
return nonInteractive ? { ...env, ...NON_INTERACTIVE_GIT_ENV } : env
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
execGit(args, { cwd, kernel, input, timeout = 30000, allowFailure = false } = {}) {
|
|
182
|
+
return new Promise((resolve, reject) => {
|
|
183
|
+
const child = execFile(
|
|
184
|
+
"git",
|
|
185
|
+
args,
|
|
186
|
+
{
|
|
187
|
+
cwd,
|
|
188
|
+
env: this.gitEnv(kernel),
|
|
189
|
+
timeout,
|
|
190
|
+
maxBuffer: 1024 * 1024,
|
|
191
|
+
windowsHide: true
|
|
192
|
+
},
|
|
193
|
+
(error, stdout, stderr) => {
|
|
194
|
+
if (error && !allowFailure) {
|
|
195
|
+
error.stderr = stderr
|
|
196
|
+
reject(error)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
resolve({ stdout: stdout || "", stderr: stderr || "", error })
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
if (typeof input === "string") {
|
|
203
|
+
child.stdin.end(input)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
runGit(args, { cwd, kernel, ondata }) {
|
|
209
|
+
return new Promise((resolve, reject) => {
|
|
210
|
+
if (ondata) ondata({ raw: `git ${args.join(" ")}\r\n` })
|
|
211
|
+
|
|
212
|
+
const child = spawn("git", args, {
|
|
213
|
+
cwd,
|
|
214
|
+
env: this.gitEnv(kernel),
|
|
215
|
+
windowsHide: true
|
|
216
|
+
})
|
|
217
|
+
let stdout = ""
|
|
218
|
+
let stderr = ""
|
|
219
|
+
|
|
220
|
+
child.stdout.on("data", (chunk) => {
|
|
221
|
+
const raw = chunk.toString()
|
|
222
|
+
stdout += raw
|
|
223
|
+
if (ondata) ondata({ raw })
|
|
224
|
+
})
|
|
225
|
+
child.stderr.on("data", (chunk) => {
|
|
226
|
+
const raw = chunk.toString()
|
|
227
|
+
stderr += raw
|
|
228
|
+
if (ondata) ondata({ raw })
|
|
229
|
+
})
|
|
230
|
+
child.on("error", reject)
|
|
231
|
+
child.on("close", (code) => {
|
|
232
|
+
if (code === 0) {
|
|
233
|
+
resolve({ stdout, stderr })
|
|
234
|
+
} else {
|
|
235
|
+
const error = new Error(`git ${args[0]} failed with exit code ${code}`)
|
|
236
|
+
error.stdout = stdout
|
|
237
|
+
error.stderr = stderr
|
|
238
|
+
reject(error)
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async getCredential(req, kernel) {
|
|
245
|
+
if (kernel && kernel.git && typeof kernel.git.ensureDefaults === "function") {
|
|
246
|
+
await kernel.git.ensureDefaults()
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const result = await this.execGit(["credential", "fill"], {
|
|
250
|
+
cwd: (kernel && kernel.homedir) || this.cwd(req, kernel),
|
|
251
|
+
kernel,
|
|
252
|
+
input: "protocol=https\nhost=github.com\n\n",
|
|
253
|
+
timeout: 30000
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const credential = Github.parseCredentialOutput(result.stdout)
|
|
257
|
+
if (!credential.password) {
|
|
258
|
+
throw new Error("GitHub is not connected. Open /github and connect GitHub first.")
|
|
259
|
+
}
|
|
260
|
+
return credential
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async githubRequest(token, method, apiPath, body) {
|
|
264
|
+
const headers = {
|
|
265
|
+
"Accept": "application/vnd.github+json",
|
|
266
|
+
"Authorization": `Bearer ${token}`,
|
|
267
|
+
"User-Agent": "Pinokio",
|
|
268
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
269
|
+
}
|
|
270
|
+
const options = { method, headers }
|
|
271
|
+
if (body) {
|
|
272
|
+
headers["Content-Type"] = "application/json"
|
|
273
|
+
options.body = JSON.stringify(body)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const response = await fetch(`https://api.github.com${apiPath}`, options)
|
|
277
|
+
const text = await response.text()
|
|
278
|
+
let data = null
|
|
279
|
+
if (text) {
|
|
280
|
+
try {
|
|
281
|
+
data = JSON.parse(text)
|
|
282
|
+
} catch (_) {
|
|
283
|
+
data = { message: text }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!response.ok) {
|
|
288
|
+
const message = data && data.message ? data.message : response.statusText
|
|
289
|
+
const details = data && Array.isArray(data.errors) && data.errors.length > 0
|
|
290
|
+
? ` ${data.errors.map((error) => error.message || error.code || "").filter(Boolean).join(" ")}`
|
|
291
|
+
: ""
|
|
292
|
+
throw new Error(`GitHub request failed (${response.status}): ${message}${details}`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return data
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async getAuthenticatedUser(token) {
|
|
299
|
+
return this.githubRequest(token, "GET", "/user")
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async remoteUrl(cwd, name, kernel) {
|
|
303
|
+
const result = await this.execGit(["remote", "get-url", name], {
|
|
304
|
+
cwd,
|
|
305
|
+
kernel,
|
|
306
|
+
allowFailure: true,
|
|
307
|
+
timeout: 10000
|
|
308
|
+
})
|
|
309
|
+
if (result.error) return ""
|
|
310
|
+
return result.stdout.trim()
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async githubRemote(cwd, kernel) {
|
|
314
|
+
for (const name of ["upstream", "origin"]) {
|
|
315
|
+
const url = await this.remoteUrl(cwd, name, kernel)
|
|
316
|
+
const parsed = Github.parseGithubRemote(url)
|
|
317
|
+
if (parsed) return { ...parsed, remote: name, url }
|
|
318
|
+
}
|
|
319
|
+
throw new Error("No GitHub remote found. Add an origin or upstream remote first.")
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async remoteExists(cwd, name, kernel) {
|
|
323
|
+
return Boolean(await this.remoteUrl(cwd, name, kernel))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async configureForkRemote({ cwd, kernel, ondata, source, forkRepo }) {
|
|
327
|
+
const forkRemote = Github.repoFromApi(forkRepo)
|
|
328
|
+
if (!forkRemote) {
|
|
329
|
+
throw new Error("GitHub fork response did not include a repository owner and name.")
|
|
330
|
+
}
|
|
331
|
+
const originUrl = await this.remoteUrl(cwd, "origin", kernel)
|
|
332
|
+
const origin = Github.parseGithubRemote(originUrl)
|
|
333
|
+
const cloneUrl = forkRepo.clone_url
|
|
334
|
+
|
|
335
|
+
if (!originUrl) {
|
|
336
|
+
if (ondata) ondata({ raw: `Adding origin remote for ${forkRepo.full_name}\r\n` })
|
|
337
|
+
await this.runGit(["remote", "add", "origin", cloneUrl], { cwd, kernel, ondata })
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (Github.repoEquals(origin, forkRemote)) {
|
|
342
|
+
if (ondata) ondata({ raw: `origin already points at ${forkRepo.full_name}\r\n` })
|
|
343
|
+
return
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const remoteName = forkRemote.owner || "fork"
|
|
347
|
+
const existingRemoteUrl = await this.remoteUrl(cwd, remoteName, kernel)
|
|
348
|
+
if (existingRemoteUrl) {
|
|
349
|
+
const existingRemote = Github.parseGithubRemote(existingRemoteUrl)
|
|
350
|
+
if (Github.repoEquals(existingRemote, forkRemote)) {
|
|
351
|
+
if (ondata) ondata({ raw: `${remoteName} already points at ${forkRepo.full_name}\r\n` })
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
if (remoteName !== "fork" && !(await this.remoteExists(cwd, "fork", kernel))) {
|
|
355
|
+
if (ondata) ondata({ raw: `Adding fork remote for ${forkRepo.full_name}\r\n` })
|
|
356
|
+
await this.runGit(["remote", "add", "fork", cloneUrl], { cwd, kernel, ondata })
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
if (ondata) ondata({ raw: `${remoteName} remote already exists; leaving it unchanged\r\n` })
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (ondata) ondata({ raw: `Adding ${remoteName} remote for ${forkRepo.full_name}\r\n` })
|
|
364
|
+
await this.runGit(["remote", "add", remoteName, cloneUrl], { cwd, kernel, ondata })
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async create(req, ondata, kernel) {
|
|
368
|
+
const params = this.params(req)
|
|
369
|
+
const cwd = this.cwd(req, kernel)
|
|
370
|
+
const credential = await this.getCredential(req, kernel)
|
|
371
|
+
const user = await this.getAuthenticatedUser(credential.password)
|
|
372
|
+
const visibility = Github.normalizeVisibility(params.visibility)
|
|
373
|
+
const repoName = Github.parseRepoName(params.name, cwd)
|
|
374
|
+
const targetRepo = {
|
|
375
|
+
owner: repoName.owner || String(user.login || ""),
|
|
376
|
+
repo: repoName.repo,
|
|
377
|
+
fullName: `${repoName.owner || String(user.login || "")}/${repoName.repo}`
|
|
378
|
+
}
|
|
379
|
+
const originUrl = await this.remoteUrl(cwd, "origin", kernel)
|
|
380
|
+
if (originUrl) {
|
|
381
|
+
const origin = Github.parseGithubRemote(originUrl)
|
|
382
|
+
if (!Github.repoEquals(origin, targetRepo)) {
|
|
383
|
+
throw new Error(`origin already points at ${originUrl}; refusing to push ${targetRepo.fullName}. Remove or update origin first.`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const request = Github.buildCreateRepoRequest({
|
|
387
|
+
owner: repoName.owner,
|
|
388
|
+
repo: repoName.repo,
|
|
389
|
+
authenticatedUser: user,
|
|
390
|
+
visibility
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
if (ondata) ondata({ raw: `Creating GitHub repository ${repoName.owner ? `${repoName.owner}/` : ""}${repoName.repo}\r\n` })
|
|
394
|
+
const repo = await this.githubRequest(credential.password, "POST", request.path, request.body)
|
|
395
|
+
|
|
396
|
+
if (!originUrl) {
|
|
397
|
+
if (ondata) ondata({ raw: `Adding origin remote ${repo.clone_url}\r\n` })
|
|
398
|
+
await this.runGit(["remote", "add", "origin", repo.clone_url], { cwd, kernel, ondata })
|
|
399
|
+
} else if (ondata) {
|
|
400
|
+
ondata({ raw: `origin already exists: ${originUrl}\r\n` })
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (ondata) ondata({ raw: "Pushing current branch to origin\r\n" })
|
|
404
|
+
await this.runGit(["push", "-u", "origin", "HEAD"], { cwd, kernel, ondata })
|
|
405
|
+
if (ondata) ondata({ raw: `Created ${repo.html_url}\r\n` })
|
|
406
|
+
|
|
407
|
+
return repo
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async fork(req, ondata, kernel) {
|
|
411
|
+
const params = this.params(req)
|
|
412
|
+
const cwd = this.cwd(req, kernel)
|
|
413
|
+
const credential = await this.getCredential(req, kernel)
|
|
414
|
+
const source = await this.githubRemote(cwd, kernel)
|
|
415
|
+
const body = {}
|
|
416
|
+
|
|
417
|
+
const requestedName = Github.cleanParam(params.name)
|
|
418
|
+
if (requestedName) {
|
|
419
|
+
const name = requestedName
|
|
420
|
+
Github.validateRepoName(name)
|
|
421
|
+
body.name = name
|
|
422
|
+
}
|
|
423
|
+
const requestedOrg = Github.cleanParam(params.org)
|
|
424
|
+
if (requestedOrg) {
|
|
425
|
+
const org = requestedOrg
|
|
426
|
+
Github.validateOwner(org)
|
|
427
|
+
body.organization = org
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (ondata) ondata({ raw: `Forking ${source.fullName}\r\n` })
|
|
431
|
+
const forkRepo = await this.githubRequest(
|
|
432
|
+
credential.password,
|
|
433
|
+
"POST",
|
|
434
|
+
`/repos/${encodeURIComponent(source.owner)}/${encodeURIComponent(source.repo)}/forks`,
|
|
435
|
+
body
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
await this.configureForkRemote({ cwd, kernel, ondata, source, forkRepo })
|
|
439
|
+
if (ondata) ondata({ raw: `Forked ${forkRepo.html_url}\r\n` })
|
|
440
|
+
return forkRepo
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
module.exports = Github
|