Add nightly release channel for zed2 (#3355)

Mikayla Maki created

Release Notes:

- N/A

Change summary

.github/actions/check_formatting/action.yml            |  15 
.github/actions/run_tests/action.yml                   |  34 +
.github/workflows/ci.yml                               |  37 
.github/workflows/release_nightly.yml                  |  98 +++
Cargo.lock                                             |  25 
Cargo.toml                                             |   1 
crates/auto_update/src/auto_update.rs                  |  39 
crates/auto_update2/Cargo.toml                         |  29 
crates/auto_update2/src/auto_update.rs                 | 392 ++++++++++++
crates/auto_update2/src/update_notification.rs         |  87 ++
crates/client/src/client.rs                            |  24 
crates/client/src/telemetry.rs                         |   2 
crates/client2/src/client2.rs                          |  22 
crates/client2/src/telemetry.rs                        |   2 
crates/gpui2/src/app.rs                                |   4 
crates/util/src/channel.rs                             |  24 
crates/workspace2/src/notifications.rs                 |   5 
crates/zed/Cargo.toml                                  |  10 
crates/zed/contents/nightly/embedded.provisionprofile  |   0 
crates/zed/src/only_instance.rs                        |   2 
crates/zed2/Cargo.toml                                 |  12 
crates/zed2/build.rs                                   |  12 
crates/zed2/contents/nightly/embedded.provisionprofile |   0 
crates/zed2/src/main.rs                                |   8 
crates/zed2/src/only_instance.rs                       |   2 
crates/zed2/src/zed2.rs                                |  13 
script/bump-nightly                                    |  11 
script/bump-zed-minor-versions                         |   5 
script/bump-zed-patch-version                          |   5 
script/bundle                                          |  31 
script/deploy                                          |   7 
script/deploy-migration                                |   9 
script/upload-nightly                                  |  37 +
script/what-is-deployed                                |   7 
34 files changed, 914 insertions(+), 97 deletions(-)

Detailed changes

.github/actions/check_formatting/action.yml 🔗

@@ -0,0 +1,15 @@
+name: 'Check formatting'
+description: 'Checks code formatting use cargo fmt'
+
+runs:
+  using: "composite"
+  steps:
+    - name: Install Rust
+      shell: bash -euxo pipefail {0}
+      run: |
+        rustup set profile minimal
+        rustup update stable
+
+    - name: cargo fmt
+      shell: bash -euxo pipefail {0}
+      run: cargo fmt --all -- --check

.github/actions/run_tests/action.yml 🔗

@@ -0,0 +1,34 @@
+name: "Run tests"
+description: "Runs the tests"
+
+runs:
+  using: "composite"
+  steps:
+    - name: Install Rust
+      shell: bash -euxo pipefail {0}
+      run: |
+        rustup set profile minimal
+        rustup update stable
+        rustup target add wasm32-wasi
+        cargo install cargo-nextest
+
+    - name: Install Node
+      uses: actions/setup-node@v3
+      with:
+        node-version: "18"
+
+    - name: Limit target directory size
+      shell: bash -euxo pipefail {0}
+      run: script/clear-target-dir-if-larger-than 70
+
+    - name: Run check
+      env:
+        RUSTFLAGS: -D warnings
+      shell: bash -euxo pipefail {0}
+      run: cargo check --tests --workspace
+
+    - name: Run tests
+      env:
+        RUSTFLAGS: -D warnings
+      shell: bash -euxo pipefail {0}
+      run: cargo nextest run --workspace --no-fail-fast

.github/workflows/ci.yml 🔗

@@ -23,19 +23,14 @@ jobs:
       - self-hosted
       - test
     steps:
-      - name: Install Rust
-        run: |
-          rustup set profile minimal
-          rustup update stable
-
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
           clean: false
           submodules: "recursive"
 
-      - name: cargo fmt
-        run: cargo fmt --all -- --check
+      - name: Run rustfmt
+        uses: ./.github/actions/check_formatting
 
   tests:
     name: Run tests
@@ -43,35 +38,15 @@ jobs:
       - self-hosted
       - test
     needs: rustfmt
-    env:
-      RUSTFLAGS: -D warnings
     steps:
-      - name: Install Rust
-        run: |
-          rustup set profile minimal
-          rustup update stable
-          rustup target add wasm32-wasi
-          cargo install cargo-nextest
-
-      - name: Install Node
-        uses: actions/setup-node@v3
-        with:
-          node-version: "18"
-
       - name: Checkout repo
         uses: actions/checkout@v3
         with:
           clean: false
           submodules: "recursive"
 
-      - name: Limit target directory size
-        run: script/clear-target-dir-if-larger-than 70
-
-      - name: Run check
-        run: cargo check --workspace
-
       - name: Run tests
-        run: cargo nextest run --workspace --no-fail-fast
+        uses: ./.github/actions/run_tests
 
       - name: Build collab
         run: cargo build -p collab
@@ -130,6 +105,8 @@ jobs:
               expected_tag_name="v${version}";;
             preview)
               expected_tag_name="v${version}-pre";;
+            nightly)
+              expected_tag_name="v${version}-nightly";;
             *)
               echo "can't publish a release on channel ${channel}"
               exit 1;;
@@ -154,7 +131,9 @@ jobs:
 
       - uses: softprops/action-gh-release@v1
         name: Upload app bundle to release
-        if: ${{ env.RELEASE_CHANNEL }}
+        # TODO kb seems that zed.dev relies on GitHub releases for release version tracking.
+        # Find alternatives for `nightly` or just go on with more releases?
+        if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
         with:
           draft: true
           prerelease: ${{ env.RELEASE_CHANNEL == 'preview' }}

.github/workflows/release_nightly.yml 🔗

