@gatherthread/dsh-host 0.1.0-alpha.7 → 0.1.0-beta.1
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 +11 -3
- package/bundle/manifest.json +1 -1
- package/client/client.js +116 -37
- package/dist/bundle/native-plugin.js +9175 -2737
- package/dist/bundle/plugin.js +8068 -2403
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ The package does not install a DSH runtime dependency.
|
|
|
19
19
|
After `@gatherthread/dsh-host` is published, the ordinary three-step path is:
|
|
20
20
|
|
|
21
21
|
```text
|
|
22
|
-
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-
|
|
22
|
+
npx @deepseek-ai/dsh@0.1.2-rc.1 plugin --profile web add @gatherthread/dsh-host@0.1.0-beta.1
|
|
23
23
|
npx @deepseek-ai/dsh@0.1.2-rc.1 web
|
|
24
24
|
# In DSH: Settings -> GatherThread / 共序 -> Sign in and pair
|
|
25
25
|
```
|
|
@@ -42,8 +42,8 @@ Inside DSH, choose the future GatherThread public service or enter one custom
|
|
|
42
42
|
server origin. The same field covers an HTTPS LAN host, self-hosted deployment,
|
|
43
43
|
or Tailscale address; plain HTTP is accepted only on loopback. The plugin opens
|
|
44
44
|
an outbound HTTPS connection. A five-minute, single-use short code is confirmed
|
|
45
|
-
through
|
|
46
|
-
|
|
45
|
+
through a signed-in GatherThread email account. Project invitations grant
|
|
46
|
+
membership only and cannot authorize pairing. Signup is closed by default. The resulting long-lived device
|
|
47
47
|
grant is stored only by DSH's credential service and never appears in argv, a
|
|
48
48
|
URL, browser storage, logs, status RPC, or shared history. Once paired, the
|
|
49
49
|
plugin reconnects automatically whenever DSH starts.
|
|
@@ -60,6 +60,14 @@ other plugins remain untouched. The repository-level `npm run dsh:connect --
|
|
|
60
60
|
--help` source-checkout connector and the examples under `bundle/` remain
|
|
61
61
|
advanced/offline diagnostics, not the normal user path.
|
|
62
62
|
|
|
63
|
+
## GitHub code synchronization
|
|
64
|
+
|
|
65
|
+
Beta 1 includes direct GitHub controls and requires matching server, Web app and native plugin releases. The project owner first connects an existing repository and base branch in Web. Install Git 2.38+ and GitHub CLI on the device, then open **Settings → GatherThread / 共序 → 项目代码 · GitHub(推荐)**. Click **连接 GitHub · 打开浏览器** to start local browser sign-in if needed, then authorize that exact project/repository configuration. `gh auth login --hostname github.com --git-protocol https --web` remains a local fallback. This consent is separate from GT Cloud and conversation sync; a new GitHub binding automatically uploads settled changes while idle and can be switched off. Configuration changes, including pause/resume, require renewed local authorization.
|
|
66
|
+
|
|
67
|
+
The Web app may request code operations only on your selected online runtime and cannot grant this local consent. Code and GitHub credentials travel directly to GitHub. GitHub permissions remain independent of GT membership; removing a GT member or project does not revoke GitHub access or delete GitHub data. See [Project code collaboration](../../docs/CODE_SYNC.md) for setup, recovery, limits and GitHub workflow-trigger risks.
|
|
68
|
+
|
|
69
|
+
GitHub 接入仍为**未发布源码预览**,需使用匹配的服务端、网页与原生插件源码构建,不能假定固定版本 npm 包已支持。在网页配置现有仓库后,每台设备安装 Git 2.38+ 与 GitHub CLI,再进入 **Settings → GatherThread / 共序 → 项目代码 · GitHub(推荐)**,点击“连接 GitHub · 打开浏览器”完成本机登录,并核对、逐项目授权。终端命令仍可作为保底。GitHub、GT Cloud 和会话同步分别授权;新 GitHub 绑定在 Agent 空闲时会自动上传稳定改动,可关闭且重连后保持关闭。配置变化及暂停后恢复需重新本地授权。网页不能替代本地文件授权,共序成员变化或项目删除不会撤销 GitHub 权限或删除 GitHub 数据。完整流程见[项目代码协作文档](../../docs/CODE_SYNC.md)。
|
|
70
|
+
|
|
63
71
|
## Security and recovery
|
|
64
72
|
|
|
65
73
|
Only allowlisted public text/tool fields leave DSH. Reasoning blocks, raw model
|
package/bundle/manifest.json
CHANGED
package/client/client.js
CHANGED
|
@@ -105,7 +105,9 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
105
105
|
}
|
|
106
106
|
onSuccess?.(next);
|
|
107
107
|
} catch {
|
|
108
|
-
setNotice(endpoint.startsWith("
|
|
108
|
+
setNotice(endpoint.startsWith("github/") && githubEnglish()
|
|
109
|
+
? "GitHub sync did not finish. Check local sign-in, project access and the remote version; no files were forcibly overwritten."
|
|
110
|
+
: endpoint.startsWith("code/") || endpoint.startsWith("github/")
|
|
109
111
|
? "代码同步未完成。请检查本机授权、项目权限和云端版本;不会强制覆盖文件。"
|
|
110
112
|
: endpoint.startsWith("sync/")
|
|
111
113
|
? "上传操作未完成。请检查连接和会话权限后重试。"
|
|
@@ -184,64 +186,112 @@ function GatherThreadStatusPanel({ connection, sessions }) {
|
|
|
184
186
|
setModel,
|
|
185
187
|
runAction,
|
|
186
188
|
}),
|
|
189
|
+
nativeState === undefined || unavailable ? null : renderCodeSyncControls(nativeState, busy, runAction, true),
|
|
187
190
|
nativeState === undefined || unavailable ? null : renderCodeSyncControls(nativeState, busy, runAction));
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
function
|
|
191
|
-
|
|
193
|
+
function githubEnglish() {
|
|
194
|
+
return typeof navigator !== "undefined" && typeof navigator.language === "string"
|
|
195
|
+
&& !/^zh(?:-|$)/iu.test(navigator.language);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function renderCodeSyncControls(state, busy, runAction, github = false) {
|
|
199
|
+
const projects = github ? state.githubSync : state.codeSync;
|
|
200
|
+
if (!projects?.length) return null;
|
|
201
|
+
const label = (zh, en) => github && githubEnglish() ? en : zh;
|
|
192
202
|
const actions = [
|
|
193
|
-
["code_sync_status", "检查状态"],
|
|
194
|
-
["code_upload", "上传代码"],
|
|
195
|
-
["code_download", "下载更新"],
|
|
196
|
-
["code_recover", "恢复到新目录"],
|
|
203
|
+
["code_sync_status", label("检查状态", "Check status")],
|
|
204
|
+
["code_upload", label("上传代码", "Upload code")],
|
|
205
|
+
["code_download", label("下载更新", "Download updates")],
|
|
206
|
+
["code_recover", label("恢复到新目录", "Recover to a new folder")],
|
|
197
207
|
];
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
if (github) actions.push(["code_update", label("从主分支更新", "Update from base branch")]);
|
|
209
|
+
const prefix = github ? "github_" : "";
|
|
210
|
+
return React.createElement("section", { style: styles.connectionCard, "aria-label": github ? label("GitHub 项目代码同步", "GitHub project code sync") : "项目代码同步" },
|
|
211
|
+
React.createElement("h3", { style: styles.sectionTitle }, github ? label("项目代码 · GitHub(推荐)", "Project code · GitHub (recommended)") : "项目代码 · GatherThread 云端"),
|
|
200
212
|
React.createElement("p", { style: styles.muted },
|
|
201
|
-
"代码与会话上传独立。每位成员使用自己的云端分支;项目成员均可读取代码。上传前请检查源文件,不上传密钥或私人资料。"),
|
|
202
|
-
|
|
203
|
-
|
|
213
|
+
github ? label("本机直接与 GitHub 同步,代码和 GitHub 凭据不经过 GatherThread。GitHub 仓库成员权限独立管理;自动上传可能触发仓库 Actions。", "This device syncs directly with GitHub; source and credentials do not pass through GatherThread. GitHub access is managed separately, and automatic pushes may trigger Actions.") : "代码与会话上传独立。每位成员使用自己的云端分支;项目成员均可读取代码。上传前请检查源文件,不上传密钥或私人资料。"),
|
|
214
|
+
github ? React.createElement("p", { style: styles.muted }, label("先在本机安装 Git 和 GitHub CLI;点击下方按钮会打开浏览器完成 GitHub 登录。", "Install Git and GitHub CLI on this device, then use the button below to open browser sign-in.")) : null,
|
|
215
|
+
...projects.map((project) => {
|
|
216
|
+
const invoke = (action) => void runAction(github ? "github/action" : "code/action", { projectId: project.projectId, action: `${prefix}${action}` });
|
|
204
217
|
const status = project.status;
|
|
218
|
+
const enabled = github ? project.connection?.enabled === true : status?.enabled;
|
|
205
219
|
return React.createElement("details", { key: project.projectId, style: { ...styles.row, display: "block" } },
|
|
206
220
|
React.createElement("summary", { style: styles.sessionTitle }, project.projectName),
|
|
207
221
|
React.createElement("div", { style: styles.form },
|
|
222
|
+
github ? React.createElement("p", { style: styles.sessionMeta }, project.connection
|
|
223
|
+
? `${project.connection.repository} · ${label("主分支", "Base")} ${project.connection.base_branch} · ${label("成员分支", "My branch")} ${project.branch ?? label("尚未分配", "not assigned")}${enabled ? "" : ` · ${label("已暂停", "paused")}`}`
|
|
224
|
+
: label("请由项目创建者在 GatherThread 的项目代码中绑定 GitHub 仓库。", "Ask the project owner to connect a GitHub repository in GatherThread project code settings.")) : null,
|
|
225
|
+
github ? React.createElement("button", { type: "button", style: styles.compactButton,
|
|
226
|
+
disabled: busy || !project.connection,
|
|
227
|
+
onClick: () => void runAction("github/connect", { projectId: project.projectId }),
|
|
228
|
+
}, label("连接 GitHub · 打开浏览器", "Connect GitHub · Open browser")) : null,
|
|
208
229
|
React.createElement("label", { style: styles.syncToggle },
|
|
209
230
|
React.createElement("input", {
|
|
210
|
-
type: "checkbox", checked: project.authorized, disabled: busy,
|
|
211
|
-
onChange: (event) => void runAction("code/authorize", {
|
|
231
|
+
type: "checkbox", checked: project.authorized, disabled: busy || (github && !project.authorized && !enabled),
|
|
232
|
+
onChange: (event) => void runAction(github ? "github/authorize" : "code/authorize", {
|
|
212
233
|
projectId: project.projectId, enabled: event.target.checked,
|
|
234
|
+
...(github && event.target.checked && project.connection ? {
|
|
235
|
+
repository: project.connection.repository, base_branch: project.connection.base_branch, revision: project.connection.revision,
|
|
236
|
+
} : {}),
|
|
213
237
|
}),
|
|
214
|
-
}), "允许此 DSH 同步该项目代码"),
|
|
215
|
-
React.createElement("p", { style: styles.muted }, "仅访问此项目已经绑定的本地目录,不改动原 Git 分支或暂存区。下载要求本地没有未上传改动;恢复始终新建目录,不更换当前 Agent 工作目录。"),
|
|
238
|
+
}), github ? label("允许此 DSH 将本项目代码同步到上述 GitHub 仓库", "Allow this DSH to sync this project's code to the GitHub repository above") : "允许此 DSH 同步该项目代码"),
|
|
239
|
+
React.createElement("p", { style: styles.muted }, label("仅访问此项目已经绑定的本地目录,不改动原 Git 分支或暂存区。下载要求本地没有未上传改动;恢复始终新建目录,不更换当前 Agent 工作目录。", "Only the local folder already bound to this project is accessed. Your original Git branch and index stay unchanged. Downloads require no unuploaded changes; recovery creates a separate folder.")),
|
|
216
240
|
status ? React.createElement("p", { style: styles.sessionMeta },
|
|
217
|
-
status.local_status_unknown ? "已恢复云端副本;原工作区状态未知,请单独检查。" : status.enabled
|
|
218
|
-
? `${status.file_count} 个源文件 · ${status.local_changes} 项待上传 · ${status.excluded_count} 项已排除 · 云端 ${status.cloud_commit?.slice(0, 8) ?? "尚无版本"}`
|
|
219
|
-
: "请先由项目创建者在 GatherThread 的「项目代码」中启用 Git。") : null,
|
|
220
|
-
status?.needs_download ? React.createElement("p", { style: styles.notice }, "云端有新版本。请先下载;本地有改动时请恢复到新目录比较,不会自动覆盖。") : null,
|
|
241
|
+
status.local_status_unknown ? label("已恢复云端副本;原工作区状态未知,请单独检查。", "A remote copy was recovered. Review the original workspace separately; its state is unknown.") : status.enabled
|
|
242
|
+
? `${status.file_count} ${label("个源文件", "source files")} · ${status.local_changes} ${label("项待上传", "pending changes")} · ${status.excluded_count} ${label("项已排除", "excluded")} · ${label("云端", "remote")} ${status.cloud_commit?.slice(0, 8) ?? label("尚无版本", "no version yet")}`
|
|
243
|
+
: github ? label("GitHub 同步已暂停,请由项目创建者恢复。", "GitHub sync is paused; ask the project owner to resume it.") : "请先由项目创建者在 GatherThread 的「项目代码」中启用 Git。") : null,
|
|
244
|
+
status?.needs_download ? React.createElement("p", { style: styles.notice }, label("云端有新版本。请先下载;本地有改动时请恢复到新目录比较,不会自动覆盖。", "A newer remote version exists. Download when your local files are clean; otherwise recover to a new folder for comparison.")) : null,
|
|
221
245
|
project.authorized ? React.createElement("label", { style: styles.syncToggle },
|
|
222
246
|
React.createElement("input", {
|
|
223
247
|
type: "checkbox", checked: status?.automatic_upload === true,
|
|
224
|
-
disabled: busy || !status?.
|
|
248
|
+
disabled: busy || (!enabled && !status?.automatic_upload),
|
|
225
249
|
onChange: (event) => invoke(event.target.checked ? "code_auto_upload_enable" : "code_auto_upload_disable"),
|
|
226
|
-
}), "空闲时自动上传本地代码至云端") : null,
|
|
227
|
-
React.createElement("div", { style: styles.syncActions }, ...actions.map(([action,
|
|
250
|
+
}), github ? label("空闲时自动上传本地代码至 GitHub", "Automatically upload local code to GitHub while idle") : "空闲时自动上传本地代码至云端") : null,
|
|
251
|
+
React.createElement("div", { style: styles.syncActions }, ...actions.map(([action, actionLabel]) => (
|
|
228
252
|
React.createElement("button", {
|
|
229
253
|
key: action, type: "button", style: styles.compactButton,
|
|
230
|
-
disabled: busy || !project.authorized || (action !== "code_sync_status" && !
|
|
254
|
+
disabled: busy || (!project.authorized && !(github && action === "code_sync_status")) || (action !== "code_sync_status" && !enabled),
|
|
231
255
|
onClick: () => {
|
|
232
|
-
if (action === "code_recover" && !window.confirm("将云端代码恢复到当前项目旁的新目录,原目录和会话保持不变。继续?")) return;
|
|
256
|
+
if (action === "code_recover" && !window.confirm(label("将云端代码恢复到当前项目旁的新目录,原目录和会话保持不变。继续?", "Recover remote code to a new sibling folder? The original folder and conversations stay unchanged."))) return;
|
|
233
257
|
invoke(action);
|
|
234
258
|
},
|
|
235
|
-
},
|
|
259
|
+
}, actionLabel)
|
|
236
260
|
))),
|
|
237
261
|
status?.recovery_directory ? React.createElement("p", { role: "status", style: styles.notice },
|
|
238
|
-
|
|
239
|
-
project.error ? React.createElement("p", { role: "status", style: styles.notice }, codeSyncErrorLabel(project.error)) : null,
|
|
262
|
+
`${label("已恢复至项目同级目录:", "Recovered to a sibling folder: ")}${status.recovery_directory}${label("。在本地 Agent 中打开该目录继续工作;原会话保持不变。", ". Open that folder in your local Agent to continue; original conversations remain unchanged.")}`) : null,
|
|
263
|
+
project.error ? React.createElement("p", { role: "status", style: styles.notice }, codeSyncErrorLabel(project.error, github)) : null,
|
|
240
264
|
));
|
|
241
265
|
}));
|
|
242
266
|
}
|
|
243
267
|
|
|
244
|
-
function codeSyncErrorLabel(code) {
|
|
268
|
+
function codeSyncErrorLabel(code, github = false) {
|
|
269
|
+
if (github && githubEnglish()) {
|
|
270
|
+
const labels = {
|
|
271
|
+
code_sync_disabled: "Allow this DSH to sync project code first.",
|
|
272
|
+
code_sync_busy: "The Agent is working. Try again after this turn finishes.",
|
|
273
|
+
code_workspace_busy: "The Agent is working. Try again after this turn finishes.",
|
|
274
|
+
code_sync_dirty: "Local changes are not uploaded. Upload them first, or recover remote code to a new folder for comparison.",
|
|
275
|
+
code_sync_recovery_required: "The local folder is missing or a download was interrupted. Recover remote code to a new folder first.",
|
|
276
|
+
code_sync_forbidden: "Code access was denied. Check this device's sign-in and project permissions.",
|
|
277
|
+
code_sync_conflict: "The remote version changed. Download safely or recover to a new folder; nothing was overwritten.",
|
|
278
|
+
code_conflict: "The remote version conflicted. Check the latest branch while preserving local changes.",
|
|
279
|
+
code_stale_head: "A newer remote version exists. Recover to a new folder before comparing changes.",
|
|
280
|
+
code_merge_conflict: "Branches conflict. Preserve local changes and resolve the conflict after comparing versions.",
|
|
281
|
+
code_git_unavailable: "Git is unavailable. Check this device's Git installation.",
|
|
282
|
+
code_secret_detected: "A possible secret was detected, so the upload was blocked. Check project files.",
|
|
283
|
+
code_sync_secret: "A possible secret was detected, so the upload was blocked. Check project files.",
|
|
284
|
+
code_github_binding_changed: "The GitHub repository changed. Review the repository and base branch, then authorize again.",
|
|
285
|
+
code_github_not_configured: "Ask the project owner to connect a GitHub repository first.",
|
|
286
|
+
code_github_unavailable: "GitHub settings are temporarily unavailable. Check the connection and retry.",
|
|
287
|
+
code_sync_binding: "The code sync target changed. Review the repository and local authorization.",
|
|
288
|
+
code_sync_unavailable: "Cannot connect to GitHub. Check Git, GitHub CLI sign-in, network and repository access on this device.",
|
|
289
|
+
code_sync_limit: "Project files exceed this sync method's safety limits. Check large files and exclusions.",
|
|
290
|
+
code_sync_unsupported: "This repository contains unsupported content; no code was changed.",
|
|
291
|
+
code_sync_unsafe_path: "The project contains an unsafe or unsupported path. Sync was stopped.",
|
|
292
|
+
};
|
|
293
|
+
return labels[code] ?? "Code sync did not finish. Check authorization, files and remote status; conversation sync is unaffected.";
|
|
294
|
+
}
|
|
245
295
|
const labels = {
|
|
246
296
|
code_sync_disabled: "请先允许此 DSH 同步项目代码。",
|
|
247
297
|
code_sync_busy: "Agent 正在工作,请等待本轮结束后再同步。",
|
|
@@ -259,6 +309,14 @@ function codeSyncErrorLabel(code) {
|
|
|
259
309
|
code_secret_detected: "检测到可能的密钥,已阻止上传。请检查项目文件。",
|
|
260
310
|
code_not_enabled: "请先由项目创建者在 GatherThread 启用项目 Git。",
|
|
261
311
|
code_sync_secret: "检测到可能的密钥,已阻止上传。请检查项目文件。",
|
|
312
|
+
code_github_binding_changed: "GitHub 仓库配置已变化,请核对最新仓库和主分支后重新授权。",
|
|
313
|
+
code_github_not_configured: "请先由项目创建者绑定 GitHub 仓库。",
|
|
314
|
+
code_github_unavailable: "GitHub 配置信息暂不可用,请检查连接后重试。",
|
|
315
|
+
code_sync_binding: "代码同步绑定已变化。请核对当前仓库与本地授权后重试。",
|
|
316
|
+
code_sync_unavailable: github ? "无法连接 GitHub。请检查本机 Git、GitHub CLI 登录状态、网络和仓库访问权限。" : "代码同步暂不可用,请检查本机 Git 与服务连接。",
|
|
317
|
+
code_sync_limit: "项目文件超过同步限制,未上传。请检查大文件和排除范围。",
|
|
318
|
+
code_sync_unsupported: "仓库包含当前同步方式不支持的内容,未修改代码。",
|
|
319
|
+
code_sync_unsafe_path: "项目含不安全或不支持的文件路径,已停止同步。",
|
|
262
320
|
};
|
|
263
321
|
return labels[code] ?? "代码同步未完成。请检查授权、文件和云端状态后重试;会话同步不受影响。";
|
|
264
322
|
}
|
|
@@ -317,7 +375,7 @@ function renderNativeControls(input) {
|
|
|
317
375
|
disabled: input.busy || state.officialServerUrl === undefined,
|
|
318
376
|
style: styles.secondaryButton,
|
|
319
377
|
onClick: () => input.setServerUrl(state.officialServerUrl || ""),
|
|
320
|
-
}, state.officialServerUrl === undefined ? "
|
|
378
|
+
}, state.officialServerUrl === undefined ? "输入共序服务器地址" : "使用共序官方服务"),
|
|
321
379
|
React.createElement("span", { style: styles.muted }, "局域网、自托管与 Tailscale 请使用自定义地址。")),
|
|
322
380
|
React.createElement("form", {
|
|
323
381
|
key: "pairing-form",
|
|
@@ -499,7 +557,7 @@ function detail(label, value) {
|
|
|
499
557
|
function parseNativeState(value) {
|
|
500
558
|
exactObject(value, [
|
|
501
559
|
"schemaVersion", "integration", "authorization", "compatibility", "runtime",
|
|
502
|
-
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "codeSync", "pairing", "recoverableError",
|
|
560
|
+
"officialServerUrl", "serverUrl", "deviceName", "route", "projectCount", "bindings", "localSync", "codeSync", "githubSync", "pairing", "recoverableError",
|
|
503
561
|
]);
|
|
504
562
|
if (value.schemaVersion !== 2 || value.integration !== "gatherthread") throw new Error("invalid native status identity");
|
|
505
563
|
if (!AUTHORIZATION_STATES.has(value.authorization)) throw new Error("invalid native authorization state");
|
|
@@ -524,6 +582,7 @@ function parseNativeState(value) {
|
|
|
524
582
|
let bindings;
|
|
525
583
|
let localSync;
|
|
526
584
|
let codeSync;
|
|
585
|
+
let githubSync;
|
|
527
586
|
if (value.route !== undefined || value.bindings !== undefined || value.projectCount !== undefined) {
|
|
528
587
|
if (value.route === undefined || !Number.isSafeInteger(value.projectCount) || value.projectCount < 0
|
|
529
588
|
|| !Array.isArray(value.bindings) || value.bindings.length > 100
|
|
@@ -564,17 +623,34 @@ function parseNativeState(value) {
|
|
|
564
623
|
}
|
|
565
624
|
return { ...sync };
|
|
566
625
|
});
|
|
567
|
-
const codeSyncValue =
|
|
626
|
+
const parseCodeProjects = (codeSyncValue, github = false) => {
|
|
568
627
|
if (!Array.isArray(codeSyncValue) || codeSyncValue.length > 100) throw new Error("invalid code sync status");
|
|
569
|
-
|
|
570
|
-
exactObject(project, ["projectId", "projectName", "authorized", "status", "error"]);
|
|
628
|
+
return codeSyncValue.map((project) => {
|
|
629
|
+
exactObject(project, ["projectId", "projectName", "authorized", "status", "error", ...(github ? ["connection", "branch"] : [])]);
|
|
571
630
|
boundedText(project.projectId, 128);
|
|
572
631
|
boundedText(project.projectName, 160);
|
|
573
632
|
if (typeof project.authorized !== "boolean") throw new Error("invalid code sync authorization");
|
|
574
|
-
if (project.error !== undefined && !/^code_[a-z_]{1,60}$/u.test(project.error)) throw new Error("invalid code sync error");
|
|
633
|
+
if (project.error !== undefined && !/^(?:github_)?code_[a-z_]{1,60}$/u.test(project.error)) throw new Error("invalid code sync error");
|
|
634
|
+
if (github) {
|
|
635
|
+
if (project.connection !== null) {
|
|
636
|
+
exactObject(project.connection, ["repository", "base_branch", "revision", "enabled"]);
|
|
637
|
+
boundedText(project.connection.repository, 160);
|
|
638
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(project.connection.repository)) throw new Error("invalid GitHub repository");
|
|
639
|
+
boundedText(project.connection.base_branch, 255);
|
|
640
|
+
boundedText(project.connection.revision, 128);
|
|
641
|
+
if (typeof project.connection.enabled !== "boolean") throw new Error("invalid GitHub state");
|
|
642
|
+
}
|
|
643
|
+
if (project.branch !== null) boundedText(project.branch, 255);
|
|
644
|
+
}
|
|
575
645
|
if (project.status !== undefined) {
|
|
576
646
|
const status = project.status;
|
|
577
|
-
exactObject(status, ["enabled", "automatic_upload", "local_changes", "file_count", "excluded_count", "base_commit", "cloud_commit", "branch_id", "needs_download", "recovery_directory", "local_status_unknown"]);
|
|
647
|
+
exactObject(status, ["enabled", "automatic_upload", "local_changes", "file_count", "excluded_count", "base_commit", "cloud_commit", "branch_id", "needs_download", "recovery_directory", "local_status_unknown", ...(github ? ["provider", "repository", "branch", "base_branch"] : [])]);
|
|
648
|
+
if (github) {
|
|
649
|
+
if (status.provider !== undefined && status.provider !== "github") throw new Error("invalid code provider");
|
|
650
|
+
if (status.repository !== undefined && status.repository !== project.connection?.repository) throw new Error("GitHub target mismatch");
|
|
651
|
+
if (status.base_branch !== undefined && status.base_branch !== project.connection?.base_branch) throw new Error("GitHub base mismatch");
|
|
652
|
+
if (status.branch !== undefined) boundedText(status.branch, 255);
|
|
653
|
+
}
|
|
578
654
|
if (status.local_status_unknown !== undefined && typeof status.local_status_unknown !== "boolean") throw new Error("invalid local status flag");
|
|
579
655
|
for (const key of ["enabled", "automatic_upload", "needs_download"]) {
|
|
580
656
|
if (typeof status[key] !== "boolean") throw new Error("invalid code sync flag");
|
|
@@ -593,6 +669,9 @@ function parseNativeState(value) {
|
|
|
593
669
|
}
|
|
594
670
|
return { ...project };
|
|
595
671
|
});
|
|
672
|
+
};
|
|
673
|
+
codeSync = parseCodeProjects(value.codeSync ?? []);
|
|
674
|
+
githubSync = parseCodeProjects(value.githubSync ?? [], true);
|
|
596
675
|
}
|
|
597
676
|
let pairing;
|
|
598
677
|
if (value.pairing !== undefined) {
|
|
@@ -628,7 +707,7 @@ function parseNativeState(value) {
|
|
|
628
707
|
...(officialServerUrl === undefined ? {} : { officialServerUrl }),
|
|
629
708
|
...(serverUrl === undefined ? {} : { serverUrl }),
|
|
630
709
|
...(value.deviceName === undefined ? {} : { deviceName: value.deviceName }),
|
|
631
|
-
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync, codeSync }),
|
|
710
|
+
...(route === undefined ? {} : { route, projectCount: value.projectCount, bindings, localSync, codeSync, githubSync }),
|
|
632
711
|
...(pairing === undefined ? {} : { pairing }),
|
|
633
712
|
...(value.recoverableError === undefined ? {} : { recoverableError: value.recoverableError }),
|
|
634
713
|
};
|