Fix Gist setup and first-run bootstrap

This commit is contained in:
2026-07-29 17:43:26 +08:00
parent 13cf327f48
commit f19466a5df
9 changed files with 189 additions and 65 deletions
+4 -4
View File
@@ -18,7 +18,7 @@ macOS/Linux
curl -fsSL https://git.okk.cool/purp1e/zed-sync/raw/branch/master/bootstrap-from-gist.sh | sh curl -fsSL https://git.okk.cool/purp1e/zed-sync/raw/branch/master/bootstrap-from-gist.sh | sh
``` ```
脚本会处理 GitHub 登录询问 Gist URL/ID、从 Gist 下载单个 `zed-settings-sync.json`备份当前 Zed 配置并完成安装安装后重启 Zed。 脚本会处理 GitHub 登录询问 Gist URL/ID。输入已有 Gist 下载其配置;直接留空会从本仓库种子配置自动创建新的 Secret Gist。随后脚本会备份当前 Zed 配置并完成安装安装后重启 Zed。
For first-machine setup, new-machine onboarding, daily Gist synchronization, and conflict handling, see [`SYNC-MANUAL.zh-CN.md`](./SYNC-MANUAL.zh-CN.md). For first-machine setup, new-machine onboarding, daily Gist synchronization, and conflict handling, see [`SYNC-MANUAL.zh-CN.md`](./SYNC-MANUAL.zh-CN.md).
@@ -38,12 +38,12 @@ For first-machine setup, new-machine onboarding, daily Gist synchronization, and
Open the Task Picker and search for `Zed Settings`: Open the Task Picker and search for `Zed Settings`:
- `Export JSON...` and `Import JSON...` show a native file picker. - `Export JSON...` and `Import JSON...` show a native file picker.
- `Configure GitHub Gist...` accepts a Gist ID or complete URL and remembers it on this machine. - `Configure GitHub Gist...` accepts an existing Gist ID or complete URL and remembers it on this machine.
- `Show GitHub Gist Status` compares the current, remote, and last synchronized SHA-256 hashes. - `Show GitHub Gist Status` compares the current, remote, and last synchronized SHA-256 hashes.
- `Push to GitHub Gist` and `Pull from GitHub Gist` use the remembered Gist and ask before overwriting divergent settings. - `Push to GitHub Gist` creates a new Secret Gist when none is configured; later Push/Pull operations use the remembered Gist and ask before overwriting divergent settings.
- `Authenticate GitHub...` runs `gh auth login` in the terminal. - `Authenticate GitHub...` runs `gh auth login` in the terminal.
GitHub CLI (`gh`) must be installed. The Gist must already exist and may contain other files; synchronization only creates or replaces `zed-settings-sync.json` inside it. GitHub CLI (`gh`) must be installed and authenticated. An existing Gist may contain other files; synchronization only creates or replaces `zed-settings-sync.json` inside it.
The synchronized JSON is deliberately flat. Its top-level keys are paths such as `settings.json`, `keymap.json`, and `scripts/file-exclusions.json`, whose values are the complete file contents. No version, metadata, `files` wrapper, or hash is embedded. SHA-256 values are calculated at runtime and only the last synchronized value is retained locally. The synchronized JSON is deliberately flat. Its top-level keys are paths such as `settings.json`, `keymap.json`, and `scripts/file-exclusions.json`, whose values are the complete file contents. No version, metadata, `files` wrapper, or hash is embedded. SHA-256 values are calculated at runtime and only the last synchronized value is retained locally.
+9 -10
View File
@@ -22,9 +22,9 @@
## 2. 准备 Gist ## 2. 准备 Gist
1. 登录 GitHub,打开 <https://gist.github.com/> 不需要预先创建 Gist:第一次执行 Push 时,如果本机尚未配置 Gist,系统会询问是否自动创建新的 Secret Gist
2. 创建一个 Secret Gist。GitHub 不允许创建完全空白的 Gist,可以先放一个 `README.md`
3. 复制完整 Gist URL 或末尾的 Gist ID 已有 Gist 时,也可以登录 GitHub、打开 <https://gist.github.com/>,复制完整 Gist URL 或末尾的 Gist ID,再通过 `Configure GitHub Gist...` 关联
Gist ID 不是访问令牌。身份认证由 GitHub CLI `gh` 管理。 Gist ID 不是访问令牌。身份认证由 GitHub CLI `gh` 管理。
@@ -58,13 +58,12 @@ Zed Settings: Authenticate GitHub...
gh auth login gh auth login
``` ```
### 3.2 保存 Gist ### 3.2 创建或关联 Gist
1.`Mod+R` 1.`Mod+R`
2. 运行 `Zed Settings: Configure GitHub Gist...` 2. 没有 Gist 时直接运行 `Zed Settings: Push to GitHub Gist`,确认自动创建
3. 粘贴 Gist URL 或 Gist ID 3. 已有 Gist 时先运行 `Zed Settings: Configure GitHub Gist...` 并粘贴 URL/ID,再执行 Push
4. 运行 `Zed Settings: Push to GitHub Gist` 4. 运行 `Zed Settings: Show GitHub Gist Status`,确认状态为 `Synchronized`
5. 运行 `Zed Settings: Show GitHub Gist Status`,确认状态为 `Synchronized`
Push 只会在该 Gist 中创建或更新 `zed-settings-sync.json`,不会删除 Gist 中的其他文件。 Push 只会在该 Gist 中创建或更新 `zed-settings-sync.json`,不会删除 Gist 中的其他文件。
@@ -126,8 +125,8 @@ curl -fsSL https://<Gitea域名>/<用户>/<仓库>/raw/branch/main/bootstrap-fro
1. 检查 Node.js 和 `gh` 1. 检查 Node.js 和 `gh`
2. 在尚未登录时运行 `gh auth login` 2. 在尚未登录时运行 `gh auth login`
3. 询问 Gist URL 或 ID。 3. 询问 Gist URL 或 ID;留空时自动创建新的 Secret Gist
4. 从 GitHub API 读取 `zed-settings-sync.json` 4. 输入已有 Gist 时从 GitHub API 读取配置;留空时使用本仓库的 `zed-settings-sync.json` 种子配置
5. 验证顶层文件路径,防止写出 Zed 配置目录。 5. 验证顶层文件路径,防止写出 Zed 配置目录。
6. 备份新机器已有配置。 6. 备份新机器已有配置。
7. 安装设置、键位、Tasks、snippets、themes 和辅助脚本。 7. 安装设置、键位、Tasks、snippets、themes 和辅助脚本。
+22 -6
View File
@@ -48,18 +48,20 @@ function Write-Atomic([string]$Path, [string]$Contents) {
} }
Require-Command "node" "winget install OpenJS.NodeJS.LTS" Require-Command "node" "winget install OpenJS.NodeJS.LTS"
Require-Command "gh" "winget install GitHub.cli" if (-not $env:ZED_SETTINGS_BUNDLE_PATH) {
Require-Command "gh" "winget install GitHub.cli"
& gh auth status *> $null Remove-Item Env:GH_TOKEN,Env:GITHUB_TOKEN -ErrorAction SilentlyContinue
if ($LASTEXITCODE -ne 0) { & gh auth status *> $null
if ($LASTEXITCODE -ne 0) {
Write-Host "GitHub authentication is required." Write-Host "GitHub authentication is required."
& gh auth login & gh auth login
if ($LASTEXITCODE -ne 0) { throw "GitHub authentication failed." } if ($LASTEXITCODE -ne 0) { throw "GitHub authentication failed." }
}
} }
$entered = if ($env:ZED_SETTINGS_GIST_ID) { $env:ZED_SETTINGS_GIST_ID } else { Read-Host "GitHub Gist ID or full URL" } $entered = if ($env:ZED_SETTINGS_GIST_ID) { $env:ZED_SETTINGS_GIST_ID } else { Read-Host "GitHub Gist ID or URL (leave blank to create a new Secret Gist)" }
$gistId = Get-GistId $entered
if ($env:ZED_SETTINGS_BUNDLE_PATH) { if ($env:ZED_SETTINGS_BUNDLE_PATH) {
$gistId = Get-GistId $entered
$bundleSource = [IO.File]::ReadAllText($env:ZED_SETTINGS_BUNDLE_PATH, [Text.Encoding]::UTF8) $bundleSource = [IO.File]::ReadAllText($env:ZED_SETTINGS_BUNDLE_PATH, [Text.Encoding]::UTF8)
} else { } else {
$token = (& gh auth token).Trim() $token = (& gh auth token).Trim()
@@ -70,11 +72,25 @@ if ($env:ZED_SETTINGS_BUNDLE_PATH) {
"User-Agent" = "zed-settings-bootstrap" "User-Agent" = "zed-settings-bootstrap"
"X-GitHub-Api-Version" = "2022-11-28" "X-GitHub-Api-Version" = "2022-11-28"
} }
if ([string]::IsNullOrWhiteSpace($entered)) {
$seedUrl = "https://git.okk.cool/purp1e/zed-sync/raw/branch/master/zed-settings-sync.json"
$bundleSource = (Invoke-WebRequest -UseBasicParsing -Uri $seedUrl).Content
$payload = @{
description = "Zed settings sync"
public = $false
files = @{ $bundleName = @{ content = $bundleSource } }
} | ConvertTo-Json -Depth 5
$created = Invoke-RestMethod -Method Post -Uri "https://api.github.com/gists" -Headers $headers -ContentType "application/json" -Body ([Text.Encoding]::UTF8.GetBytes($payload))
$gistId = $created.id
Write-Host "Created Secret Gist: $($created.html_url)"
} else {
$gistId = Get-GistId $entered
$gist = Invoke-RestMethod -Uri "https://api.github.com/gists/$gistId" -Headers $headers $gist = Invoke-RestMethod -Uri "https://api.github.com/gists/$gistId" -Headers $headers
$remoteFile = $gist.files.$bundleName $remoteFile = $gist.files.$bundleName
if (-not $remoteFile) { throw "Gist $gistId does not contain $bundleName." } if (-not $remoteFile) { throw "Gist $gistId does not contain $bundleName." }
if ($remoteFile.truncated) { throw "$bundleName is too large for the Gist API response." } if ($remoteFile.truncated) { throw "$bundleName is too large for the Gist API response." }
$bundleSource = [string]$remoteFile.content $bundleSource = [string]$remoteFile.content
}
} }
try { try {
+47 -15
View File
@@ -5,24 +5,63 @@ command -v node >/dev/null 2>&1 || {
printf '%s\n' 'Node.js is required. Install it with your system package manager.' >&2 printf '%s\n' 'Node.js is required. Install it with your system package manager.' >&2
exit 1 exit 1
} }
command -v gh >/dev/null 2>&1 || { if [ -z "${ZED_SETTINGS_BUNDLE_PATH:-}" ]; then
command -v gh >/dev/null 2>&1 || {
printf '%s\n' 'GitHub CLI (gh) is required: https://cli.github.com/' >&2 printf '%s\n' 'GitHub CLI (gh) is required: https://cli.github.com/' >&2
exit 1 exit 1
} }
unset GH_TOKEN GITHUB_TOKEN
if ! gh auth status >/dev/null 2>&1; then if ! gh auth status >/dev/null 2>&1; then
printf '%s\n' 'GitHub authentication is required.' printf '%s\n' 'GitHub authentication is required.'
gh auth login </dev/tty >/dev/tty gh auth login </dev/tty >/dev/tty
fi
fi fi
if [ -n "${ZED_SETTINGS_GIST_ID:-}" ]; then if [ -n "${ZED_SETTINGS_GIST_ID:-}" ]; then
entered=$ZED_SETTINGS_GIST_ID entered=$ZED_SETTINGS_GIST_ID
else else
printf 'GitHub Gist ID or full URL: ' >/dev/tty printf 'GitHub Gist ID or URL (leave blank to create a new Secret Gist): ' >/dev/tty
IFS= read -r entered </dev/tty IFS= read -r entered </dev/tty
fi fi
gist_id=$(printf '%s' "$entered" | sed -nE 's#.*[^a-fA-F0-9]([a-fA-F0-9]{8,})/?$#\1#p')
if [ -z "$gist_id" ]; then temporary_dir=$(mktemp -d)
trap 'rm -rf "$temporary_dir"' EXIT INT TERM
response="$temporary_dir/gist.json"
bundle_path=${ZED_SETTINGS_BUNDLE_PATH:-}
if [ -n "$bundle_path" ]; then
gist_id=$(printf '%s' "$entered" | sed -nE 's#.*[^a-fA-F0-9]([a-fA-F0-9]{8,})/?$#\1#p')
elif [ -z "$entered" ]; then
bundle_path="$temporary_dir/zed-settings-sync.json"
payload="$temporary_dir/create-gist.json"
curl -fsSL 'https://git.okk.cool/purp1e/zed-sync/raw/branch/master/zed-settings-sync.json' -o "$bundle_path"
node - "$bundle_path" "$payload" <<'NODE_CREATE'
const fs = require("node:fs");
const [bundlePath, payloadPath] = process.argv.slice(2);
const content = fs.readFileSync(bundlePath, "utf8");
fs.writeFileSync(payloadPath, JSON.stringify({
description: "Zed settings sync",
public: false,
files: { "zed-settings-sync.json": { content } },
}));
NODE_CREATE
gh api --method POST gists --input "$payload" > "$response"
gist_id=$(node -e 'const g=require(process.argv[1]); process.stdout.write(g.id)' "$response")
gist_url=$(node -e 'const g=require(process.argv[1]); process.stdout.write(g.html_url)' "$response")
printf 'Created Secret Gist: %s\n' "$gist_url"
else
gist_id=$(printf '%s' "$entered" | sed -nE 's#.*[^a-fA-F0-9]([a-fA-F0-9]{8,})/?$#\1#p')
if [ -z "$gist_id" ]; then
gist_id=$(printf '%s' "$entered" | sed -nE 's#^([a-fA-F0-9]{8,})$#\1#p')
fi
if [ -z "$gist_id" ]; then
printf '%s\n' 'Enter a valid GitHub Gist ID or URL.' >&2
exit 1
fi
gh api "gists/$gist_id" > "$response"
fi
if [ -z "${gist_id:-}" ]; then
gist_id=$(printf '%s' "$entered" | sed -nE 's#^([a-fA-F0-9]{8,})$#\1#p') gist_id=$(printf '%s' "$entered" | sed -nE 's#^([a-fA-F0-9]{8,})$#\1#p')
fi fi
if [ -z "$gist_id" ]; then if [ -z "$gist_id" ]; then
@@ -30,14 +69,7 @@ if [ -z "$gist_id" ]; then
exit 1 exit 1
fi fi
temporary_dir=$(mktemp -d) node - "$response" "$gist_id" "$bundle_path" <<'NODE'
trap 'rm -rf "$temporary_dir"' EXIT INT TERM
response="$temporary_dir/gist.json"
if [ -z "${ZED_SETTINGS_BUNDLE_PATH:-}" ]; then
gh api "gists/$gist_id" > "$response"
fi
node - "$response" "$gist_id" "${ZED_SETTINGS_BUNDLE_PATH:-}" <<'NODE'
const { createHash } = require("node:crypto"); const { createHash } = require("node:crypto");
const fs = require("node:fs"); const fs = require("node:fs");
const os = require("node:os"); const os = require("node:os");
+3
View File
@@ -22,4 +22,7 @@ foreach ($script in @(
} }
[IO.File]::WriteAllText((Join-Path $target "tasks.json"), $tasks, $utf8) [IO.File]::WriteAllText((Join-Path $target "tasks.json"), $tasks, $utf8)
node (Join-Path $source "scripts\zed-settings-sync.mjs") export (Join-Path $target "zed-settings-sync.json")
if ($LASTEXITCODE -ne 0) { throw "Failed to generate zed-settings-sync.json" }
Write-Host "Exported Zed configuration from $source" Write-Host "Exported Zed configuration from $source"
+2
View File
@@ -19,4 +19,6 @@ const tasks = fs.readFileSync(source, "utf8")
fs.writeFileSync(target, tasks); fs.writeFileSync(target, tasks);
NODE NODE
node "$source/scripts/zed-settings-sync.mjs" export "$target/zed-settings-sync.json"
printf 'Exported Zed configuration from %s\n' "$source" printf 'Exported Zed configuration from %s\n' "$source"
+70 -10
View File
@@ -191,7 +191,7 @@ function runPowerShell(command, extraEnv = {}) {
const result = spawnSync("powershell.exe", ["-NoProfile", "-STA", "-Command", command], { const result = spawnSync("powershell.exe", ["-NoProfile", "-STA", "-Command", command], {
encoding: "utf8", encoding: "utf8",
env: { ...process.env, ...extraEnv }, env: { ...process.env, ...extraEnv },
windowsHide: true, windowsHide: false,
}); });
if (result.status !== 0) throw new Error(result.stderr.trim() || "Windows dialog failed"); if (result.status !== 0) throw new Error(result.stderr.trim() || "Windows dialog failed");
return result.stdout.trim(); return result.stdout.trim();
@@ -199,10 +199,10 @@ function runPowerShell(command, extraEnv = {}) {
function nativeFileDialog(mode) { function nativeFileDialog(mode) {
if (process.platform === "win32") { if (process.platform === "win32") {
const common = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; "; const common = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); ";
const command = mode === "save" const command = mode === "save"
? `${common}$d=New-Object System.Windows.Forms.SaveFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; $d.AddExtension=$true; if($d.ShowDialog() -eq 'OK'){[Console]::Write($d.FileName)}` ? `${common}$d=New-Object System.Windows.Forms.SaveFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; $d.AddExtension=$true; if($d.ShowDialog($o) -eq 'OK'){[Console]::Write($d.FileName)}; $o.Close()`
: `${common}$d=New-Object System.Windows.Forms.OpenFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; if($d.ShowDialog() -eq 'OK'){[Console]::Write($d.FileName)}`; : `${common}$d=New-Object System.Windows.Forms.OpenFileDialog; $d.Filter='Zed settings (*.json)|*.json'; $d.FileName='${BUNDLE_NAME}'; if($d.ShowDialog($o) -eq 'OK'){[Console]::Write($d.FileName)}; $o.Close()`;
return runPowerShell(command); return runPowerShell(command);
} }
if (process.platform === "darwin") { if (process.platform === "darwin") {
@@ -220,7 +220,42 @@ function nativeFileDialog(mode) {
function nativeInput(prompt, title, defaultValue = "") { function nativeInput(prompt, title, defaultValue = "") {
if (process.platform === "win32") { if (process.platform === "win32") {
const command = "[Console]::OutputEncoding=[Text.Encoding]::UTF8; Add-Type -AssemblyName Microsoft.VisualBasic; [Console]::Write([Microsoft.VisualBasic.Interaction]::InputBox($env:ZED_SYNC_PROMPT,$env:ZED_SYNC_TITLE,$env:ZED_SYNC_DEFAULT))"; const command = `
[Console]::OutputEncoding=[Text.Encoding]::UTF8
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$f=New-Object System.Windows.Forms.Form
$f.Text=$env:ZED_SYNC_TITLE
$f.Size=New-Object System.Drawing.Size(620,190)
$f.StartPosition='CenterScreen'
$f.TopMost=$true
$f.ShowInTaskbar=$true
$f.FormBorderStyle='FixedDialog'
$f.MaximizeBox=$false
$f.MinimizeBox=$false
$l=New-Object System.Windows.Forms.Label
$l.Text=$env:ZED_SYNC_PROMPT
$l.AutoSize=$false
$l.Location=New-Object System.Drawing.Point(14,14)
$l.Size=New-Object System.Drawing.Size(575,42)
$t=New-Object System.Windows.Forms.TextBox
$t.Text=$env:ZED_SYNC_DEFAULT
$t.Location=New-Object System.Drawing.Point(14,62)
$t.Size=New-Object System.Drawing.Size(575,24)
$ok=New-Object System.Windows.Forms.Button
$ok.Text='OK'
$ok.DialogResult=[System.Windows.Forms.DialogResult]::OK
$ok.Location=New-Object System.Drawing.Point(427,105)
$cancel=New-Object System.Windows.Forms.Button
$cancel.Text='Cancel'
$cancel.DialogResult=[System.Windows.Forms.DialogResult]::Cancel
$cancel.Location=New-Object System.Drawing.Point(514,105)
$f.Controls.AddRange(@($l,$t,$ok,$cancel))
$f.AcceptButton=$ok
$f.CancelButton=$cancel
$f.Add_Shown({$f.Activate();$t.Focus();$t.SelectAll()})
if($f.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){[Console]::Write($t.Text)}
`;
return runPowerShell(command, { return runPowerShell(command, {
ZED_SYNC_PROMPT: prompt, ZED_SYNC_PROMPT: prompt,
ZED_SYNC_TITLE: title, ZED_SYNC_TITLE: title,
@@ -239,7 +274,7 @@ function nativeInput(prompt, title, defaultValue = "") {
function nativeMessage(message, title = "Zed Settings Sync", error = false) { function nativeMessage(message, title = "Zed Settings Sync", error = false) {
if (process.platform === "win32") { if (process.platform === "win32") {
const command = "Add-Type -AssemblyName System.Windows.Forms; [void][System.Windows.Forms.MessageBox]::Show($env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'OK',$env:ZED_SYNC_ICON)"; const command = "Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); [void][System.Windows.Forms.MessageBox]::Show($o,$env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'OK',$env:ZED_SYNC_ICON); $o.Close()";
runPowerShell(command, { runPowerShell(command, {
ZED_SYNC_MESSAGE: message, ZED_SYNC_MESSAGE: message,
ZED_SYNC_TITLE: title, ZED_SYNC_TITLE: title,
@@ -257,7 +292,7 @@ function nativeMessage(message, title = "Zed Settings Sync", error = false) {
function nativeConfirm(message, title = "Zed Settings Sync") { function nativeConfirm(message, title = "Zed Settings Sync") {
if (process.platform === "win32") { if (process.platform === "win32") {
const command = "Add-Type -AssemblyName System.Windows.Forms; [Console]::Write([System.Windows.Forms.MessageBox]::Show($env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'YesNo','Warning'))"; const command = "Add-Type -AssemblyName System.Windows.Forms; $o=New-Object System.Windows.Forms.Form; $o.TopMost=$true; $o.ShowInTaskbar=$false; $o.Opacity=0; $o.Show(); $o.Activate(); [Console]::Write([System.Windows.Forms.MessageBox]::Show($o,$env:ZED_SYNC_MESSAGE,$env:ZED_SYNC_TITLE,'YesNo','Warning')); $o.Close()";
return runPowerShell(command, { ZED_SYNC_MESSAGE: message, ZED_SYNC_TITLE: title }) === "Yes"; return runPowerShell(command, { ZED_SYNC_MESSAGE: message, ZED_SYNC_TITLE: title }) === "Yes";
} }
if (process.platform === "darwin") { if (process.platform === "darwin") {
@@ -297,6 +332,7 @@ function normalizeGistId(value) {
async function promptForGist() { async function promptForGist() {
const state = await readSyncConfiguration(); const state = await readSyncConfiguration();
console.log("Opening Gist configuration dialog...");
const entered = nativeInput( const entered = nativeInput(
"Paste the GitHub Gist ID or full Gist URL. The previous value is remembered on this machine.", "Paste the GitHub Gist ID or full Gist URL. The previous value is remembered on this machine.",
"Zed Settings Gist", "Zed Settings Gist",
@@ -313,7 +349,10 @@ async function promptForGist() {
} }
function runGh(args, input) { function runGh(args, input) {
const result = spawnSync("gh", args, { encoding: "utf8", input, windowsHide: true }); const env = { ...process.env };
delete env.GH_TOKEN;
delete env.GITHUB_TOKEN;
const result = spawnSync("gh", args, { encoding: "utf8", env, input, windowsHide: true });
if (result.error?.code === "ENOENT") { if (result.error?.code === "ENOENT") {
throw new Error("GitHub CLI (gh) is not installed. Install it, then run: gh auth login"); throw new Error("GitHub CLI (gh) is not installed. Install it, then run: gh auth login");
} }
@@ -334,6 +373,15 @@ function updateRemoteGist(gistId, contents) {
return JSON.parse(runGh(["api", "--method", "PATCH", `gists/${gistId}`, "--input", "-"], payload)); return JSON.parse(runGh(["api", "--method", "PATCH", `gists/${gistId}`, "--input", "-"], payload));
} }
function createRemoteGist(contents) {
const payload = JSON.stringify({
description: "Zed settings sync",
public: false,
files: { [BUNDLE_NAME]: { content: contents } },
});
return JSON.parse(runGh(["api", "--method", "POST", "gists", "--input", "-"], payload));
}
function shortHash(hash) { function shortHash(hash) {
return hash ? hash.slice(0, 12) : "none"; return hash ? hash.slice(0, 12) : "none";
} }
@@ -345,9 +393,21 @@ async function configureGist() {
} }
async function pushGist() { async function pushGist() {
const state = await promptForGist(); const state = await readSyncConfiguration();
if (!state) return;
const localBundle = await createBundle(); const localBundle = await createBundle();
if (!state.config.gist_id) {
console.log("No Gist is configured. Asking to create one...");
const proceed = nativeConfirm(
"No GitHub Gist is configured. Create a new Secret Gist from this machine's current Zed settings?",
);
if (!proceed) return;
const created = createRemoteGist(localBundle.contents);
state.config.gist_id = created.id;
state.config.last_synced_hash = localBundle.hash;
await saveSyncConfiguration(state.path, state.config);
nativeMessage(`Created and uploaded Secret Gist:\n${created.html_url || created.id}\n\nSHA-256: ${localBundle.hash}`);
return;
}
const remote = readRemoteGist(state.config.gist_id); const remote = readRemoteGist(state.config.gist_id);
const remoteHash = remote.parsed?.hash || ""; const remoteHash = remote.parsed?.hash || "";
const lastHash = state.config.last_synced_hash; const lastHash = state.config.last_synced_hash;
+12 -8
View File
@@ -107,8 +107,8 @@
"gist-configure" "gist-configure"
], ],
"allow_concurrent_runs": false, "allow_concurrent_runs": false,
"reveal": "never", "reveal": "no_focus",
"hide": "always", "hide": "never",
"show_summary": false, "show_summary": false,
"show_command": false, "show_command": false,
"save": "all" "save": "all"
@@ -121,8 +121,8 @@
"gist-status" "gist-status"
], ],
"allow_concurrent_runs": false, "allow_concurrent_runs": false,
"reveal": "never", "reveal": "no_focus",
"hide": "always", "hide": "never",
"show_summary": false, "show_summary": false,
"show_command": false, "show_command": false,
"save": "all" "save": "all"
@@ -136,7 +136,7 @@
], ],
"allow_concurrent_runs": false, "allow_concurrent_runs": false,
"reveal": "no_focus", "reveal": "no_focus",
"hide": "on_success", "hide": "never",
"show_summary": false, "show_summary": false,
"show_command": false, "show_command": false,
"save": "all" "save": "all"
@@ -150,15 +150,19 @@
], ],
"allow_concurrent_runs": false, "allow_concurrent_runs": false,
"reveal": "no_focus", "reveal": "no_focus",
"hide": "on_success", "hide": "never",
"show_summary": false, "show_summary": false,
"show_command": false, "show_command": false,
"save": "all" "save": "all"
}, },
{ {
"label": "Zed Settings: Authenticate GitHub...", "label": "Zed Settings: Authenticate GitHub...",
"command": "gh", "command": "powershell",
"args": ["auth", "login"], "args": [
"-NoExit",
"-Command",
"Remove-Item Env:GH_TOKEN,Env:GITHUB_TOKEN -ErrorAction SilentlyContinue; gh auth login"
],
"allow_concurrent_runs": false, "allow_concurrent_runs": false,
"reveal": "always", "reveal": "always",
"hide": "never", "hide": "never",
File diff suppressed because one or more lines are too long