release: glchat 0.1.1
Версия — в одном месте: файл VERSION в корне, из него её берут Makefile, deploy/install.sh (IMAGE_TAG) и desktop-обёртка (tauri.conf.json, Cargo.toml). Публикация обновлений (scripts/desktop-release.sh): - манифесты прошлых релизов удаляются: оставшийся <старая версия>.json перекрывал новый релиз — обёртка спрашивает манифест своей версии и видела «обновлений нет»; - mktemp-каталог раскладки получает 755, каталоги на инстансе нормализуются: rsync -a переносил режим 0700 на корень каталога обновлений, и приложение не могло читать манифесты (404 при живых файлах); - --remote работает без TTY и без sudo, если каталог обновлений записываем; опции ssh — в GLCHAT_SSH_OPTS; - предупреждение при публикации версии старше уже опубликованной. Сервер: HEAD /updates/... (chi не выводит его из GET) — размер и тип артефакта без скачивания; тест TestUpdatesAnswersHead. Приёмка на живом канале: 0.1.0 → 0.1.1 на стенде (скачивание, проверка подписи, установка, перезапуск), /Applications/glchat.app = 0.1.1.
This commit is contained in:
@@ -291,9 +291,13 @@ func (s *Server) registerRoutes(router chi.Router) {
|
||||
// Манифест автообновления desktop-обёртки (docs/client-tauri.md §4): обычная
|
||||
// статика без сессии — обёртка проверяет обновления до входа в аккаунт.
|
||||
// Обработчик берётся через поле, а не значением метода: каталог обновлений
|
||||
// задаётся конфигом и в тестах подменяется до первого запроса.
|
||||
// задаётся конфигом и в тестах подменяется до первого запроса. HEAD
|
||||
// регистрируется отдельно (chi не выводит его из GET): по нему удобно
|
||||
// проверять размер и тип артефакта, не скачивая его.
|
||||
router.Get("/updates/*", func(w http.ResponseWriter, r *http.Request) { s.updates.serve(w, r) })
|
||||
router.Get("/updates", func(w http.ResponseWriter, r *http.Request) { s.updates.serve(w, r) })
|
||||
router.Head("/updates/*", func(w http.ResponseWriter, r *http.Request) { s.updates.serve(w, r) })
|
||||
router.Head("/updates", func(w http.ResponseWriter, r *http.Request) { s.updates.serve(w, r) })
|
||||
|
||||
router.NotFound(s.handleFallback)
|
||||
router.MethodNotAllowed(func(w http.ResponseWriter, _ *http.Request) {
|
||||
|
||||
@@ -108,6 +108,27 @@ func TestUpdatesServesArtifactAndSignature(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdatesAnswersHead: HEAD отдаёт заголовки файла без тела — по нему
|
||||
// удобно проверять размер и тип артефакта, не скачивая его (chi не выводит
|
||||
// HEAD из GET, маршрут регистрируется отдельно).
|
||||
func TestUpdatesAnswersHead(t *testing.T) {
|
||||
srv, _ := updatesFixture(t)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
srv.Handler().ServeHTTP(rec,
|
||||
httptest.NewRequestWithContext(context.Background(), http.MethodHead,
|
||||
"/updates/darwin/aarch64/glchat_0.2.0_aarch64.app.tar.gz", nil))
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", rec.Code)
|
||||
}
|
||||
if rec.Body.Len() != 0 {
|
||||
t.Errorf("тело ответа на HEAD = %q, want пусто", rec.Body.String())
|
||||
}
|
||||
if rec.Header().Get("Content-Length") == "" {
|
||||
t.Error("HEAD без Content-Length: размер артефакта не узнать")
|
||||
}
|
||||
}
|
||||
|
||||
// TestUpdatesMissingManifestIsNotFound: неизвестная версия — честный 404, а не
|
||||
// SPA-заглушка: иначе обёртка получила бы HTML вместо манифеста.
|
||||
func TestUpdatesMissingManifestIsNotFound(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user