diff --git a/Fake.sln b/Fake.sln index 950586119c3..f239696c684 100644 --- a/Fake.sln +++ b/Fake.sln @@ -104,6 +104,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E09B72E4-D EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fake.Core.CommandLine.UnitTests", "src/test/Fake.Core.CommandLine.UnitTests/Fake.Core.CommandLine.UnitTests.fsproj", "{8561A35A-C2A4-43C7-A938-CB35A7747121}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fake.Api.HockeyApp", "src/app/Fake.Api.HockeyApp/Fake.Api.HockeyApp.fsproj", "{B636A082-4DB4-439D-8A37-E5214BDC00A3}" +EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fake.JavaScript.Yarn", "src/app/Fake.JavaScript.Yarn/Fake.JavaScript.Yarn.fsproj", "{DE7579F2-C20F-4C35-BC04-C10362912243}" EndProject Global @@ -668,6 +670,18 @@ Global {8561A35A-C2A4-43C7-A938-CB35A7747121}.Release|x64.Build.0 = Release|x64 {8561A35A-C2A4-43C7-A938-CB35A7747121}.Release|x86.ActiveCfg = Release|x86 {8561A35A-C2A4-43C7-A938-CB35A7747121}.Release|x86.Build.0 = Release|x86 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|x64.ActiveCfg = Debug|x64 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|x64.Build.0 = Debug|x64 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|x86.ActiveCfg = Debug|x86 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Debug|x86.Build.0 = Debug|x86 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|Any CPU.Build.0 = Release|Any CPU + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|x64.ActiveCfg = Release|x64 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|x64.Build.0 = Release|x64 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|x86.ActiveCfg = Release|x86 + {B636A082-4DB4-439D-8A37-E5214BDC00A3}.Release|x86.Build.0 = Release|x86 {DE7579F2-C20F-4C35-BC04-C10362912243}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE7579F2-C20F-4C35-BC04-C10362912243}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE7579F2-C20F-4C35-BC04-C10362912243}.Debug|x64.ActiveCfg = Debug|x64 @@ -733,6 +747,7 @@ Global {CA6EB1B3-EB3A-4063-8A6C-DE099A53A8B1} = {901F162F-8925-4390-89C5-9EE2C343F744} {E09B72E4-D890-46A8-8D14-7367C2E23E9D} = {539D7B9A-18A1-4D79-86AB-C8B48090CA84} {8561A35A-C2A4-43C7-A938-CB35A7747121} = {E09B72E4-D890-46A8-8D14-7367C2E23E9D} + {B636A082-4DB4-439D-8A37-E5214BDC00A3} = {901F162F-8925-4390-89C5-9EE2C343F744} {DE7579F2-C20F-4C35-BC04-C10362912243} = {901F162F-8925-4390-89C5-9EE2C343F744} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/build.fsx b/build.fsx index 70b188bbc42..ca2fff13dd3 100644 --- a/build.fsx +++ b/build.fsx @@ -243,8 +243,9 @@ let common = [ // New FAKE libraries let dotnetAssemblyInfos = [ "dotnet-fake", "Fake dotnet-cli command line tool" - "Fake.Api.Slack", "Slack Integration Support" "Fake.Api.GitHub", "GitHub Client API Support via Octokit" + "Fake.Api.HockeyApp", "HockeyApp Integration Support" + "Fake.Api.Slack", "Slack Integration Support" "Fake.Azure.CloudServices", "Azure Cloud Services Support" "Fake.Azure.Emulators", "Azure Emulators Support" "Fake.Azure.Kudu", "Azure Kudu Support" diff --git a/src/app/Fake.Api.HockeyApp/AssemblyInfo.fs b/src/app/Fake.Api.HockeyApp/AssemblyInfo.fs new file mode 100644 index 00000000000..cfa6a1d8226 --- /dev/null +++ b/src/app/Fake.Api.HockeyApp/AssemblyInfo.fs @@ -0,0 +1,17 @@ +// Auto-Generated by FAKE; do not edit +namespace System +open System.Reflection + +[] +[] +[] +[] +[] +do () + +module internal AssemblyVersionInformation = + let [] AssemblyTitle = "FAKE - F# Make HockeyApp Integration Support" + let [] AssemblyProduct = "FAKE - F# Make" + let [] AssemblyVersion = "5.0.0" + let [] AssemblyInformationalVersion = "5.0.0-rc004" + let [] AssemblyFileVersion = "5.0.0" diff --git a/src/app/Fake.Api.HockeyApp/Fake.Api.HockeyApp.fsproj b/src/app/Fake.Api.HockeyApp/Fake.Api.HockeyApp.fsproj new file mode 100644 index 00000000000..e91b9002a4a --- /dev/null +++ b/src/app/Fake.Api.HockeyApp/Fake.Api.HockeyApp.fsproj @@ -0,0 +1,21 @@ + + + net46;netstandard1.6;netstandard2.0 + Fake.Api.HockeyApp + Library + + + $(DefineConstants);NETSTANDARD;USE_HTTPCLIENT + + + $(DefineConstants);RELEASE + + + + + + + + + + diff --git a/src/app/Fake.Api.HockeyApp/HockeyApp.fs b/src/app/Fake.Api.HockeyApp/HockeyApp.fs new file mode 100644 index 00000000000..c53fd371a71 --- /dev/null +++ b/src/app/Fake.Api.HockeyApp/HockeyApp.fs @@ -0,0 +1,354 @@ +namespace Fake.Api + +#if NETSTANDARD +open System.Net.Http +#else +open System.Net +#endif + +open Microsoft.FSharp.Core +open System +open System.IO +open System.Text.RegularExpressions +open Fake.Core +open Newtonsoft.Json + +/// Contains tasks to interact with [HockeyApp](http://hockeyapp.com) +module HockeyApp = + + /// The release type of the app + type ReleaseType = + | Beta = 0 + | Store = 1 + | Alpha = 2 + | Enterprise = 3 + + /// The notification options + type NotifyOption = + | None = 0 + | CanInstallApp = 1 + | All = 2 + + /// The note types + type NoteType = + | Textile = 0 + | Markdown = 1 + + /// The mandatory options + type MandatoryOption = + | NotMandatory = 0 + | Mandatory = 1 + + /// The release download status + type DownloadStatusOption = + | NotDownloadable = 1 + | Downloadable = 2 + + /// HockeyApp's success response + type HockeyResponse = { + Title : string + + [] + BundleIdentifier: string + + [] + PublicIdentifier: string + + [] + DeviceFamily: string + + [] + MinimumOSVersion: string + + [] + ReleaseType : ReleaseType + + Platform : string + + Status : int + + [] + ConfigUrl : string + + [] + PublicUrl : string + + AppSize : int64 + } + + /// HockeyAppVersion's success response + /// https://support.hockeyapp.net/kb/api/api-versions#create-version + type HockeyVersionResponse = { + Title: string + + Timestamp : int64 + + Id : string + + Version : string + + ShortVersion : string + + Status : int + + [] + ConfigUrl : string + + [] + PublicUrl : string + } + + type BaseHockeyAppParams = + /// (Required) API token + abstract ApiToken: string + + /// Set to your App Id (required for UWP apps targeting windows phone) + abstract AppId: string + + /// Set maximum upload delay + abstract UploadTimeout: TimeSpan + + /// The HockeyAppVersion parameter type + /// Based on https://support.hockeyapp.net/kb/api/api-versions#create-version + type HockeyAppVersionParams = + { + /// (Required) API token + ApiToken: string + + /// Set to your App Id (required for UWP apps targeting windows phone) + AppId: string + + /// Human readable version + Version: string + + /// Set maximum upload delay + UploadTimeout: TimeSpan + } + interface BaseHockeyAppParams with + member this.ApiToken = this.ApiToken + member this.AppId = this.AppId + member this.UploadTimeout = this.UploadTimeout + + /// The HockeyApp parameter type + /// Based on http://support.hockeyapp.net/kb/api/api-apps#upload-app + type HockeyAppUploadParams = + { + /// (Required) API token + ApiToken: string + + /// (Required) file data for the build (.ipa or .apk) + File: string + + /// file data for dsym (IOS: *.dysm.zip or Android: mapping.txt) + Dsym: string + + /// Release notes for the build + Notes: string + + /// Release notes type for the build + NotesType: NoteType + + /// Set the release type of the app + ReleaseType: ReleaseType + + /// Set the owner of the app + OwnerId: string + + /// Set the notify option + Notify: NotifyOption + + /// Set version as mandatory + Mandatory: MandatoryOption + + /// Set to true to enable the private download page (default is false) + Private: bool + + /// Set to the git commit sha for this build + CommitSHA: string + + /// Set to the URL of the build job on your build server + BuildServerUrl: string + + /// Set to your source repository + RepositoryUrl: string + + /// Release download status (can only be set with full-access tokens) + DownloadStatus: DownloadStatusOption + + /// Restrict download to specific teams + Teams: string[] + + /// Set maximum upload delay + UploadTimeout: TimeSpan + + /// Set to your App Id (required for UWP apps targeting windows phone) + AppId: string + + /// When uploading a build, specify to which version (hockeyapp version id) + VersionId: string + } + interface BaseHockeyAppParams with + member this.ApiToken: string = this.ApiToken + member this.AppId: string = this.AppId + member this.UploadTimeout = this.UploadTimeout + + /// The default HockeyApp parameters to upload a build + let HockeyAppUploadDefaults = { + ApiToken = String.Empty + File = String.Empty + Dsym = String.Empty + Notes = String.Empty + NotesType = NoteType.Textile + ReleaseType = ReleaseType.Beta + OwnerId = String.Empty + Notify = NotifyOption.None + Mandatory = MandatoryOption.NotMandatory + Private = false + CommitSHA = String.Empty + BuildServerUrl = String.Empty + RepositoryUrl = String.Empty + DownloadStatus = DownloadStatusOption.NotDownloadable + Teams = Array.empty + UploadTimeout = TimeSpan.FromMinutes 2. + AppId = String.Empty + VersionId = String.Empty + } + + /// The default HockeyAppVersion parameters to create a version + let HockeyAppVersionDefaults = { + ApiToken = String.Empty + AppId = String.Empty + Version = String.Empty + UploadTimeout = TimeSpan.FromMinutes 2. + } + + /// [omit] + let private nl = Environment.NewLine + + /// [omit] + let private validateParams param:HockeyAppUploadParams = + if param.ApiToken = "" then failwith "You must provide your API token" + if param.File = "" then failwith "You must provide an app file to upload" + if not <| File.Exists param.File then + failwithf "No such file: %s" param.File + + if not (String.IsNullOrEmpty param.Dsym) then + if not (param.Dsym.EndsWith(".dsym.zip", StringComparison.OrdinalIgnoreCase) + || param.Dsym.Equals("mapping.txt", StringComparison.OrdinalIgnoreCase)) then + failwith "DSYM files should only be: IOS: *.dsym.zip Android: mapping.txt" + + if param.File.EndsWith(".ipa") + && not (param.Dsym.EndsWith(".dsym.zip", StringComparison.OrdinalIgnoreCase)) then + failwith "DSYM for an .ipa file can only only be: *.dsym.zip" + + if param.File.EndsWith(".apk") + && not (param.Dsym.Equals("mapping.txt", StringComparison.OrdinalIgnoreCase)) then + failwith "DSYM for an .apk file can only only be: mapping.txt" + + param + + /// [omit] + let private validateVersionParams (param:HockeyAppVersionParams) = + if param.ApiToken = "" then failwith "You must provide your API token" + if param.Version = "" then failwith "You must provide a version" + if param.AppId = "" then failwith "You must provide an app id" + param + + /// [omit] + let private toCurlArgs (param:HockeyAppUploadParams) = seq { + yield (String.Format("-sL -w \"{0}%{{http_code}}{0}\"", Regex.Escape(nl))) + if not (String.IsNullOrEmpty param.VersionId) then yield "-X PUT" + yield sprintf "-H \"X-HockeyAppToken:%s\"" param.ApiToken + yield sprintf "-F \"ipa=@%s\"" param.File + if not (String.IsNullOrEmpty param.Dsym) then yield sprintf "-F \"dsym=@%s\"" param.Dsym + yield sprintf "-F \"notes=%s\"" param.Notes + yield sprintf "-F \"notes_type=%i\"" (int param.NotesType) + yield sprintf "-F \"release_type=%i\"" (int param.ReleaseType) + yield sprintf "-F \"notify=%i\"" (int param.Notify) + yield sprintf "-F \"mandatory=%i\"" (int param.Mandatory) + yield sprintf "-F \"status=%i\"" (int param.DownloadStatus) + yield sprintf "-F \"private=%b\"" param.Private + yield sprintf "-F \"teams=%s\"" (param.Teams |> String.concat ",") + if not (String.IsNullOrEmpty param.OwnerId) then yield sprintf "-F \"owner_id=%s\"" param.OwnerId + if not (String.IsNullOrEmpty param.CommitSHA) then yield sprintf "-F \"commit_sha=%s\"" param.CommitSHA + if not (String.IsNullOrEmpty param.BuildServerUrl) then yield sprintf "-F \"build_server_url=%s\"" param.BuildServerUrl + if not (String.IsNullOrEmpty param.RepositoryUrl) then yield sprintf "-F \"repository_url=%s\"" param.RepositoryUrl + + if not (String.IsNullOrEmpty param.AppId) && (String.IsNullOrEmpty param.VersionId) then + yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/upload" param.AppId + else if not (String.IsNullOrEmpty param.AppId) && not (String.IsNullOrEmpty param.VersionId) then + yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/%s" param.AppId param.VersionId + else + yield "https://rink.hockeyapp.net/api/2/apps/upload" + } + + /// [omit] + let private toVersionCurlArgs (param:HockeyAppVersionParams) = seq { + yield (String.Format("-sL -w \"{0}%{{http_code}}{0}\"", Regex.Escape(nl))) + yield "-X POST" + yield "-H \"Content-Type: application/json\"" + yield sprintf "-d '{\"bundle_version\":\"%s\"}'" param.Version + yield sprintf "-H \"X-HockeyAppToken:%s\"" param.ApiToken + yield sprintf "https://rink.hockeyapp.net/api/2/apps/%s/app_versions/new" param.AppId + } + + /// [omit] + let private processHockeyAppCmd<'TParam, 'TResponse when 'TParam :> BaseHockeyAppParams> defaults + (setParams: 'TParam -> 'TParam) + (validateParam: 'TParam -> 'TParam) + (toCurlArgs: 'TParam -> seq) = + let p = defaults + |> setParams + |> validateParam + + p + |> toCurlArgs + |> fun args -> + Process.execWithResult (fun p -> + { p with + FileName = "curl" + Arguments = (String.concat " " args) + }) p.UploadTimeout + |> fun response -> + let error = sprintf "Error while posting to HockeyApp.%sMessages: %s%sErrors: %s%s" nl (String.concat "; " response.Messages) nl (String.concat "; " response.Errors) nl + match response.ExitCode with + | 0 -> + match Int32.TryParse (response.Messages.[response.Messages.Length - 1].Trim()) with + | (false, _) -> failwith error + | (true, responseCode) -> + match responseCode with + | 201 -> JsonConvert.DeserializeObject<'TResponse>(response.Messages.[0]) + | _ -> failwith error + | _ -> failwith error + + + /// Uploads an app to HockeyApp + /// ## Parameters + /// - `setParams` - Function used to override the default parameters + /// + /// ## Sample + /// + /// uploadApp (fun defaults -> + /// {defaults with + /// AppId = ... + /// ApiToken = ... + /// ... + /// }) + + let uploadApp setParams = processHockeyAppCmd HockeyAppUploadDefaults setParams validateParams toCurlArgs + + /// Create a new version of an app on HockeyApp + /// ## Parameters + /// - `setParams` - Function used to override the default parameters + /// + /// ## Sample + /// + /// createAppVersion (fun defaults -> + /// {defaults with + /// AppId = ... + /// ApiToken = ... + /// Version = ... + /// ... + /// }) + + let createAppVersion setParams = processHockeyAppCmd HockeyAppVersionDefaults setParams validateVersionParams toVersionCurlArgs diff --git a/src/app/Fake.Api.HockeyApp/paket.references b/src/app/Fake.Api.HockeyApp/paket.references new file mode 100644 index 00000000000..7e3f02584b1 --- /dev/null +++ b/src/app/Fake.Api.HockeyApp/paket.references @@ -0,0 +1,6 @@ +group netcore + +FSharp.Core +NETStandard.Library +System.Net.Http +Newtonsoft.Json diff --git a/src/legacy/FakeLib/FakeLib.fsproj b/src/legacy/FakeLib/FakeLib.fsproj index 266f739bf21..e1b29b4b904 100644 --- a/src/legacy/FakeLib/FakeLib.fsproj +++ b/src/legacy/FakeLib/FakeLib.fsproj @@ -376,6 +376,9 @@ Fake.BuildServer.TeamFoundation/TeamFoundation.fs + + Fake.Api.HockeyApp\HockeyApp.fs + diff --git a/src/legacy/FakeLib/HockeyAppHelper.fs b/src/legacy/FakeLib/HockeyAppHelper.fs index 3033c8480a2..27f7145ee4a 100644 --- a/src/legacy/FakeLib/HockeyAppHelper.fs +++ b/src/legacy/FakeLib/HockeyAppHelper.fs @@ -1,4 +1,5 @@ /// Contains tasks to interact with [HockeyApp](http://hockeyapp.com) +[] module Fake.HockeyAppHelper open Microsoft.FSharp.Core @@ -12,6 +13,7 @@ open Fake open Newtonsoft.Json /// The release type of the app +[] type ReleaseType = | Beta = 0 | Store = 1 @@ -19,27 +21,32 @@ type ReleaseType = | Enterprise = 3 /// The notification options +[] type NotifyOption = | None = 0 | CanInstallApp = 1 | All = 2 /// The note types +[] type NoteType = | Textile = 0 | Markdown = 1 /// The mandatory options +[] type MandatoryOption = | NotMandatory = 0 | Mandatory = 1 /// The release download status +[] type DownloadStatusOption = | NotDownloadable = 1 | Downloadable = 2 /// HockeyApp's success response +[] type HockeyResponse = { Title : string @@ -73,6 +80,7 @@ type HockeyResponse = { /// HockeyAppVersion's success response /// https://support.hockeyapp.net/kb/api/api-versions#create-version +[] type HockeyVersionResponse = { Title: string @@ -93,6 +101,7 @@ type HockeyVersionResponse = { PublicUrl : string } +[] type BaseHockeyAppParams = /// (Required) API token abstract ApiToken: string @@ -105,6 +114,7 @@ type BaseHockeyAppParams = /// The HockeyAppVersion parameter type /// Based on https://support.hockeyapp.net/kb/api/api-versions#create-version +[] [] type HockeyAppVersionParams = { @@ -127,6 +137,7 @@ type HockeyAppVersionParams = /// The HockeyApp parameter type /// Based on http://support.hockeyapp.net/kb/api/api-apps#upload-app +[] [] type HockeyAppUploadParams = { @@ -331,6 +342,7 @@ let private processHockeyAppCmd<'TParam, 'TResponse when 'TParam :> BaseHockeyAp /// ... /// }) +[] let HockeyApp setParams = processHockeyAppCmd HockeyAppUploadDefaults setParams validateParams toCurlArgs /// Create a new version of an app on HockeyApp @@ -347,4 +359,5 @@ let HockeyApp setParams = processHockeyAppCmd] let HockeyAppVersion setParams = processHockeyAppCmd HockeyAppVersionDefaults setParams validateVersionParams toVersionCurlArgs