@@ -0,0 +1,98 @@
+name: Release Nightly
+
+on:
+  schedule:
+    # Fire every night at 1:00am
+    - cron: "0 1 * * *"
+  push:
+    tags:
+      - "nightly"
+
+env:
+  CARGO_TERM_COLOR: always
+  CARGO_INCREMENTAL: 0
+  RUST_BACKTRACE: 1
+
+jobs:
+  rustfmt:
+    name: Check formatting
+    runs-on:
+      - self-hosted
+      - test
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          clean: false
+          submodules: "recursive"
+
+      - name: Run rustfmt
+        uses: ./.github/actions/check_formatting
+
+  tests:
+    name: Run tests
+    runs-on:
+      - self-hosted
+      - test
+    needs: rustfmt
+    steps:
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          clean: false
+          submodules: "recursive"
+
+      - name: Run tests
+        uses: ./.github/actions/run_tests
+
+  bundle:
+    name: Bundle app
+    runs-on:
+      - self-hosted
+      - bundle
+    needs: tests
+    env:
+      MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
+      MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
+      APPLE_NOTARIZATION_USERNAME: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
+      APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
+      DIGITALOCEAN_SPACES_ACCESS_KEY: ${{ secrets.DIGITALOCEAN_SPACES_ACCESS_KEY }}
+      DIGITALOCEAN_SPACES_SECRET_KEY: ${{ secrets.DIGITALOCEAN_SPACES_SECRET_KEY }}
+    steps:
+      - name: Install Rust
+        run: |
+          rustup set profile minimal
+          rustup update stable
+          rustup target add aarch64-apple-darwin
+          rustup target add x86_64-apple-darwin
+          rustup target add wasm32-wasi
+
+      - name: Install Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: "18"
+
+      - name: Checkout repo
+        uses: actions/checkout@v3
+        with:
+          clean: false
+          submodules: "recursive"
+
+      - name: Limit target directory size
+        run: script/clear-target-dir-if-larger-than 70
+
+      - name: Set release channel to nightly
+        run: |
+          set -eu
+          version=$(git rev-parse --short HEAD)
+          echo "Publishing version: ${version} on release channel nightly"
+          echo "nightly" > crates/zed/RELEASE_CHANNEL
+
+      - name: Generate license file
+        run: script/generate-licenses
+
+      - name: Create app bundle
+        run: script/bundle -2
+
+      - name: Upload Zed Nightly
+        run: script/upload-nightly

Cargo.lock 🔗

@@ -724,6 +724,30 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "auto_update2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client2",
+ "db2",
+ "gpui2",
+ "isahc",
+ "lazy_static",
+ "log",
+ "menu2",
+ "project2",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings2",
+ "smol",
+ "tempdir",
+ "theme2",
+ "util",
+ "workspace2",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -11571,6 +11595,7 @@ dependencies = [
  "async-recursion 0.3.2",
  "async-tar",
  "async-trait",
+ "auto_update2",
  "backtrace",
  "call2",
  "chrono",

Cargo.toml 🔗

@@ -6,6 +6,7 @@ members = [
     "crates/audio",
     "crates/audio2",
     "crates/auto_update",
+    "crates/auto_update2",
     "crates/breadcrumbs",
     "crates/call",
     "crates/call2",

crates/auto_update/src/auto_update.rs 🔗

@@ -118,14 +118,18 @@ fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
         let auto_updater = auto_updater.read(cx);
         let server_url = &auto_updater.server_url;
         let current_version = auto_updater.current_version;
-        let latest_release_url = if cx.has_global::<ReleaseChannel>()
-            && *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview
-        {
-            format!("{server_url}/releases/preview/{current_version}")
-        } else {
-            format!("{server_url}/releases/stable/{current_version}")
-        };
-        cx.platform().open_url(&latest_release_url);
+        if cx.has_global::<ReleaseChannel>() {
+            match cx.global::<ReleaseChannel>() {
+                ReleaseChannel::Dev => {}
+                ReleaseChannel::Nightly => {}
+                ReleaseChannel::Preview => cx
+                    .platform()
+                    .open_url(&format!("{server_url}/releases/preview/{current_version}")),
+                ReleaseChannel::Stable => cx
+                    .platform()
+                    .open_url(&format!("{server_url}/releases/stable/{current_version}")),
+            }
+        }
     }
 }
 
@@ -224,22 +228,19 @@ impl AutoUpdater {
             )
         });
 
