Create new Zed release channel: nightly

Kirill Bulatov created

Change summary

.github/workflows/ci.yml              |  6 +++
crates/auto_update/src/auto_update.rs | 41 +++++++++++++++-------------
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/util/src/channel.rs            | 19 +++++++++++++
crates/zed/Cargo.toml                 | 10 ++++++
crates/zed/src/only_instance.rs       |  2 +
crates/zed2/Cargo.toml                |  9 ++++++
crates/zed2/src/only_instance.rs      |  2 +
script/bump-zed-minor-versions        |  5 ++-
script/bump-zed-patch-version         |  5 ++
script/deploy                         |  1 
script/deploy-migration               |  3 +
script/what-is-deployed               |  1 
16 files changed, 111 insertions(+), 43 deletions(-)

Detailed changes

.github/workflows/ci.yml 🔗

@@ -130,6 +130,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 +156,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' }}

crates/auto_update/src/auto_update.rs 🔗

@@ -118,14 +118,20 @@ 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 => cx
+                    .platform()
+                    .open_url(&format!("{server_url}/releases/nightly/{current_version}")),
+                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 +230,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/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/util/src/channel.rs 🔗

@@ -11,6 +11,7 @@ lazy_static! {
     };
     pub static ref RELEASE_CHANNEL: ReleaseChannel = match RELEASE_CHANNEL_NAME.as_str() {
         "dev" => ReleaseChannel::Dev,
+        "nightly" => ReleaseChannel::Nightly,
         "preview" => ReleaseChannel::Preview,
         "stable" => ReleaseChannel::Stable,
         _ => panic!("invalid release channel {}", *RELEASE_CHANNEL_NAME),
@@ -21,6 +22,7 @@ lazy_static! {
 pub enum ReleaseChannel {
     #[default]
     Dev,
+    Nightly,
     Preview,
     Stable,
 }
@@ -29,6 +31,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 +40,8 @@ impl ReleaseChannel {
     pub fn dev_name(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "dev",
+            // TODO kb need to add DB data
+            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,28 @@ 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,
+            // TODO kb need to add server handling
+            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/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 🔗

@@ -166,6 +166,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"

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",
     }

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/deploy 🔗

@@ -4,6 +4,7 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 2 ]]; then
+  # TODO kb nightly deploy?
   echo "Usage: $0 <production|staging|preview> <tag-name>"
   exit 1
 fi

script/deploy-migration 🔗

@@ -4,6 +4,7 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 2 ]]; then
+  # TODO kb nightly migrations?
   echo "Usage: $0 <production|staging|preview> <tag-name>"
   exit 1
 fi
@@ -23,4 +24,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/what-is-deployed 🔗

@@ -4,6 +4,7 @@ set -eu
 source script/lib/deploy-helpers.sh
 
 if [[ $# < 1 ]]; then
+  # TODO kb infra for nightly?
   echo "Usage: $0 <production|staging|preview>"
   exit 1
 fi