mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-06 13:44:21 +08:00
refactor: rebuild desktop app with Tauri
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { stat, readFile, writeFile } from "node:fs/promises";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
|
||||
const assetSpecs = [
|
||||
["macos-arm64", ".tar.gz"],
|
||||
["macos-amd64", ".tar.gz"],
|
||||
["windows-amd64", ".zip"],
|
||||
["linux-amd64", ".tar.gz"],
|
||||
];
|
||||
|
||||
function readOptions(args) {
|
||||
const options = new Map();
|
||||
for (let index = 0; index < args.length; index += 2) {
|
||||
const name = args[index];
|
||||
const value = args[index + 1];
|
||||
if (!name?.startsWith("--") || value === undefined) {
|
||||
throw new Error(`invalid argument near ${name ?? "end of command"}`);
|
||||
}
|
||||
options.set(name.slice(2), value);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function required(options, name) {
|
||||
const value = options.get(name)?.trim();
|
||||
if (!value) throw new Error(`--${name} is required`);
|
||||
return value;
|
||||
}
|
||||
|
||||
async function sha256(path) {
|
||||
const content = await readFile(path);
|
||||
return createHash("sha256").update(content).digest("hex");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = readOptions(process.argv.slice(2));
|
||||
const version = required(options, "version").replace(/^v/, "");
|
||||
const repository = required(options, "repository");
|
||||
const assetsDir = resolve(required(options, "assets-dir"));
|
||||
const output = resolve(required(options, "output"));
|
||||
const releaseNotes = options.get("notes")?.trim() || `Cursor BYOK v${version}`;
|
||||
|
||||
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
||||
throw new Error(`invalid semantic version: ${version}`);
|
||||
}
|
||||
if (!/^[^/\s]+\/[^/\s]+$/.test(repository)) {
|
||||
throw new Error(`invalid GitHub repository: ${repository}`);
|
||||
}
|
||||
|
||||
const platforms = {};
|
||||
for (const [platform, suffix] of assetSpecs) {
|
||||
const filename = `cursor-byok-${version}-${platform}${suffix}`;
|
||||
const path = join(assetsDir, filename);
|
||||
const info = await stat(path);
|
||||
if (!info.isFile()) throw new Error(`release asset is not a file: ${path}`);
|
||||
platforms[platform] = {
|
||||
url: `https://github.com/${repository}/releases/download/v${version}/${basename(path)}`,
|
||||
size: info.size,
|
||||
checksum: `sha256:${await sha256(path)}`,
|
||||
};
|
||||
}
|
||||
|
||||
const manifest = {
|
||||
version,
|
||||
release_date: new Date().toISOString(),
|
||||
release_notes: releaseNotes,
|
||||
platforms,
|
||||
mandatory: false,
|
||||
};
|
||||
await writeFile(output, `${JSON.stringify(manifest, null, 2)}\n`);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -1,249 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type buildConfig struct {
|
||||
Info struct {
|
||||
Version string `yaml:"version"`
|
||||
} `yaml:"info"`
|
||||
}
|
||||
|
||||
type updateManifest struct {
|
||||
Version string `json:"version"`
|
||||
ReleaseDate string `json:"release_date"`
|
||||
ReleaseNotes string `json:"release_notes"`
|
||||
Platforms map[string]updateManifestAsset `json:"platforms"`
|
||||
Mandatory bool `json:"mandatory"`
|
||||
}
|
||||
|
||||
type updateManifestAsset struct {
|
||||
URL string `json:"url"`
|
||||
Size int64 `json:"size"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
type assetSpec struct {
|
||||
platform string
|
||||
suffix string
|
||||
}
|
||||
|
||||
var releaseAssets = []assetSpec{
|
||||
{platform: "macos-arm64", suffix: ".tar.gz"},
|
||||
{platform: "macos-amd64", suffix: ".tar.gz"},
|
||||
{platform: "windows-amd64", suffix: ".zip"},
|
||||
{platform: "linux-amd64", suffix: ".tar.gz"},
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
exitf("usage: go run ./scripts/release <version|notes|manifest> [flags]")
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "version":
|
||||
runVersion(os.Args[2:])
|
||||
case "notes":
|
||||
runNotes(os.Args[2:])
|
||||
case "manifest":
|
||||
runManifest(os.Args[2:])
|
||||
default:
|
||||
exitf("unknown subcommand: %s", os.Args[1])
|
||||
}
|
||||
}
|
||||
|
||||
func runVersion(args []string) {
|
||||
flags := flag.NewFlagSet("version", flag.ExitOnError)
|
||||
configPath := flags.String("config", "build/config.yml", "path to build config")
|
||||
_ = flags.Parse(args)
|
||||
|
||||
version, err := readVersion(*configPath)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
|
||||
fmt.Print(version)
|
||||
}
|
||||
|
||||
func runNotes(args []string) {
|
||||
flags := flag.NewFlagSet("notes", flag.ExitOnError)
|
||||
_ = flags.String("config", "build/config.yml", "path to build config")
|
||||
outputPath := flags.String("out", "", "output file path")
|
||||
sourcePath := flags.String("source", "", "source markdown file")
|
||||
_ = flags.Parse(args)
|
||||
|
||||
if strings.TrimSpace(*outputPath) == "" {
|
||||
exitf("notes output path is required")
|
||||
}
|
||||
if strings.TrimSpace(*sourcePath) == "" {
|
||||
exitf("notes source path is required")
|
||||
}
|
||||
|
||||
notes, err := resolveReleaseNotes(*sourcePath)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(*outputPath), 0o755); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
if err := os.WriteFile(*outputPath, []byte(notes), 0o644); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func runManifest(args []string) {
|
||||
flags := flag.NewFlagSet("manifest", flag.ExitOnError)
|
||||
configPath := flags.String("config", "build/config.yml", "path to build config")
|
||||
assetsDir := flags.String("assets-dir", "", "directory containing release assets")
|
||||
outputPath := flags.String("out", "", "manifest output file")
|
||||
repo := flags.String("repo", "", "GitHub repo in owner/repo form")
|
||||
baseName := flags.String("base-name", "cursor-byok", "release asset basename")
|
||||
notesPath := flags.String("notes", "", "release notes file")
|
||||
_ = flags.Parse(args)
|
||||
|
||||
if strings.TrimSpace(*assetsDir) == "" {
|
||||
exitf("assets-dir is required")
|
||||
}
|
||||
if strings.TrimSpace(*outputPath) == "" {
|
||||
exitf("manifest output path is required")
|
||||
}
|
||||
if strings.TrimSpace(*repo) == "" {
|
||||
exitf("repo is required")
|
||||
}
|
||||
if strings.TrimSpace(*notesPath) == "" {
|
||||
exitf("notes is required")
|
||||
}
|
||||
|
||||
version, err := readVersion(*configPath)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
|
||||
notes, err := resolveReleaseNotes(*notesPath)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
|
||||
manifest := updateManifest{
|
||||
Version: version,
|
||||
ReleaseDate: time.Now().UTC().Format(time.RFC3339),
|
||||
ReleaseNotes: notes,
|
||||
Platforms: map[string]updateManifestAsset{},
|
||||
Mandatory: false,
|
||||
}
|
||||
|
||||
for _, spec := range releaseAssets {
|
||||
filename := fmt.Sprintf("%s-%s-%s%s", *baseName, version, spec.platform, spec.suffix)
|
||||
fullpath := filepath.Join(*assetsDir, filename)
|
||||
asset, err := buildManifestAsset(fullpath, *repo, version, filename)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
manifest.Platforms[spec.platform] = asset
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(*outputPath), 0o755); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
|
||||
content, err := json.MarshalIndent(manifest, "", " ")
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
content = append(content, '\n')
|
||||
|
||||
if err := os.WriteFile(*outputPath, content, 0o644); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func readVersion(configPath string) (string, error) {
|
||||
content, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var cfg buildConfig
|
||||
if err := yaml.Unmarshal(content, &cfg); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
version := strings.TrimSpace(strings.TrimPrefix(cfg.Info.Version, "v"))
|
||||
if version == "" {
|
||||
return "", errors.New("build/config.yml info.version is empty")
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
func resolveReleaseNotes(sourcePath string) (string, error) {
|
||||
candidate := strings.TrimSpace(sourcePath)
|
||||
if candidate == "" {
|
||||
return "", errors.New("release notes source path is required")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(candidate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
notes := strings.TrimSpace(string(content))
|
||||
if notes == "" {
|
||||
return "", fmt.Errorf("release notes file %s is empty", candidate)
|
||||
}
|
||||
return notes, nil
|
||||
}
|
||||
|
||||
func buildManifestAsset(path, repo, version, filename string) (updateManifestAsset, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return updateManifestAsset{}, err
|
||||
}
|
||||
|
||||
checksum, err := sha256File(path)
|
||||
if err != nil {
|
||||
return updateManifestAsset{}, err
|
||||
}
|
||||
|
||||
return updateManifestAsset{
|
||||
URL: fmt.Sprintf("https://github.com/%s/releases/download/v%s/%s", repo, version, filename),
|
||||
Size: info.Size(),
|
||||
Checksum: "sha256:" + checksum,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func sha256File(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hasher := sha256.New()
|
||||
if _, err := io.Copy(hasher, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func exitErr(err error) {
|
||||
exitf("%v", err)
|
||||
}
|
||||
|
||||
func exitf(format string, args ...any) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, format+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
Reference in New Issue
Block a user