-        let preview_param = cx.read(|cx| {
+        let mut url_string = format!(
+            "{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
+        );
+        cx.read(|cx| {
             if cx.has_global::<ReleaseChannel>() {
-                if *cx.global::<ReleaseChannel>() == ReleaseChannel::Preview {
-                    return "&preview=1";
+                if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
+                    url_string += "&";
+                    url_string += param;
                 }
             }
-            ""
         });
 
-        let mut response = client
-            .get(
-                &format!("{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg{preview_param}"),
-                Default::default(),
-                true,
-            )
-            .await?;
+        let mut response = client.get(&url_string, Default::default(), true).await?;
 
         let mut body = Vec::new();
         response

crates/auto_update2/Cargo.toml 🔗

@@ -0,0 +1,29 @@
+[package]
+name = "auto_update2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/auto_update.rs"
+doctest = false
+
+[dependencies]
+db = { package = "db2", path = "../db2" }
+client = { package = "client2", path = "../client2" }
+gpui = { package = "gpui2", path = "../gpui2" }
+menu = { package = "menu2", path = "../menu2" }
+project = { package = "project2", path = "../project2" }
+settings = { package = "settings2", path = "../settings2" }
+theme = { package = "theme2", path = "../theme2" }
+workspace = { package = "workspace2", path = "../workspace2" }
+util = { path = "../util" }
+anyhow.workspace = true
+isahc.workspace = true
+lazy_static.workspace = true
+log.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+serde_json.workspace = true
+smol.workspace = true
+tempdir.workspace = true

crates/auto_update2/src/auto_update.rs 🔗

@@ -0,0 +1,392 @@
+mod update_notification;
+
+use anyhow::{anyhow, Context, Result};
+use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION, ZED_SECRET_CLIENT_TOKEN};
+use db::kvp::KEY_VALUE_STORE;
+use db::RELEASE_CHANNEL;
+use gpui::{
+    actions, AppContext, AsyncAppContext, Context as _, Model, ModelContext, SemanticVersion, Task,
+    ViewContext, VisualContext,
+};
+use isahc::AsyncBody;
+use serde::Deserialize;
+use serde_derive::Serialize;
+use smol::io::AsyncReadExt;
+
+use settings::{Settings, SettingsStore};
+use smol::{fs::File, process::Command};
+use std::{ffi::OsString, sync::Arc, time::Duration};
+use update_notification::UpdateNotification;
+use util::channel::{AppCommitSha, ReleaseChannel};
+use util::http::HttpClient;
+use workspace::Workspace;
+
+const SHOULD_SHOW_UPDATE_NOTIFICATION_KEY: &str = "auto-updater-should-show-updated-notification";
+const POLL_INTERVAL: Duration = Duration::from_secs(60 * 60);
+
+actions!(Check, DismissErrorMessage, ViewReleaseNotes);
+
+#[derive(Serialize)]
+struct UpdateRequestBody {
+    installation_id: Option<Arc<str>>,
+    release_channel: Option<&'static str>,
+    telemetry: bool,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum AutoUpdateStatus {
+    Idle,
+    Checking,
+    Downloading,
+    Installing,
+    Updated,
+    Errored,
+}
+
+pub struct AutoUpdater {
+    status: AutoUpdateStatus,
+    current_version: SemanticVersion,
+    http_client: Arc<dyn HttpClient>,
+    pending_poll: Option<Task<Option<()>>>,
+    server_url: String,
+}
+
+#[derive(Deserialize)]
+struct JsonRelease {
+    version: String,
+    url: String,
+}
+
+struct AutoUpdateSetting(bool);
+
+impl Settings for AutoUpdateSetting {
+    const KEY: Option<&'static str> = Some("auto_update");
+
+    type FileContent = Option<bool>;
+
+    fn load(
+        default_value: &Option<bool>,
+        user_values: &[&Option<bool>],
+        _: &mut AppContext,
+    ) -> Result<Self> {
+        Ok(Self(
+            Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
+        ))
+    }
+}
+
+pub fn init(http_client: Arc<dyn HttpClient>, server_url: String, cx: &mut AppContext) {
+    AutoUpdateSetting::register(cx);
+
+    cx.observe_new_views(|wokrspace: &mut Workspace, _cx| {
+        wokrspace.register_action(|_, action: &Check, cx| check(action, cx));
+    })
+    .detach();
+
+    if let Some(version) = *ZED_APP_VERSION {
+        let auto_updater = cx.build_model(|cx| {
+            let updater = AutoUpdater::new(version, http_client, server_url);
+
+            let mut update_subscription = AutoUpdateSetting::get_global(cx)
+                .0
+                .then(|| updater.start_polling(cx));
+
+            cx.observe_global::<SettingsStore>(move |updater, cx| {
+                if AutoUpdateSetting::get_global(cx).0 {
+                    if update_subscription.is_none() {
+                        update_subscription = Some(updater.start_polling(cx))
+                    }
+                } else {
+                    update_subscription.take();
+                }
+            })
+            .detach();
+
+            updater
+        });
+        cx.set_global(Some(auto_updater));
+        //todo!(action)
+        // cx.add_global_action(view_release_notes);
+        // cx.add_action(UpdateNotification::dismiss);
+    }
+}
+
+pub fn check(_: &Check, cx: &mut AppContext) {
+    if let Some(updater) = AutoUpdater::get(cx) {
+        updater.update(cx, |updater, cx| updater.poll(cx));
+    }
+}
+
+fn _view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) {
+    if let Some(auto_updater) = AutoUpdater::get(cx) {
+        let auto_updater = auto_updater.read(cx);
+        let server_url = &auto_updater.server_url;
+        let current_version = auto_updater.current_version;
+        if cx.has_global::<ReleaseChannel>() {
+            match cx.global::<ReleaseChannel>() {
+                ReleaseChannel::Dev => {}
+                ReleaseChannel::Nightly => {}
+                ReleaseChannel::Preview => {
+                    cx.open_url(&format!("{server_url}/releases/preview/{current_version}"))
+                }
+                ReleaseChannel::Stable => {
+                    cx.open_url(&format!("{server_url}/releases/stable/{current_version}"))
+                }
+            }
+        }
+    }
+}
+
+pub fn notify_of_any_new_update(cx: &mut ViewContext<Workspace>) -> Option<()> {
+    let updater = AutoUpdater::get(cx)?;
+    let version = updater.read(cx).current_version;
+    let should_show_notification = updater.read(cx).should_show_update_notification(cx);
+
+    cx.spawn(|workspace, mut cx| async move {
+        let should_show_notification = should_show_notification.await?;
+        if should_show_notification {
+            workspace.update(&mut cx, |workspace, cx| {
+                workspace.show_notification(0, cx, |cx| {
+                    cx.build_view(|_| UpdateNotification::new(version))
+                });
+                updater
+                    .read(cx)
+                    .set_should_show_update_notification(false, cx)
+                    .detach_and_log_err(cx);
+            })?;
+        }
+        anyhow::Ok(())
+    })
+    .detach();
+
+    None
+}
+
+impl AutoUpdater {
+    pub fn get(cx: &mut AppContext) -> Option<Model<Self>> {
+        cx.default_global::<Option<Model<Self>>>().clone()
+    }
+
+    fn new(
+        current_version: SemanticVersion,
+        http_client: Arc<dyn HttpClient>,
+        server_url: String,
+    ) -> Self {
+        Self {
+            status: AutoUpdateStatus::Idle,
+            current_version,
+            http_client,
+            server_url,
+            pending_poll: None,
+        }
+    }
+
+    pub fn start_polling(&self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
+        cx.spawn(|this, mut cx| async move {
+            loop {
+                this.update(&mut cx, |this, cx| this.poll(cx))?;
+                cx.background_executor().timer(POLL_INTERVAL).await;
+            }
+        })
+    }
+
+    pub fn poll(&mut self, cx: &mut ModelContext<Self>) {
+        if self.pending_poll.is_some() || self.status == AutoUpdateStatus::Updated {
+            return;
+        }
+
+        self.status = AutoUpdateStatus::Checking;
+        cx.notify();
+
+        self.pending_poll = Some(cx.spawn(|this, mut cx| async move {
+            let result = Self::update(this.upgrade()?, cx.clone()).await;
+            this.update(&mut cx, |this, cx| {
+                this.pending_poll = None;
+                if let Err(error) = result {
+                    log::error!("auto-update failed: error:{:?}", error);
+                    this.status = AutoUpdateStatus::Errored;
+                    cx.notify();
+                }
+            })
+            .ok()
+        }));
+    }
+
+    pub fn status(&self) -> AutoUpdateStatus {
+        self.status
+    }
+
+    pub fn dismiss_error(&mut self, cx: &mut ModelContext<Self>) {
+        self.status = AutoUpdateStatus::Idle;
+        cx.notify();
+    }
+
+    async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
+        let (client, server_url, current_version) = this.read_with(&cx, |this, _| {
+            (
+                this.http_client.clone(),
+                this.server_url.clone(),
+                this.current_version,
+            )
+        })?;
+
+        let mut url_string = format!(
+            "{server_url}/api/releases/latest?token={ZED_SECRET_CLIENT_TOKEN}&asset=Zed.dmg"
+        );
+        cx.update(|cx| {
+            if cx.has_global::<ReleaseChannel>() {
+                if let Some(param) = cx.global::<ReleaseChannel>().release_query_param() {
+                    url_string += "&";
+                    url_string += param;
+                }
+            }
+        })?;
+
+        let mut response = client.get(&url_string, Default::default(), true).await?;
+
+        let mut body = Vec::new();
+        response
+            .body_mut()
+            .read_to_end(&mut body)
+            .await
+            .context("error reading release")?;
+        let release: JsonRelease =
+            serde_json::from_slice(body.as_slice()).context("error deserializing release")?;
+
+        let should_download = match *RELEASE_CHANNEL {
+            ReleaseChannel::Nightly => cx
+                .try_read_global::<AppCommitSha, _>(|sha, _| release.version != sha.0)
+                .unwrap_or(true),
+            _ => release.version.parse::<SemanticVersion>()? <= current_version,
+        };
+
+        if !should_download {
+            this.update(&mut cx, |this, cx| {
+                this.status = AutoUpdateStatus::Idle;
+                cx.notify();
+            })?;
+            return Ok(());
+        }
+
+        this.update(&mut cx, |this, cx| {
+            this.status = AutoUpdateStatus::Downloading;
+            cx.notify();
+        })?;
+
+        let temp_dir = tempdir::TempDir::new("zed-auto-update")?;
+        let dmg_path = temp_dir.path().join("Zed.dmg");
+        let mount_path = temp_dir.path().join("Zed");
+        let running_app_path = ZED_APP_PATH
+            .clone()
+            .map_or_else(|| cx.update(|cx| cx.app_path())?, Ok)?;
+        let running_app_filename = running_app_path
+            .file_name()
+            .ok_or_else(|| anyhow!("invalid running app path"))?;
+        let mut mounted_app_path: OsString = mount_path.join(running_app_filename).into();
+        mounted_app_path.push("/");
+
+        let mut dmg_file = File::create(&dmg_path).await?;
+
+        let (installation_id, release_channel, telemetry) = cx.update(|cx| {
+            let installation_id = cx.global::<Arc<Client>>().telemetry().installation_id();
+            let release_channel = cx
+                .has_global::<ReleaseChannel>()
+                .then(|| cx.global::<ReleaseChannel>().display_name());
+            let telemetry = TelemetrySettings::get_global(cx).metrics;
+
+            (installation_id, release_channel, telemetry)
+        })?;
+
+        let request_body = AsyncBody::from(serde_json::to_string(&UpdateRequestBody {
+            installation_id,
+            release_channel,
+            telemetry,
+        })?);
+
+        let mut response = client.get(&release.url, request_body, true).await?;
+        smol::io::copy(response.body_mut(), &mut dmg_file).await?;
+        log::info!("downloaded update. path:{:?}", dmg_path);
+
+        this.update(&mut cx, |this, cx| {
+            this.status = AutoUpdateStatus::Installing;
+            cx.notify();
+        })?;
+
+        let output = Command::new("hdiutil")
+            .args(&["attach", "-nobrowse"])
+            .arg(&dmg_path)
+            .arg("-mountroot")
+            .arg(&temp_dir.path())
+            .output()
+            .await?;
+        if !output.status.success() {
+            Err(anyhow!(
+                "failed to mount: {:?}",
+                String::from_utf8_lossy(&output.stderr)
+            ))?;
+        }
+
+        let output = Command::new("rsync")
+            .args(&["-av", "--delete"])
+            .arg(&mounted_app_path)
+            .arg(&running_app_path)
+            .output()
+            .await?;
+        if !output.status.success() {
+            Err(anyhow!(
+                "failed to copy app: {:?}",
+                String::from_utf8_lossy(&output.stderr)
+            ))?;
+        }
+
+        let output = Command::new("hdiutil")
+            .args(&["detach"])
+            .arg(&mount_path)
+            .output()
+            .await?;
+        if !output.status.success() {
+            Err(anyhow!(
+                "failed to unmount: {:?}",
+                String::from_utf8_lossy(&output.stderr)
+            ))?;
+        }
+
+        this.update(&mut cx, |this, cx| {
+            this.set_should_show_update_notification(true, cx)
+                .detach_and_log_err(cx);
+            this.status = AutoUpdateStatus::Updated;
+            cx.notify();
+        })?;
+        Ok(())
+    }
+
+    fn set_should_show_update_notification(
+        &self,
+        should_show: bool,
+        cx: &AppContext,
+    ) -> Task<Result<()>> {
+        cx.background_executor().spawn(async move {
+            if should_show {
+                KEY_VALUE_STORE
+                    .write_kvp(
+                        SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string(),
+                        "".to_string(),
+                    )
+                    .await?;
+            } else {
+                KEY_VALUE_STORE
+                    .delete_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY.to_string())
+                    .await?;
+            }
+            Ok(())
+        })
+    }
+
+    fn should_show_update_notification(&self, cx: &AppContext) -> Task<Result<bool>> {
+        cx.background_executor().spawn(async move {
+            Ok(KEY_VALUE_STORE
+                .read_kvp(SHOULD_SHOW_UPDATE_NOTIFICATION_KEY)?
+                .is_some())
+        })
+    }
+}

