From a96ffb580140d17e779bc20ba02e3aac315af053 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Wed, 17 Jun 2026 17:13:14 +0800 Subject: [PATCH] chore: split build.sh and update-and-build-all.sh - build.sh: deterministic local-only build, fail-fast, no git pull - update-and-build-all.sh: dev helper, pulls all repos, builds official plugins, then builds desktop - Docs: added Build Scripts section explaining the difference --- docs/PLUGIN_RUNTIME.md | 24 +++ frontend/wailsjs/go/models.ts | 304 ++++++++++---------------------- scripts/build.sh | 216 +++++++---------------- scripts/update-and-build-all.sh | 80 +++++++++ 4 files changed, 256 insertions(+), 368 deletions(-) create mode 100755 scripts/update-and-build-all.sh diff --git a/docs/PLUGIN_RUNTIME.md b/docs/PLUGIN_RUNTIME.md index b2a69b8..23872f8 100644 --- a/docs/PLUGIN_RUNTIME.md +++ b/docs/PLUGIN_RUNTIME.md @@ -519,3 +519,27 @@ WorkspaceTree в sidebar: - Создание case/folder - Выбор текущей ноды - Индикатор статуса (active/archived/sleeping) + +--- + +## Build Scripts + +В `verstak-desktop/scripts/` есть два скрипта: + +### `build.sh` — локальная детерминированная сборка + +- Собирает **только** `verstak-desktop` (core platform). +- Не трогает другие репозитории. +- **Fail-fast**: любая ошибка (go vet, go test, frontend build, wails build) прерывает сборку. +- Проверяет: deps → frontend build → go mod download → go vet → go build → go test → wails build + plugin copy. +- Используется в CI и для повседневной работы над core. + +### `update-and-build-all.sh` — dev helper для полной пересборки связки + +- **Не для CI.** Только для разработки, когда нужно быстро собрать всё вместе. +- Шаги: + 1. `git pull --ff-only` во всех 6 репозиториях + 2. Сборка official plugins (frontend npm build + backend go build для каждого плагина) + 3. Копирование собранных плагинов в `verstak-desktop/plugins/` + 4. Запуск `build.sh` для сборки desktop +- Ошибки pull и сборки плагинов не прерывают скрипт (best-effort), но ошибка build.sh прерывает (fail-fast). diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index b3511b8..8cd0925 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,13 +1,92 @@ export namespace api { + export class FlatSidebarItem { + pluginId: string; + id: string; + title: string; + icon?: string; + view: string; + position?: number; + + static createFrom(source: any = {}) { + return new FlatSidebarItem(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.pluginId = source["pluginId"]; + this.id = source["id"]; + this.title = source["title"]; + this.icon = source["icon"]; + this.view = source["view"]; + this.position = source["position"]; + } + } + export class FlatSettingsPanel { + pluginId: string; + id: string; + title: string; + icon?: string; + component: string; + + static createFrom(source: any = {}) { + return new FlatSettingsPanel(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.pluginId = source["pluginId"]; + this.id = source["id"]; + this.title = source["title"]; + this.icon = source["icon"]; + this.component = source["component"]; + } + } + export class FlatCommand { + pluginId: string; + id: string; + title: string; + icon?: string; + handler?: string; + + static createFrom(source: any = {}) { + return new FlatCommand(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.pluginId = source["pluginId"]; + this.id = source["id"]; + this.title = source["title"]; + this.icon = source["icon"]; + this.handler = source["handler"]; + } + } + export class FlatView { + pluginId: string; + id: string; + title: string; + icon?: string; + component: string; + + static createFrom(source: any = {}) { + return new FlatView(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.pluginId = source["pluginId"]; + this.id = source["id"]; + this.title = source["title"]; + this.icon = source["icon"]; + this.component = source["component"]; + } + } export class ContributionSummary { - views: contribution.ContributionView[]; - commands: contribution.ContributionCommand[]; - settingsPanels: contribution.ContributionSettingsPanel[]; - sidebarItems: contribution.ContributionSidebarItem[]; - fileActions: contribution.ContributionAction[]; - noteActions: contribution.ContributionAction[]; - searchProviders: contribution.ContributionSearchProvider[]; + views: FlatView[]; + commands: FlatCommand[]; + settingsPanels: FlatSettingsPanel[]; + sidebarItems: FlatSidebarItem[]; static createFrom(source: any = {}) { return new ContributionSummary(source); @@ -15,13 +94,10 @@ export namespace api { constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); - this.views = this.convertValues(source["views"], contribution.ContributionView); - this.commands = this.convertValues(source["commands"], contribution.ContributionCommand); - this.settingsPanels = this.convertValues(source["settingsPanels"], contribution.ContributionSettingsPanel); - this.sidebarItems = this.convertValues(source["sidebarItems"], contribution.ContributionSidebarItem); - this.fileActions = this.convertValues(source["fileActions"], contribution.ContributionAction); - this.noteActions = this.convertValues(source["noteActions"], contribution.ContributionAction); - this.searchProviders = this.convertValues(source["searchProviders"], contribution.ContributionSearchProvider); + this.views = this.convertValues(source["views"], FlatView); + this.commands = this.convertValues(source["commands"], FlatCommand); + this.settingsPanels = this.convertValues(source["settingsPanels"], FlatSettingsPanel); + this.sidebarItems = this.convertValues(source["sidebarItems"], FlatSidebarItem); } convertValues(a: any, classs: any, asMap: boolean = false): any { @@ -42,6 +118,9 @@ export namespace api { return a; } } + + + } @@ -68,203 +147,6 @@ export namespace capability { } -export namespace contribution { - - export class ContributionAction { - pluginId: string; - item: plugin.ContributionAction; - - static createFrom(source: any = {}) { - return new ContributionAction(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionAction); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ContributionCommand { - pluginId: string; - item: plugin.ContributionCommand; - - static createFrom(source: any = {}) { - return new ContributionCommand(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionCommand); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ContributionSearchProvider { - pluginId: string; - item: plugin.ContributionSearchProvider; - - static createFrom(source: any = {}) { - return new ContributionSearchProvider(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionSearchProvider); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ContributionSettingsPanel { - pluginId: string; - item: plugin.ContributionSettingsPanel; - - static createFrom(source: any = {}) { - return new ContributionSettingsPanel(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionSettingsPanel); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ContributionSidebarItem { - pluginId: string; - item: plugin.ContributionSidebarItem; - - static createFrom(source: any = {}) { - return new ContributionSidebarItem(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionSidebarItem); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class ContributionView { - pluginId: string; - item: plugin.ContributionView; - - static createFrom(source: any = {}) { - return new ContributionView(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.pluginId = source["pluginId"]; - this.item = this.convertValues(source["item"], plugin.ContributionView); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - export namespace permissions { export class Entry { diff --git a/scripts/build.sh b/scripts/build.sh index 2ad691d..bcebdd8 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,166 +2,75 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -VERSTAK_ROOT="$(cd "$ROOT/.." && pwd)" -FAILED=0 -GLOBAL_ERRORS="" - -report() { - if [ "$2" -eq 0 ]; then - echo " ✅ $1" - else - echo " ❌ $1" - FAILED=1 - fi -} - -# ── Global update: pull all repos, build official plugins ───────────────── -global_update() { - local repos=("verstak-desktop" "verstak-sdk" "verstak-official-plugins" "verstak-sync-server" "verstak-browser-extension" "verstak-docs") - local errors="" - - echo "" - echo "=== global update: pull all repos ===" - for repo in "${repos[@]}"; do - local repo_path="$VERSTAK_ROOT/$repo" - if [ ! -d "$repo_path" ]; then - errors="$errors ⚠️ $repo: directory not found at $repo_path\n" - continue - fi - echo "[$repo]" - (cd "$repo_path" && git pull --ff-only 2>&1) && echo " ✅ pulled" || { - errors="$errors ❌ $repo: git pull failed\n" - echo " ❌ git pull failed" - } - done - - # Build official plugins - local official_plugins="$VERSTAK_ROOT/verstak-official-plugins" - if [ -d "$official_plugins" ]; then - echo "" - echo "=== build official plugins ===" - (cd "$official_plugins" && if [ -f "package.json" ] && [ -d "plugins" ]; then - if [ ! -d "node_modules" ]; then - echo "[official-plugins] installing npm deps..." - npm install --no-audit --no-fund 2>&1 || true - fi - # Build each plugin that has a frontend - for plugin_dir in plugins/*/; do - local plugin_name=$(basename "$plugin_dir") - local fe_dir="$plugin_dir/frontend" - if [ -d "$fe_dir" ] && [ -f "$fe_dir/package.json" ]; then - echo "[$plugin_name] building frontend..." - (cd "$fe_dir" && [ ! -d "node_modules" ] && npm install --no-audit --no-fund 2>&1 || true; npm run build 2>&1 || true) - fi - # Build backend if main.go exists - local backend_dir="$plugin_dir/backend" - if [ -f "$backend_dir/main.go" ]; then - echo "[$plugin_name] building backend..." - (cd "$backend_dir" && go build -o "$(basename "$backend_dir")" . 2>&1 || true) - fi - done - echo " ✅ official plugins built" - else - echo " ℹ️ no plugins to build" - fi) - fi - - # Copy official plugins to desktop - local dest="$ROOT/plugins" - if [ -d "$official_plugins/plugins" ]; then - echo "" - echo "=== install official plugins to desktop ===" - rm -rf "$dest" - mkdir -p "$dest" - cp -r "$official_plugins/plugins/"* "$dest/" 2>/dev/null || true - echo " ✅ plugins installed to $dest" - fi - - GLOBAL_ERRORS="$errors" -} - -ensure_npm_deps() { - local dir="$1" - if [ ! -f "$dir/package.json" ]; then - return 1 - fi - if [ ! -d "$dir/node_modules" ]; then - echo " 📦 node_modules missing — installing..." - if [ -f "$dir/package-lock.json" ]; then - (cd "$dir" && npm ci --no-audit --no-fund) - else - (cd "$dir" && npm install --no-audit --no-fund) - fi - report "npm install in $(basename "$dir")" $? - fi - return 0 -} echo "=== verstak-desktop build ===" echo "" -# ── Global update (best-effort — errors collected, not fatal) ── -global_update - # ── Dependency checks ── echo "[deps]" if ! command -v go &>/dev/null; then echo " ❌ go: not found. Install Go 1.24+ from https://go.dev/dl/" - FAILED=1 -else - echo " ✅ go $(go version | grep -oP 'go\S+')" -fi -if ! command -v node &>/dev/null; then - echo " ❌ node: not found. Install Node.js 20+" - FAILED=1 -else - echo " ✅ node $(node --version)" -fi -if ! command -v npm &>/dev/null; then - echo " ❌ npm: not found" - FAILED=1 -fi - -if [ "$FAILED" -ne 0 ]; then - echo "" - echo "❌ build failed — missing core dependencies" exit 1 fi +echo " ✅ go $(go version | grep -oP 'go\S+')" + +if ! command -v node &>/dev/null; then + echo " ❌ node: not found. Install Node.js 20+" + exit 1 +fi +echo " ✅ node $(node --version)" + +if ! command -v npm &>/dev/null; then + echo " ❌ npm: not found" + exit 1 +fi +echo " ✅ npm $(npm --version)" # ── Frontend (build first — Go //go:embed needs frontend/dist/) ── +echo "" echo "[frontend]" if [ -f "$ROOT/frontend/package.json" ]; then - ensure_npm_deps "$ROOT/frontend" + if [ ! -d "$ROOT/frontend/node_modules" ]; then + echo " 📦 node_modules missing — installing..." + if [ -f "$ROOT/frontend/package-lock.json" ]; then + (cd "$ROOT/frontend" && npm ci --no-audit --no-fund) + else + (cd "$ROOT/frontend" && npm install --no-audit --no-fund) + fi + fi (cd "$ROOT/frontend" && npm run build) - report "frontend build" $? + echo " ✅ frontend build" else echo " ℹ️ frontend/package.json not found — skipping" fi # ── Go backend ── +echo "" echo "[backend]" -# Ensure Go module deps are downloaded +echo " 📦 go mod download..." (cd "$ROOT" && go mod download) -report "go mod download" $? +echo " ✅ go mod download" +echo " 🔍 go vet..." (cd "$ROOT" && go vet ./...) -report "go vet" $? +echo " ✅ go vet" +echo " 🔨 go build..." (cd "$ROOT" && go build ./...) -report "go build" $? +echo " ✅ go build" -# Go test (best-effort — some packages may have no tests) -(cd "$ROOT" && go test -count=1 ./... 2>&1 || true) -report "go test" $? +echo " 🧪 go test..." +(cd "$ROOT" && go test -count=1 ./...) +echo " ✅ go test" # ── Wails ── +echo "" echo "[wails]" WAILS="" if command -v wails &>/dev/null; then WAILS="wails" else - # Check GO bin paths GOBIN="$(go env GOBIN 2>/dev/null)" GOPATH="$(go env GOPATH 2>/dev/null)" if [ -n "$GOBIN" ] && [ -f "$GOBIN/wails" ]; then @@ -172,7 +81,6 @@ else if [ -z "$WAILS" ]; then echo " 📦 wails not found — installing..." go install github.com/wailsapp/wails/v2/cmd/wails@latest - report "wails install" $? if [ -n "$GOBIN" ] && [ -f "$GOBIN/wails" ]; then WAILS="$GOBIN/wails" elif [ -f "$GOPATH/bin/wails" ]; then @@ -180,6 +88,7 @@ else fi fi fi + WAILS_BINARY="verstak-desktop" WAILS_TAGS="" if command -v pkg-config &>/dev/null; then @@ -194,37 +103,30 @@ if command -v pkg-config &>/dev/null; then else echo " ⚠️ pkg-config not found" fi -if [ -n "$WAILS" ]; then - echo " 🔨 wails build..." - (cd "$ROOT" && "$WAILS" build -clean $WAILS_TAGS) - report "wails build" $? - # Copy plugins/ to build/bin/ so the binary can find them at runtime - if [ -d "$ROOT/plugins" ]; then - mkdir -p "$ROOT/build/bin/plugins" - cp -r "$ROOT/plugins/"* "$ROOT/build/bin/plugins/" 2>/dev/null || true - echo " 📦 plugins copied to build/bin/plugins/" - fi - # Show where the binary ended up - if [ -f "$ROOT/build/bin/$WAILS_BINARY" ]; then - echo " 📦 binary: $ROOT/build/bin/$WAILS_BINARY" - fi - if [ -f "$ROOT/$WAILS_BINARY" ]; then - echo " 📦 binary: $ROOT/$WAILS_BINARY" - fi -else - echo " ❌ wails: could not install" - FAILED=1 + +if [ -z "$WAILS" ]; then + echo " ❌ wails: could not find or install" + exit 1 +fi + +echo " 🔨 wails build..." +(cd "$ROOT" && "$WAILS" build -clean $WAILS_TAGS) +echo " ✅ wails build" + +# Copy plugins/ to build/bin/ so the binary can find them at runtime +if [ -d "$ROOT/plugins" ]; then + mkdir -p "$ROOT/build/bin/plugins" + cp -r "$ROOT/plugins/"* "$ROOT/build/bin/plugins/" 2>/dev/null || true + echo " 📦 plugins copied to build/bin/plugins/" +fi + +# Show where the binary ended up +if [ -f "$ROOT/build/bin/$WAILS_BINARY" ]; then + echo " 📦 binary: $ROOT/build/bin/$WAILS_BINARY" +fi +if [ -f "$ROOT/$WAILS_BINARY" ]; then + echo " 📦 binary: $ROOT/$WAILS_BINARY" fi echo "" -if [ "$FAILED" -eq 0 ] && [ -z "$GLOBAL_ERRORS" ]; then - echo "✅ build passed" -else - echo "❌ build completed with issues" - if [ -n "$GLOBAL_ERRORS" ]; then - echo "" - echo "Global update errors:" - echo -e "$GLOBAL_ERRORS" - fi -fi -exit "$FAILED" +echo "✅ build passed" diff --git a/scripts/update-and-build-all.sh b/scripts/update-and-build-all.sh new file mode 100755 index 0000000..42f099a --- /dev/null +++ b/scripts/update-and-build-all.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# update-and-build-all.sh — dev helper: pull all repos, build official plugins, build desktop +# This is NOT part of CI. For local deterministic build, use scripts/build.sh in each repo. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +VERSTAK_ROOT="$(cd "$ROOT/.." && pwd)" + +echo "=== update-and-build-all ===" +echo "" + +# ── 1. Pull all repos ── +echo "=== pull all repos ===" +repos=("verstak-desktop" "verstak-sdk" "verstak-official-plugins" "verstak-sync-server" "verstak-browser-extension" "verstak-docs") +for repo in "${repos[@]}"; do + repo_path="$VERSTAK_ROOT/$repo" + if [ ! -d "$repo_path" ]; then + echo " ⚠️ $repo: directory not found at $repo_path — skipping" + continue + fi + echo "[$repo]" + (cd "$repo_path" && git pull --ff-only 2>&1) && echo " ✅ pulled" || echo " ❌ git pull failed" +done + +# ── 2. Build official plugins ── +echo "" +echo "=== build official plugins ===" +OFFICIAL="$VERSTAK_ROOT/verstak-official-plugins" +if [ ! -d "$OFFICIAL" ]; then + echo " ⚠️ verstak-official-plugins not found — skipping" +else + # npm deps + if [ ! -d "$OFFICIAL/node_modules" ] && [ -f "$OFFICIAL/package.json" ]; then + echo " 📦 installing npm deps..." + (cd "$OFFICIAL" && npm install --no-audit --no-fund) + fi + # Build each plugin that has a frontend or backend + for plugin_dir in "$OFFICIAL"/plugins/*/; do + [ -d "$plugin_dir" ] || continue + plugin_name="$(basename "$plugin_dir")" + + # Frontend build + fe_dir="$plugin_dir/frontend" + if [ -d "$fe_dir" ] && [ -f "$fe_dir/package.json" ]; then + echo "[$plugin_name] building frontend..." + if [ ! -d "$fe_dir/node_modules" ]; then + (cd "$fe_dir" && npm install --no-audit --no-fund) + fi + (cd "$fe_dir" && npm run build) + echo " ✅ $plugin_name frontend" + fi + + # Backend build + backend_dir="$plugin_dir/backend" + if [ -f "$backend_dir/main.go" ]; then + echo "[$plugin_name] building backend..." + (cd "$backend_dir" && go build -o "$(basename "$backend_dir")" .) + echo " ✅ $plugin_name backend" + fi + done + echo " ✅ official plugins built" +fi + +# ── 3. Copy plugins to desktop ── +echo "" +echo "=== install plugins to desktop ===" +DEST="$ROOT/plugins" +rm -rf "$DEST" +mkdir -p "$DEST" +if [ -d "$OFFICIAL/plugins" ]; then + cp -r "$OFFICIAL/plugins/"* "$DEST/" 2>/dev/null + echo " ✅ plugins copied to $DEST" +else + echo " ℹ️ no plugins to copy" +fi + +# ── 4. Build desktop ── +echo "" +echo "=== build desktop ===" +exec "$ROOT/scripts/build.sh"