111 lines
4.8 KiB
Bash
111 lines
4.8 KiB
Bash
#!/usr/bin/env sh
|
|
set -eu
|
|
|
|
bundle_url='https://git.okk.cool/purp1e/zed-sync/raw/branch/master/zed-settings-sync.json'
|
|
command -v node >/dev/null 2>&1 || {
|
|
printf '%s\n' 'Node.js is required. Install it with your system package manager.' >&2
|
|
exit 1
|
|
}
|
|
command -v curl >/dev/null 2>&1 || {
|
|
printf '%s\n' 'curl is required. Install it with your system package manager.' >&2
|
|
exit 1
|
|
}
|
|
|
|
temporary_dir=$(mktemp -d)
|
|
trap 'rm -rf "$temporary_dir"' EXIT INT TERM
|
|
bundle_path="$temporary_dir/zed-settings-sync.json"
|
|
if [ -n "${ZED_SETTINGS_BUNDLE_PATH:-}" ]; then
|
|
bundle_path=$ZED_SETTINGS_BUNDLE_PATH
|
|
else
|
|
printf '%s\n' 'Downloading the public Zed configuration bundle...'
|
|
curl -fsSL "$bundle_url" -o "$bundle_path"
|
|
fi
|
|
|
|
node - "$bundle_path" <<'NODE'
|
|
const fs = require("node:fs");
|
|
const os = require("node:os");
|
|
const path = require("node:path");
|
|
|
|
const bundlePath = process.argv[2];
|
|
const source = fs.readFileSync(bundlePath, "utf8");
|
|
const bundle = JSON.parse(source);
|
|
const fixed = new Set([
|
|
"settings.json",
|
|
"keymap.json",
|
|
"tasks.json",
|
|
"debug.json",
|
|
"scripts/toggle-file-scan-exclusions.mjs",
|
|
"scripts/file-exclusions.json",
|
|
"scripts/zed-settings-sync.mjs",
|
|
]);
|
|
function allowed(relativePath) {
|
|
const parts = relativePath.split("/");
|
|
return typeof relativePath === "string"
|
|
&& !relativePath.startsWith("/")
|
|
&& !relativePath.includes("\\")
|
|
&& !parts.some((part) => !part || part === "." || part === "..")
|
|
&& (fixed.has(relativePath) || relativePath.startsWith("snippets/") || relativePath.startsWith("themes/"));
|
|
}
|
|
for (const required of ["settings.json", "keymap.json", "tasks.json"]) {
|
|
if (typeof bundle[required] !== "string") throw new Error(`The bundle is missing ${required}.`);
|
|
}
|
|
for (const [relativePath, contents] of Object.entries(bundle)) {
|
|
if (!allowed(relativePath) || typeof contents !== "string") throw new Error(`Invalid bundle file: ${relativePath}`);
|
|
}
|
|
|
|
const target = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "zed");
|
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
const backup = path.join(target, "backups", `bootstrap-${stamp}`);
|
|
fs.mkdirSync(backup, { recursive: true });
|
|
for (const relativePath of Object.keys(bundle)) {
|
|
const existing = path.join(target, ...relativePath.split("/"));
|
|
if (!fs.existsSync(existing)) continue;
|
|
const backupPath = path.join(backup, ...relativePath.split("/"));
|
|
fs.mkdirSync(path.dirname(backupPath), { recursive: true });
|
|
fs.copyFileSync(existing, backupPath);
|
|
}
|
|
|
|
const toggleScript = path.join(target, "scripts", "toggle-file-scan-exclusions.mjs").replaceAll("\\", "/");
|
|
const syncScript = path.join(target, "scripts", "zed-settings-sync.mjs").replaceAll("\\", "/");
|
|
for (const [relativePath, sourceContents] of Object.entries(bundle)) {
|
|
const destination = path.join(target, ...relativePath.split("/"));
|
|
const contents = relativePath === "tasks.json"
|
|
? sourceContents.replaceAll("__TOGGLE_SCRIPT__", toggleScript).replaceAll("__SYNC_SCRIPT__", syncScript)
|
|
: sourceContents;
|
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
const temporary = path.join(path.dirname(destination), `.${path.basename(destination)}.${process.pid}.tmp`);
|
|
fs.writeFileSync(temporary, contents, "utf8");
|
|
fs.renameSync(temporary, destination);
|
|
}
|
|
|
|
const statePath = path.join(target, "scripts", "settings-sync.json");
|
|
const previousState = fs.existsSync(statePath) ? JSON.parse(fs.readFileSync(statePath, "utf8")) : {};
|
|
const state = {
|
|
gist_id: typeof previousState.gist_id === "string" ? previousState.gist_id : "",
|
|
last_synced_hash: typeof previousState.last_synced_hash === "string" ? previousState.last_synced_hash : "",
|
|
auto_sync: previousState.auto_sync === true,
|
|
interval_minutes: Number.isInteger(previousState.interval_minutes) && previousState.interval_minutes >= 5 ? previousState.interval_minutes : 15,
|
|
last_run_at: typeof previousState.last_run_at === "string" ? previousState.last_run_at : "",
|
|
last_result: typeof previousState.last_result === "string" ? previousState.last_result : "",
|
|
};
|
|
fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
console.log(`Installed Zed settings to ${target}`);
|
|
console.log(`Previous settings were backed up to ${backup}`);
|
|
console.log("GitHub and Gist setup are optional and did not block installation.");
|
|
NODE
|
|
|
|
target="${XDG_CONFIG_HOME:-$HOME/.config}/zed"
|
|
if [ "${ZED_SYNC_SKIP_SETUP:-0}" != "1" ] && [ -r /dev/tty ]; then
|
|
printf 'Run the GitHub sync setup now? [Y/n] ' >/dev/tty
|
|
IFS= read -r answer </dev/tty || answer=n
|
|
case "$answer" in
|
|
""|y|Y|yes|YES)
|
|
if ! node "$target/scripts/zed-settings-sync.mjs" setup </dev/tty >/dev/tty; then
|
|
printf '%s\n' 'Sync setup did not complete. Local installation is still valid; run the setup Task from Zed later.' >&2
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
printf '%s\n' 'Restart or reload Zed to use the installed configuration.'
|