crates/auto_update2/src/update_notification.rs 🔗

@@ -0,0 +1,87 @@
+use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext};
+use menu::Cancel;
+use workspace::notifications::NotificationEvent;
+
+pub struct UpdateNotification {
+    _version: SemanticVersion,
+}
+
+impl EventEmitter<NotificationEvent> for UpdateNotification {}
+
+impl Render for UpdateNotification {
+    type Element = Div<Self>;
+
+    fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {
+        div().child("Updated zed!")
+        // let theme = theme::current(cx).clone();
+        // let theme = &theme.update_notification;
+
+        // let app_name = cx.global::<ReleaseChannel>().display_name();
+
+        // MouseEventHandler::new::<ViewReleaseNotes, _>(0, cx, |state, cx| {
+        //     Flex::column()
+        //         .with_child(
+        //             Flex::row()
+        //                 .with_child(
+        //                     Text::new(
+        //                         format!("Updated to {app_name} {}", self.version),
+        //                         theme.message.text.clone(),
+        //                     )
+        //                     .contained()
+        //                     .with_style(theme.message.container)
+        //                     .aligned()
+        //                     .top()
+        //                     .left()
+        //                     .flex(1., true),
+        //                 )
+        //                 .with_child(
+        //                     MouseEventHandler::new::<Cancel, _>(0, cx, |state, _| {
+        //                         let style = theme.dismiss_button.style_for(state);
+        //                         Svg::new("icons/x.svg")
+        //                             .with_color(style.color)
+        //                             .constrained()
+        //                             .with_width(style.icon_width)
+        //                             .aligned()
+        //                             .contained()
+        //                             .with_style(style.container)
+        //                             .constrained()
+        //                             .with_width(style.button_width)
+        //                             .with_height(style.button_width)
+        //                     })
+        //                     .with_padding(Padding::uniform(5.))
+        //                     .on_click(MouseButton::Left, move |_, this, cx| {
+        //                         this.dismiss(&Default::default(), cx)
+        //                     })
+        //                     .aligned()
+        //                     .constrained()
+        //                     .with_height(cx.font_cache().line_height(theme.message.text.font_size))
+        //                     .aligned()
+        //                     .top()
+        //                     .flex_float(),
+        //                 ),
+        //         )
+        //         .with_child({
+        //             let style = theme.action_message.style_for(state);
+        //             Text::new("View the release notes", style.text.clone())
+        //                 .contained()
+        //                 .with_style(style.container)
+        //         })
+        //         .contained()
+        // })
+        // .with_cursor_style(CursorStyle::PointingHand)
+        // .on_click(MouseButton::Left, |_, _, cx| {
+        //     crate::view_release_notes(&Default::default(), cx)
+        // })
+        // .into_any_named("update notification")
+    }
+}
+
+impl UpdateNotification {
+    pub fn new(version: SemanticVersion) -> Self {
+        Self { _version: version }
+    }
+
+    pub fn _dismiss(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+        cx.emit(NotificationEvent::Dismiss);
+    }
+}

crates/client/src/client.rs 🔗

@@ -987,9 +987,17 @@ impl Client {
         self.establish_websocket_connection(credentials, cx)
     }
 
-    async fn get_rpc_url(http: Arc<dyn HttpClient>, is_preview: bool) -> Result<Url> {
-        let preview_param = if is_preview { "?preview=1" } else { "" };
-        let url = format!("{}/rpc{preview_param}", *ZED_SERVER_URL);
+    async fn get_rpc_url(
+        http: Arc<dyn HttpClient>,
+        release_channel: Option<ReleaseChannel>,
+    ) -> Result<Url> {
+        let mut url = format!("{}/rpc", *ZED_SERVER_URL);
+        if let Some(preview_param) =
+            release_channel.and_then(|channel| channel.release_query_param())
+        {
+            url += "?";
+            url += preview_param;
+        }
         let response = http.get(&url, Default::default(), false).await?;
 
         // Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
@@ -1024,11 +1032,11 @@ impl Client {
         credentials: &Credentials,
         cx: &AsyncAppContext,
     ) -> Task<Result<Connection, EstablishConnectionError>> {
-        let use_preview_server = cx.read(|cx| {
+        let release_channel = cx.read(|cx| {
             if cx.has_global::<ReleaseChannel>() {
-                *cx.global::<ReleaseChannel>() != ReleaseChannel::Stable
+                Some(*cx.global::<ReleaseChannel>())
             } else {
-                false
+                None
             }
         });
 
@@ -1041,7 +1049,7 @@ impl Client {
 
         let http = self.http.clone();
         cx.background().spawn(async move {
-            let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
+            let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
             let rpc_host = rpc_url
                 .host_str()
                 .zip(rpc_url.port_or_known_default())
@@ -1191,7 +1199,7 @@ impl Client {
 
         // Use the collab server's admin API to retrieve the id
         // of the impersonated user.
-        let mut url = Self::get_rpc_url(http.clone(), false).await?;
+        let mut url = Self::get_rpc_url(http.clone(), None).await?;
         url.set_path("/user");
         url.set_query(Some(&format!("github_login={login}")));
         let request = Request::get(url.as_str())

crates/client/src/telemetry.rs 🔗

@@ -20,7 +20,7 @@ pub struct Telemetry {
 #[derive(Default)]
 struct TelemetryState {
     metrics_id: Option<Arc<str>>,      // Per logged-in user
-    installation_id: Option<Arc<str>>, // Per app installation (different for dev, preview, and stable)
+    installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
     session_id: Option<Arc<str>>,      // Per app launch
     app_version: Option<Arc<str>>,
     release_channel: Option<&'static str>,

crates/client2/src/client2.rs 🔗

@@ -923,9 +923,17 @@ impl Client {
         self.establish_websocket_connection(credentials, cx)
     }
 
-    async fn get_rpc_url(http: Arc<dyn HttpClient>, is_preview: bool) -> Result<Url> {
-        let preview_param = if is_preview { "?preview=1" } else { "" };
-        let url = format!("{}/rpc{preview_param}", *ZED_SERVER_URL);
+    async fn get_rpc_url(
+        http: Arc<dyn HttpClient>,
+        release_channel: Option<ReleaseChannel>,
+    ) -> Result<Url> {
+        let mut url = format!("{}/rpc", *ZED_SERVER_URL);
+        if let Some(preview_param) =
+            release_channel.and_then(|channel| channel.release_query_param())
+        {
+            url += "?";
+            url += preview_param;
+        }
         let response = http.get(&url, Default::default(), false).await?;
 
         // Normally, ZED_SERVER_URL is set to the URL of zed.dev website.
@@ -960,9 +968,7 @@ impl Client {
         credentials: &Credentials,
         cx: &AsyncAppContext,
     ) -> Task<Result<Connection, EstablishConnectionError>> {
-        let use_preview_server = cx
-            .try_read_global(|channel: &ReleaseChannel, _| *channel != ReleaseChannel::Stable)
-            .unwrap_or(false);
+        let release_channel = cx.try_read_global(|channel: &ReleaseChannel, _| *channel);
 
         let request = Request::builder()
             .header(
@@ -973,7 +979,7 @@ impl Client {
 
         let http = self.http.clone();
         cx.background_executor().spawn(async move {
-            let mut rpc_url = Self::get_rpc_url(http, use_preview_server).await?;
+            let mut rpc_url = Self::get_rpc_url(http, release_channel).await?;
             let rpc_host = rpc_url
                 .host_str()
                 .zip(rpc_url.port_or_known_default())
@@ -1120,7 +1126,7 @@ impl Client {
 
         // Use the collab server's admin API to retrieve the id
         // of the impersonated user.
-        let mut url = Self::get_rpc_url(http.clone(), false).await?;
+        let mut url = Self::get_rpc_url(http.clone(), None).await?;
         url.set_path("/user");
         url.set_query(Some(&format!("github_login={login}")));
         let request = Request::get(url.as_str())

crates/client2/src/telemetry.rs 🔗

@@ -20,7 +20,7 @@ pub struct Telemetry {
 
 struct TelemetryState {
     metrics_id: Option<Arc<str>>,      // Per logged-in user
-    installation_id: Option<Arc<str>>, // Per app installation (different for dev, preview, and stable)
+    installation_id: Option<Arc<str>>, // Per app installation (different for dev, nightly, preview, and stable)
     session_id: Option<Arc<str>>,      // Per app launch
     release_channel: Option<&'static str>,
     app_metadata: AppMetadata,

crates/gpui2/src/app.rs 🔗

@@ -492,6 +492,10 @@ impl AppContext {
         self.platform.open_url(url);
     }
 
+    pub fn app_path(&self) -> Result<PathBuf> {
+        self.platform.app_path()
+    }
+
     pub fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
         self.platform.path_for_auxiliary_executable(name)
     }

crates/util/src/channel.rs 🔗

@@ -1,6 +1,5 @@
-use std::env;
-
 use lazy_static::lazy_static;
+use std::env;
 
 lazy_static! {
     pub static ref RELEASE_CHANNEL_NAME: String = if cfg!(debug_assertions) {
@@ -9,18 +8,22 @@ lazy_static! {
     } else {
         include_str!("../../zed/RELEASE_CHANNEL").to_string()
     };
-    pub static ref RELEASE_CHANNEL: ReleaseChannel = match RELEASE_CHANNEL_NAME.as_str() {
+    pub static ref RELEASE_CHANNEL: ReleaseChannel = match RELEASE_CHANNEL_NAME.as_str().trim() {
         "dev" => ReleaseChannel::Dev,
+        "nightly" => ReleaseChannel::Nightly,
         "preview" => ReleaseChannel::Preview,
         "stable" => ReleaseChannel::Stable,
         _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME),
     };
 }
 
+pub struct AppCommitSha(pub String);
+
 #[derive(Copy, Clone, PartialEq, Eq, Default)]
 pub enum ReleaseChannel {
     #[default]
     Dev,
+    Nightly,
     Preview,
     Stable,
 }
@@ -29,6 +32,7 @@ impl ReleaseChannel {
     pub fn display_name(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "Zed Dev",
+            ReleaseChannel::Nightly => "Zed Nightly",
             ReleaseChannel::Preview => "Zed Preview",
             ReleaseChannel::Stable => "Zed",
         }
@@ -37,6 +41,7 @@ impl ReleaseChannel {
     pub fn dev_name(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "dev",
+            ReleaseChannel::Nightly => "nightly",
             ReleaseChannel::Preview => "preview",
             ReleaseChannel::Stable => "stable",
         }
@@ -45,6 +50,7 @@ impl ReleaseChannel {
     pub fn url_scheme(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "zed-dev://",
+            ReleaseChannel::Nightly => "zed-nightly://",
             ReleaseChannel::Preview => "zed-preview://",
             ReleaseChannel::Stable => "zed://",
         }
@@ -53,15 +59,27 @@ impl ReleaseChannel {
     pub fn link_prefix(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "https://zed.dev/dev/",
+            // TODO kb need to add server handling
+            ReleaseChannel::Nightly => "https://zed.dev/nightly/",
             ReleaseChannel::Preview => "https://zed.dev/preview/",
             ReleaseChannel::Stable => "https://zed.dev/",
         }
     }
+
+    pub fn release_query_param(&self) -> Option<&'static str> {
+        match self {
+            Self::Dev => None,
+            Self::Nightly => Some("nightly=1"),
+            Self::Preview => Some("preview=1"),
+            Self::Stable => None,
+        }
+    }
 }
 
 pub fn parse_zed_link(link: &str) -> Option<&str> {
     for release in [
         ReleaseChannel::Dev,
+        ReleaseChannel::Nightly,
         ReleaseChannel::Preview,
         ReleaseChannel::Stable,
     ] {

crates/workspace2/src/notifications.rs 🔗

@@ -15,6 +15,8 @@ pub enum NotificationEvent {
 
 pub trait Notification: EventEmitter<NotificationEvent> + Render {}
 
+impl<V: EventEmitter<NotificationEvent> + Render> Notification for V {}
+
 pub trait NotificationHandle: Send {
     fn id(&self) -> EntityId;
     fn to_any(&self) -> AnyView;
@@ -164,7 +166,7 @@ impl Workspace {
 }
 
 pub mod simple_message_notification {
-    use super::{Notification, NotificationEvent};
+    use super::NotificationEvent;
     use gpui::{AnyElement, AppContext, Div, EventEmitter, Render, TextStyle, ViewContext};
     use serde::Deserialize;
     use std::{borrow::Cow, sync::Arc};
@@ -359,7 +361,6 @@ pub mod simple_message_notification {
     //     }
 
     impl EventEmitter<NotificationEvent> for MessageNotification {}
-    impl Notification for MessageNotification {}
 }
 
 pub trait NotifyResultExt {

crates/zed/Cargo.toml 🔗

@@ -170,6 +170,15 @@ osx_minimum_system_version = "10.15.7"
 osx_info_plist_exts = ["resources/info/*"]
 osx_url_schemes = ["zed-dev"]
 
+[package.metadata.bundle-nightly]
+# TODO kb different icon?
+icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
+identifier = "dev.zed.Zed-Nightly"
+name = "Zed Nightly"
+osx_minimum_system_version = "10.15.7"
+osx_info_plist_exts = ["resources/info/*"]
+osx_url_schemes = ["zed-nightly"]
+
 [package.metadata.bundle-preview]
 icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
 identifier = "dev.zed.Zed-Preview"
@@ -178,7 +187,6 @@ osx_minimum_system_version = "10.15.7"
 osx_info_plist_exts = ["resources/info/*"]
 osx_url_schemes = ["zed-preview"]
 
-
 [package.metadata.bundle-stable]
 icon = ["resources/app-icon@2x.png", "resources/app-icon.png"]
 identifier = "dev.zed.Zed"

crates/zed/src/only_instance.rs 🔗

@@ -17,6 +17,7 @@ fn address() -> SocketAddr {
         ReleaseChannel::Dev => 43737,
         ReleaseChannel::Preview => 43738,
         ReleaseChannel::Stable => 43739,
+        ReleaseChannel::Nightly => 43740,
     };
 
     SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
@@ -25,6 +26,7 @@ fn address() -> SocketAddr {
 fn instance_handshake() -> &'static str {
     match *util::channel::RELEASE_CHANNEL {
         ReleaseChannel::Dev => "Zed Editor Dev Instance Running",
+        ReleaseChannel::Nightly => "Zed Editor Nightly Instance Running",
         ReleaseChannel::Preview => "Zed Editor Preview Instance Running",
         ReleaseChannel::Stable => "Zed Editor Stable Instance Running",
     }

crates/zed2/Cargo.toml 🔗

@@ -11,14 +11,14 @@ path = "src/zed2.rs"
 doctest = false
 
 [[bin]]
-name = "Zed2"
+name = "zed2"
 path = "src/main.rs"
 
 [dependencies]
 ai = { package = "ai2", path = "../ai2"}
 # audio = { path = "../audio" }
 # activity_indicator = { path = "../activity_indicator" }
-# auto_update = { path = "../auto_update" }
+auto_update = { package = "auto_update2", path = "../auto_update2" }
 # breadcrumbs = { path = "../breadcrumbs" }
 call = { package = "call2", path = "../call2" }
 # channel = { path = "../channel" }
@@ -166,6 +166,14 @@ osx_minimum_system_version = "10.15.7"
 osx_info_plist_exts = ["resources/info/*"]
 osx_url_schemes = ["zed-dev"]
 
+[package.metadata.bundle-nightly]
+icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
+identifier = "dev.zed.Zed-Dev"
+name = "Zed Nightly"
+osx_minimum_system_version = "10.15.7"
+osx_info_plist_exts = ["resources/info/*"]
+osx_url_schemes = ["zed-dev"]
+
 [package.metadata.bundle-preview]
 icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
 identifier = "dev.zed.Zed-Preview"

crates/zed2/build.rs 🔗

@@ -1,3 +1,5 @@
+use std::process::Command;
+
 fn main() {
     println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.15.7");
 
@@ -21,4 +23,14 @@ fn main() {
 
     // Register exported Objective-C selectors, protocols, etc
     println!("cargo:rustc-link-arg=-Wl,-ObjC");
+
+    // Populate git sha environment variable if git is available
+    if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
+        if output.status.success() {
+            println!(
+                "cargo:rustc-env=ZED_COMMIT_SHA={}",
+                String::from_utf8_lossy(&output.stdout).trim()
+            );
+        }
+    }
 }

crates/zed2/src/main.rs 🔗

@@ -43,7 +43,7 @@ use std::{
 use theme::ActiveTheme;
 use util::{
     async_maybe,
-    channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
+    channel::{parse_zed_link, AppCommitSha, ReleaseChannel, RELEASE_CHANNEL},
     http::{self, HttpClient},
     paths, ResultExt,
 };
@@ -113,6 +113,10 @@ fn main() {
 
     app.run(move |cx| {
         cx.set_global(*RELEASE_CHANNEL);
+        if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
+            cx.set_global(AppCommitSha(build_sha.into()))
+        }
+
         cx.set_global(listener.clone());
 
         load_embedded_fonts(cx);
@@ -183,7 +187,7 @@ fn main() {
         cx.set_global(Arc::downgrade(&app_state));
 
         // audio::init(Assets, cx);
-        // auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
+        auto_update::init(http.clone(), client::ZED_SERVER_URL.clone(), cx);
 
         workspace::init(app_state.clone(), cx);
         // recent_projects::init(cx);

crates/zed2/src/only_instance.rs 🔗

@@ -17,6 +17,7 @@ fn address() -> SocketAddr {
         ReleaseChannel::Dev => 43737,
         ReleaseChannel::Preview => 43738,
         ReleaseChannel::Stable => 43739,
+        ReleaseChannel::Nightly => 43740,
     };
 
     SocketAddr::V4(SocketAddrV4::new(LOCALHOST, port))
@@ -25,6 +26,7 @@ fn address() -> SocketAddr {
 fn instance_handshake() -> &'static str {
     match *util::channel::RELEASE_CHANNEL {
         ReleaseChannel::Dev => "Zed Editor Dev Instance Running",
+        ReleaseChannel::Nightly => "Zed Editor Nightly Instance Running",
         ReleaseChannel::Preview => "Zed Editor Preview Instance Running",
         ReleaseChannel::Stable => "Zed Editor Stable Instance Running",
     }

crates/zed2/src/zed2.rs 🔗

@@ -23,7 +23,7 @@ use std::{borrow::Cow, ops::Deref, sync::Arc};
 use terminal_view::terminal_panel::TerminalPanel;
 use util::{
     asset_str,
-    channel::ReleaseChannel,
+    channel::{AppCommitSha, ReleaseChannel},
     paths::{self, LOCAL_SETTINGS_RELATIVE_PATH},
     ResultExt,
 };
@@ -162,7 +162,7 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
             // status_bar.add_right_item(cursor_position, cx);
         });
 
-        //     auto_update::notify_of_any_new_update(cx.weak_handle(), cx);
+        auto_update::notify_of_any_new_update(cx);
 
         //     vim::observe_keystrokes(cx);
 
@@ -432,9 +432,16 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
 }
 
 fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
+    use std::fmt::Write as _;
+
     let app_name = cx.global::<ReleaseChannel>().display_name();
     let version = env!("CARGO_PKG_VERSION");
-    let prompt = cx.prompt(PromptLevel::Info, &format!("{app_name} {version}"), &["OK"]);
+    let mut message = format!("{app_name} {version}");
+    if let Some(sha) = cx.try_global::<AppCommitSha>() {
+        write!(&mut message, "\n\n{}", sha.0).unwrap();
+    }
+
+    let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
     cx.foreground_executor()
         .spawn(async {
             prompt.await.ok();

script/bump-nightly 🔗

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+branch=$(git rev-parse --abbrev-ref HEAD)
+if [ "$branch" != "main" ]; then
+  echo "You must be on main to run this script"
+  exit 1
+fi
+
+git pull --ff-only origin main
+git tag -f nightly
+git push -f origin nightly

script/bump-zed-minor-versions 🔗

@@ -43,8 +43,8 @@ if [[ $patch != 0 ]]; then
   echo "patch version on main should be zero"
   exit 1
 fi
-if [[ $(cat crates/zed/RELEASE_CHANNEL) != dev ]]; then
-  echo "release channel on main should be dev"
+if [[ $(cat crates/zed/RELEASE_CHANNEL) != dev && $(cat crates/zed/RELEASE_CHANNEL) != nightly ]]; then
+  echo "release channel on main should be dev or nightly"
   exit 1
 fi
 if git show-ref --quiet refs/tags/${preview_tag_name}; then
@@ -59,6 +59,7 @@ if ! git show-ref --quiet refs/heads/${prev_minor_branch_name}; then
   echo "previous branch ${minor_branch_name} doesn't exist"
   exit 1
 fi
+# TODO kb anything else for RELEASE_CHANNEL == nightly needs to be done below?
 if [[ $(git show ${prev_minor_branch_name}:crates/zed/RELEASE_CHANNEL) != preview ]]; then
   echo "release channel on branch ${prev_minor_branch_name} should be preview"
   exit 1

script/bump-zed-patch-version 🔗

@@ -9,8 +9,11 @@ case $channel in
   preview)
     tag_suffix="-pre"
     ;;
+  nightly)
+    tag_suffix="-nightly"
+    ;;
   *)
-    echo "this must be run on a stable or preview release branch" >&2
+    echo "this must be run on either of stable|preview|nightly release branches" >&2
     exit 1
     ;;
 esac

script/bundle 🔗

@@ -9,6 +9,7 @@ local_arch=false
 local_only=false
 overwrite_local_app=false
 bundle_name=""
+zed_crate="zed"
 
 # This must match the team in the provsiioning profile.
 APPLE_NOTORIZATION_TEAM="MQ55VZLNZQ"
@@ -25,13 +26,11 @@ Options:
   -o    Open the resulting DMG or the app itself in local mode.
   -f    Overwrite the local app bundle if it exists.
   -h    Display this help and exit.
+  -2    Build zed 2 instead of zed 1.
   "
 }
 
-# If -o option is specified, the folder of the resulting dmg will be opened in finder
-# If -d is specified, Zed will be compiled in debug mode and the application's path printed
-# If -od or -do is specified Zed will be bundled in debug and the application will be run.
-while getopts 'dlfoh' flag
+while getopts 'dlfoh2' flag
 do
     case "${flag}" in
         o) open_result=true;;
@@ -51,6 +50,7 @@ do
             target_dir="debug"
             ;;
         f) overwrite_local_app=true;;
+        2) zed_crate="zed2";;
         h)
            help_info
            exit 0
@@ -83,16 +83,19 @@ local_target_triple=${host_line#*: }
 
 if [ "$local_arch" = true ]; then
     echo "Building for local target only."
-    cargo build ${build_flag} --package zed
+    cargo build ${build_flag} --package ${zed_crate}
     cargo build ${build_flag} --package cli
 else
     echo "Compiling zed binaries"
-    cargo build ${build_flag} --package zed --package cli --target aarch64-apple-darwin --target x86_64-apple-darwin
+    cargo build ${build_flag} --package ${zed_crate} --package cli --target aarch64-apple-darwin --target x86_64-apple-darwin
 fi
 
 echo "Creating application bundle"
 pushd crates/zed
 channel=$(<RELEASE_CHANNEL)
+popd
+
+pushd crates/${zed_crate}
 cp Cargo.toml Cargo.toml.backup
 sed \
     -i .backup \
@@ -131,7 +134,8 @@ else
     cp -R target/${target_dir}/WebRTC.framework "${app_path}/Contents/Frameworks/"
 fi
 
-cp crates/zed/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
+#todo!(The app identifier has been set to 'Dev', but the channel is nightly, RATIONALIZE ALL OF THIS MESS)
+cp crates/${zed_crate}/contents/$channel/embedded.provisionprofile "${app_path}/Contents/"
 
 if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTARIZATION_USERNAME && -n $APPLE_NOTARIZATION_PASSWORD ]]; then
     echo "Signing bundle with Apple-issued certificate"
@@ -145,9 +149,14 @@ if [[ -n $MACOS_CERTIFICATE && -n $MACOS_CERTIFICATE_PASSWORD && -n $APPLE_NOTAR
 
     # sequence of codesign commands modeled after this example: https://developer.apple.com/forums/thread/701514
     /usr/bin/codesign --deep --force --timestamp --sign "Zed Industries, Inc." "${app_path}/Contents/Frameworks/WebRTC.framework" -v
-    /usr/bin/codesign --deep --force --timestamp --options runtime --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/cli" -v
-    /usr/bin/codesign --deep --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/zed" -v
-    /usr/bin/codesign --force --timestamp --options runtime --entitlements crates/zed/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}" -v
+
+    # todo!(restore cli to zed2)
+    if [[ "$zed_crate" == "zed" ]]; then
+        /usr/bin/codesign --deep --force --timestamp --options runtime --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/cli" -v
+    fi
+
+    /usr/bin/codesign --deep --force --timestamp --options runtime --entitlements crates/${zed_crate}/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}/Contents/MacOS/${zed_crate}" -v
+    /usr/bin/codesign --force --timestamp --options runtime --entitlements crates/${zed_crate}/resources/zed.entitlements --sign "Zed Industries, Inc." "${app_path}" -v
 
     security default-keychain -s login.keychain
 else
@@ -166,7 +175,7 @@ else
     # - get a signing key for the MQ55VZLNZQ team from Nathan.
     # - create your own signing key, and update references to MQ55VZLNZQ to your own team ID
     # then comment out this line.
-    cat crates/zed/resources/zed.entitlements | sed '/com.apple.developer.associated-domains/,+1d' > "${app_path}/Contents/Resources/zed.entitlements"
+    cat crates/${zed_crate}/resources/zed.entitlements | sed '/com.apple.developer.associated-domains/,+1d' > "${app_path}/Contents/Resources/zed.entitlements"
 
     codesign --force --deep --entitlements "${app_path}/Contents/Resources/zed.entitlements" --sign ${MACOS_SIGNING_KEY:- -} "${app_path}" -v
 fi

script/deploy 🔗

@@ -4,12 +4,17 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 2 ]]; then
-  echo "Usage: $0 <production|staging|preview> <tag-name>"
+  echo "Usage: $0 <production|staging|preview> <tag-name> (nightly is not yet supported)"
   exit 1
 fi
 environment=$1
 version=$2
 
+if [[ ${environment} == "nightly" ]]; then
+  echo "nightly is not yet supported"
+  exit 1
+fi
+
 export_vars_for_environment ${environment}
 image_id=$(image_id_for_version ${version})
 

script/deploy-migration 🔗

@@ -4,12 +4,17 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 2 ]]; then
-  echo "Usage: $0 <production|staging|preview> <tag-name>"
+  echo "Usage: $0 <production|staging|preview> <tag-name> (nightly is not yet supported)"
   exit 1
 fi
 environment=$1
 version=$2
 
+if [[ ${environment} == "nightly" ]]; then
+  echo "nightly is not yet supported"
+  exit 1
+fi
+
 export_vars_for_environment ${environment}
 image_id=$(image_id_for_version ${version})
 
@@ -23,4 +28,4 @@ envsubst < crates/collab/k8s/migrate.template.yml | kubectl apply -f -
 pod=$(kubectl --namespace=${environment} get pods --selector=job-name=${ZED_MIGRATE_JOB_NAME} --output=jsonpath='{.items[0].metadata.name}')
 
 echo "Job pod:" $pod
-kubectl --namespace=${environment} logs -f ${pod}
+kubectl --namespace=${environment} logs -f ${pod}

script/upload-nightly 🔗

@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# Based on the template in: https://docs.digitalocean.com/reference/api/spaces-api/
+set -ux
+
+# Step 1: Define the parameters for the Space you want to upload to.
+SPACE="zed-nightly-host" # Find your endpoint in the control panel, under Settings.
+REGION="nyc3" # Must be "us-east-1" when creating new Spaces. Otherwise, use the region in your endpoint (e.g. nyc3).
+
+# Step 2: Define a function that uploads your object via cURL.
+function uploadToSpaces
+{
+  file_to_upload="$1"
+  file_name="$2"
+  space_path="nightly"
+  date=$(date +"%a, %d %b %Y %T %z")
+  acl="x-amz-acl:private"
+  content_type="application/octet-stream"
+  storage_type="x-amz-storage-class:STANDARD"
+  string="PUT\n\n${content_type}\n${date}\n${acl}\n${storage_type}\n/${SPACE}/${space_path}/${file_name}"
+  signature=$(echo -en "${string}" | openssl sha1 -hmac "${DIGITALOCEAN_SPACES_SECRET_KEY}" -binary | base64)
+
+  curl -vv -s -X PUT -T "$file_to_upload" \
+    -H "Host: ${SPACE}.${REGION}.digitaloceanspaces.com" \
+    -H "Date: $date" \
+    -H "Content-Type: $content_type" \
+    -H "$storage_type" \
+    -H "$acl" \
+    -H "Authorization: AWS ${DIGITALOCEAN_SPACES_ACCESS_KEY}:$signature" \
+    "https://${SPACE}.${REGION}.digitaloceanspaces.com/${space_path}/${file_name}"
+}
+
+sha=$(git rev-parse HEAD)
+echo ${sha} > target/latest-sha
+
+uploadToSpaces "target/release/Zed.dmg" "Zed.dmg"
+uploadToSpaces "target/latest-sha" "latest-sha"

script/what-is-deployed 🔗

@@ -4,11 +4,16 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 1 ]]; then
-  echo "Usage: $0 <production|staging|preview>"
+  echo "Usage: $0 <production|staging|preview> (nightly is not yet supported)"
   exit 1
 fi
 environment=$1
 
+if [[ ${environment} == "nightly" ]]; then
+  echo "nightly is not yet supported"
+  exit 1
+fi
+
 export_vars_for_environment ${environment}
 target_zed_kube_cluster