├── docs ├── favicon.ico ├── htmltest.yml ├── assets │ └── images │ │ ├── icon.png │ │ ├── banner.png │ │ ├── header.png │ │ ├── favicon_120px.png │ │ ├── favicon_152px.png │ │ ├── favicon_167px.png │ │ ├── favicon_16px.png │ │ ├── favicon_180px.png │ │ ├── favicon_32px.png │ │ └── favicon_96px.png ├── Gemfile ├── commands │ ├── commands.md │ ├── cider_docs_man.md │ ├── cider_docs_md.md │ ├── cider_docs_config.md │ ├── cider_completions.md │ ├── cider.md │ ├── cider_check.md │ ├── cider_docs.md │ ├── cider_init.md │ └── cider_release.md ├── _sass │ └── custom │ │ └── custom.scss ├── man │ ├── cider.1 │ ├── cider_docs_man.1 │ ├── cider_docs_md.1 │ ├── cider_completions.1 │ ├── cider_docs_config.1 │ ├── cider_docs.1 │ ├── cider_check.1 │ ├── cider_init.1 │ └── cider_release.1 ├── _includes │ └── head_custom.html ├── _config.yml ├── index.md ├── install.md ├── faq.md ├── configuration-footer.md └── quick-start.md ├── .golangci.yml ├── pkg ├── config │ ├── testdata │ │ ├── invalid.yml │ │ └── valid.yml │ ├── doc.go │ └── config_test.go └── context │ ├── interrupt_test.go │ ├── credentials_test.go │ ├── credentials.go │ ├── interrupt.go │ ├── context_test.go │ └── context.go ├── go.mod ├── .gitignore ├── .github └── workflows │ ├── docs.yml │ └── build.yml ├── internal ├── clicommand │ ├── import.go │ ├── error_test.go │ ├── error.go │ ├── init_test.go │ ├── root_test.go │ ├── clicommand.go │ ├── check_test.go │ ├── config.go │ ├── completions_test.go │ ├── config_test.go │ ├── completions.go │ ├── check.go │ └── root.go ├── defaults │ ├── defaults_test.go │ └── defaults.go ├── pipeline │ ├── pipeline_test.go │ └── pipeline.go ├── client │ ├── project.go │ ├── assets_test.go │ ├── clienttest │ │ └── clienttest_test.go │ └── util_test.go ├── middleware │ ├── middleware.go │ ├── logging_test.go │ ├── error.go │ ├── error_test.go │ └── logging.go ├── closer │ ├── closer.go │ └── closer_test.go ├── log │ ├── log_test.go │ └── log.go ├── pipe │ ├── pipe_test.go │ ├── defaults │ │ ├── defaults.go │ │ └── defaults_test.go │ ├── semver │ │ ├── semver.go │ │ └── semver_test.go │ ├── pipe.go │ ├── testflight │ │ ├── testflight_test.go │ │ └── testflight.go │ ├── publish │ │ ├── publish.go │ │ └── publish_test.go │ ├── env │ │ ├── env.go │ │ └── env_test.go │ └── store │ │ └── store_test.go ├── git │ ├── errors_test.go │ ├── errors.go │ ├── git.go │ └── git_test.go ├── template │ ├── template_test.go │ └── template.go ├── shell │ ├── shell_test.go │ ├── shelltest │ │ ├── shelltest_test.go │ │ └── shelltest.go │ └── shell.go └── parallel │ ├── group_test.go │ └── group.go ├── tools ├── gendoc │ ├── doc.go │ ├── man.go │ ├── md.go │ ├── main.go │ └── config.go └── licensing │ └── main.go ├── cmd └── cider │ ├── main.go │ └── main_test.go ├── README.md └── .goreleaser.yml /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /docs/htmltest.yml: -------------------------------------------------------------------------------- 1 | DirectoryPath: _site 2 | IgnoreInternalEmptyHash: true 3 | IgnoreSSLVerify: true 4 | -------------------------------------------------------------------------------- /docs/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/icon.png -------------------------------------------------------------------------------- /docs/assets/images/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/banner.png -------------------------------------------------------------------------------- /docs/assets/images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/header.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_120px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_120px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_152px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_152px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_167px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_167px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_16px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_16px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_180px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_180px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_32px.png -------------------------------------------------------------------------------- /docs/assets/images/favicon_96px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cidertool/cider/HEAD/docs/assets/images/favicon_96px.png -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } 4 | 5 | gem "github-pages", "~> 214", group: :jekyll_plugins 6 | gem "jekyll-seo-tag", "~> 2.7" 7 | gem "just-the-docs" 8 | -------------------------------------------------------------------------------- /docs/commands/commands.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | nav_order: 7 4 | has_children: true 5 | --- 6 | 7 | # Commands 8 | 9 | Please refer to the reference documentation for each command for more information, or consult the manpage if it's more accessible to you. 10 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | issues: 4 | max-per-linter: 0 5 | max-same-issues: 0 6 | exclude-use-default: false 7 | linters: 8 | enable-all: true 9 | disable: 10 | - cyclop 11 | - exhaustivestruct 12 | - funlen 13 | - gofumpt 14 | - interfacer 15 | - lll 16 | - maligned 17 | - scopelint 18 | - testpackage 19 | - wrapcheck 20 | -------------------------------------------------------------------------------- /pkg/config/testdata/invalid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Wayfair: 3 | id: com.sky.ProjectApp 4 | localizations: 5 | - name: My App 6 | subtitle: congratulations 7 | privacyPolicy: 8 | text: go away 9 | url: https://google.com 10 | - name: 僕のアップ 11 | subtitle: おめでとう 12 | privacyPolicy: 13 | text: 消えろ 14 | url: https://google.co.jp 15 | versions: ~ 16 | -------------------------------------------------------------------------------- /docs/_sass/custom/custom.scss: -------------------------------------------------------------------------------- 1 | .main-content .task-list-item { 2 | display: block; 3 | } 4 | 5 | h4, 6 | .text-delta, 7 | h5, 8 | .text-epsilon, 9 | h6, 10 | .text-zeta { 11 | font-size: 18px !important; 12 | font-weight: inherit; 13 | text-transform: inherit; 14 | letter-spacing: inherit; 15 | } 16 | 17 | img.header { 18 | display: block; 19 | width: auto; 20 | height: 100px; 21 | margin: 0 auto 2em; 22 | } 23 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cidertool/cider 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Masterminds/semver/v3 v3.1.1 7 | github.com/alessio/shellescape v1.4.1 8 | github.com/apex/log v1.9.0 9 | github.com/cidertool/asc-go v0.5.1 10 | github.com/fatih/color v1.10.0 11 | github.com/hashicorp/go-multierror v1.1.1 12 | github.com/manifoldco/promptui v0.8.0 13 | github.com/spf13/cobra v1.1.3 14 | github.com/stretchr/testify v1.7.0 15 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 16 | gopkg.in/yaml.v2 v2.4.0 17 | ) 18 | -------------------------------------------------------------------------------- /docs/commands/cider_docs_man.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: docs man 5 | nav_order: 0 6 | nav_exclude: true 7 | --- 8 | 9 | ## cider docs man 10 | 11 | Generate man documentation for Cider. 12 | 13 | ``` 14 | cider docs man [flags] 15 | ``` 16 | 17 | ### Options 18 | 19 | ``` 20 | -h, --help help for man 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | --debug Enable debug mode 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [cider docs](/commands/cider_docs/) - Generate documentation for Cider 32 | 33 | -------------------------------------------------------------------------------- /docs/commands/cider_docs_md.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: docs md 5 | nav_order: 0 6 | nav_exclude: true 7 | --- 8 | 9 | ## cider docs md 10 | 11 | Generate Markdown documentation for Cider. 12 | 13 | ``` 14 | cider docs md [flags] 15 | ``` 16 | 17 | ### Options 18 | 19 | ``` 20 | -h, --help help for md 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | --debug Enable debug mode 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [cider docs](/commands/cider_docs/) - Generate documentation for Cider 32 | 33 | -------------------------------------------------------------------------------- /docs/commands/cider_docs_config.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: docs config 5 | nav_order: 0 6 | nav_exclude: true 7 | --- 8 | 9 | ## cider docs config 10 | 11 | Generate configuration file documentation for Cider. 12 | 13 | ``` 14 | cider docs config [flags] 15 | ``` 16 | 17 | ### Options 18 | 19 | ``` 20 | -h, --help help for config 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | --debug Enable debug mode 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [cider docs](/commands/cider_docs/) - Generate documentation for Cider 32 | 33 | -------------------------------------------------------------------------------- /docs/man/cider.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER" "1" "Apr 2021" "" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider \- Submit your builds to the Apple App Store in seconds 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Submit your builds to the Apple App Store in seconds 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-\-debug\fP[=false] 22 | Enable debug mode 23 | 24 | .PP 25 | \fB\-h\fP, \fB\-\-help\fP[=false] 26 | help for cider 27 | 28 | 29 | .SH SEE ALSO 30 | .PP 31 | \fBcider\-check(1)\fP, \fBcider\-completions(1)\fP, \fBcider\-init(1)\fP, \fBcider\-release(1)\fP 32 | -------------------------------------------------------------------------------- /docs/commands/cider_completions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: completions 5 | nav_order: 4 6 | nav_exclude: false 7 | --- 8 | 9 | ## cider completions 10 | 11 | Generate shell completions 12 | 13 | ``` 14 | cider completions [bash|zsh|fish|powershell] [flags] 15 | ``` 16 | 17 | ### Options 18 | 19 | ``` 20 | -h, --help help for completions 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | --debug Enable debug mode 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [cider](/commands/cider/) - Submit your builds to the Apple App Store in seconds 32 | 33 | -------------------------------------------------------------------------------- /docs/man/cider_docs_man.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-DOCS\-MAN" "1" "Oct 2020" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-docs\-man \- Generate man documentation for Cider. 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider docs man [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Generate man documentation for Cider. 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-h\fP, \fB\-\-help\fP[=false] 22 | help for man 23 | 24 | 25 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 26 | .PP 27 | \fB\-\-debug\fP[=false] 28 | Enable debug mode 29 | 30 | 31 | .SH SEE ALSO 32 | .PP 33 | \fBcider\-docs(1)\fP 34 | -------------------------------------------------------------------------------- /docs/man/cider_docs_md.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-DOCS\-MD" "1" "Oct 2020" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-docs\-md \- Generate Markdown documentation for Cider. 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider docs md [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Generate Markdown documentation for Cider. 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-h\fP, \fB\-\-help\fP[=false] 22 | help for md 23 | 24 | 25 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 26 | .PP 27 | \fB\-\-debug\fP[=false] 28 | Enable debug mode 29 | 30 | 31 | .SH SEE ALSO 32 | .PP 33 | \fBcider\-docs(1)\fP 34 | -------------------------------------------------------------------------------- /docs/man/cider_completions.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-COMPLETIONS" "1" "Apr 2021" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-completions \- Generate shell completions 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider completions [bash|zsh|fish|powershell] [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Generate shell completions 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-h\fP, \fB\-\-help\fP[=false] 22 | help for completions 23 | 24 | 25 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 26 | .PP 27 | \fB\-\-debug\fP[=false] 28 | Enable debug mode 29 | 30 | 31 | .SH SEE ALSO 32 | .PP 33 | \fBcider(1)\fP 34 | -------------------------------------------------------------------------------- /docs/man/cider_docs_config.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-DOCS\-CONFIG" "1" "Oct 2020" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-docs\-config \- Generate configuration file documentation for Cider. 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider docs config [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Generate configuration file documentation for Cider. 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-h\fP, \fB\-\-help\fP[=false] 22 | help for config 23 | 24 | 25 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 26 | .PP 27 | \fB\-\-debug\fP[=false] 28 | Enable debug mode 29 | 30 | 31 | .SH SEE ALSO 32 | .PP 33 | \fBcider\-docs(1)\fP 34 | -------------------------------------------------------------------------------- /docs/man/cider_docs.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-DOCS" "1" "Oct 2020" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-docs \- Generate documentation for Cider 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider docs [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Generate documentation for Cider 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-h\fP, \fB\-\-help\fP[=false] 22 | help for docs 23 | 24 | 25 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 26 | .PP 27 | \fB\-\-debug\fP[=false] 28 | Enable debug mode 29 | 30 | 31 | .SH SEE ALSO 32 | .PP 33 | \fBcider(1)\fP, \fBcider\-docs\-config(1)\fP, \fBcider\-docs\-man(1)\fP, \fBcider\-docs\-md(1)\fP 34 | -------------------------------------------------------------------------------- /docs/commands/cider.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: cider 5 | nav_order: 0 6 | nav_exclude: false 7 | --- 8 | 9 | ## cider 10 | 11 | Submit your builds to the Apple App Store in seconds 12 | 13 | ### Options 14 | 15 | ``` 16 | --debug Enable debug mode 17 | -h, --help help for cider 18 | ``` 19 | 20 | ### SEE ALSO 21 | 22 | * [cider check](/commands/cider_check/) - Checks if the configuration is valid 23 | * [cider completions](/commands/cider_completions/) - Generate shell completions 24 | * [cider init](/commands/cider_init/) - Generates a .cider.yml file 25 | * [cider release](/commands/cider_release/) - Release the selected apps in the current project 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything then restore any directories or files with extensions, so binaries are properly excluded 2 | * 3 | !*.* 4 | !*/ 5 | !/COPYING 6 | !/Gemfile 7 | !Dockerfile* 8 | 9 | # Binaries for programs and plugins 10 | *.exe 11 | *.exe~ 12 | *.dll 13 | *.so 14 | *.dylib 15 | 16 | # Test binary, built with `go test -c` 17 | *.test 18 | 19 | # Output of the go coverage tool 20 | *.out 21 | 22 | # Dependency directories (remove the comment below to include it) 23 | vendor/ 24 | 25 | # Environment files, containing secrets 26 | .env 27 | *.p8 28 | .vscode/ 29 | *.prettierrc* 30 | 31 | # System files 32 | Thumbs.db 33 | .DS_Store 34 | *.swp 35 | 36 | # Documentation 37 | docs/_site 38 | docs/bin 39 | docs/tmp 40 | 41 | # GoReleaser 42 | dist/ 43 | -------------------------------------------------------------------------------- /docs/commands/cider_check.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: check 5 | nav_order: 3 6 | nav_exclude: false 7 | --- 8 | 9 | ## cider check 10 | 11 | Checks if the configuration is valid 12 | 13 | ### Synopsis 14 | 15 | Use to validate your configuration file. 16 | 17 | ``` 18 | cider check [flags] 19 | ``` 20 | 21 | ### Examples 22 | 23 | ``` 24 | cider check 25 | ``` 26 | 27 | ### Options 28 | 29 | ``` 30 | -f, --config string Configuration file to check 31 | -h, --help help for check 32 | ``` 33 | 34 | ### Options inherited from parent commands 35 | 36 | ``` 37 | --debug Enable debug mode 38 | ``` 39 | 40 | ### SEE ALSO 41 | 42 | * [cider](/commands/cider/) - Submit your builds to the Apple App Store in seconds 43 | 44 | -------------------------------------------------------------------------------- /docs/_includes/head_custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/man/cider_check.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-CHECK" "1" "Apr 2021" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-check \- Checks if the configuration is valid 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider check [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Use to validate your configuration file. 17 | 18 | 19 | .SH OPTIONS 20 | .PP 21 | \fB\-f\fP, \fB\-\-config\fP="" 22 | Configuration file to check 23 | 24 | .PP 25 | \fB\-h\fP, \fB\-\-help\fP[=false] 26 | help for check 27 | 28 | 29 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 30 | .PP 31 | \fB\-\-debug\fP[=false] 32 | Enable debug mode 33 | 34 | 35 | .SH EXAMPLE 36 | .PP 37 | .RS 38 | 39 | .nf 40 | cider check 41 | 42 | .fi 43 | .RE 44 | 45 | 46 | .SH SEE ALSO 47 | .PP 48 | \fBcider(1)\fP 49 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'docs/**' 8 | pull_request: 9 | paths: 10 | - 'docs/**' 11 | 12 | jobs: 13 | tests: 14 | name: test docs 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Build Site 20 | run: | 21 | docker run --rm \ 22 | --env JEKYLL_UID=$(id -u) \ 23 | --env JEKYLL_GID=$(id -g) \ 24 | --volume="$PWD/docs:/srv/jekyll" \ 25 | jekyll/jekyll:latest \ 26 | jekyll build 27 | 28 | - name: Run htmltest 29 | run: | 30 | cd docs/ 31 | curl https://htmltest.wjdp.uk | bash 32 | ./bin/htmltest -c htmltest.yml 33 | -------------------------------------------------------------------------------- /internal/clicommand/import.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | -------------------------------------------------------------------------------- /docs/commands/cider_docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: docs 5 | nav_order: 0 6 | nav_exclude: true 7 | --- 8 | 9 | ## cider docs 10 | 11 | Generate documentation for Cider 12 | 13 | ``` 14 | cider docs [flags] 15 | ``` 16 | 17 | ### Options 18 | 19 | ``` 20 | -h, --help help for docs 21 | ``` 22 | 23 | ### Options inherited from parent commands 24 | 25 | ``` 26 | --debug Enable debug mode 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [cider](/commands/cider/) - Submit your builds to the Apple App Store in seconds 32 | * [cider docs config](/commands/cider_docs_config/) - Generate configuration file documentation for Cider. 33 | * [cider docs man](/commands/cider_docs_man/) - Generate man documentation for Cider. 34 | * [cider docs md](/commands/cider_docs_md/) - Generate Markdown documentation for Cider. 35 | 36 | -------------------------------------------------------------------------------- /internal/defaults/defaults_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package defaults 22 | 23 | // this package has no testable statements 24 | -------------------------------------------------------------------------------- /internal/pipeline/pipeline_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package pipeline 22 | 23 | // this package has no testable statements 24 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Cider 2 | description: Cider documentation 3 | permalink: pretty 4 | remote_theme: pmarsceill/just-the-docs 5 | logo: assets/images/icon.png 6 | 7 | plugins: 8 | - jekyll-seo-tag 9 | 10 | heading_anchors: true 11 | aux_links: 12 | GitHub: 13 | - '//github.com/cidertool/cider' 14 | aux_links_new_tab: true 15 | 16 | footer_content: 'Copyright © 2020 Aaron Sky. Distributed under the GNU General Public License version 3.0 or later.' 17 | # last_edit_timestamp show or hide edit time - page must have `last_modified_date` defined in the frontmatter 18 | last_edit_timestamp: true 19 | # last_edit_time_format uses ruby's time format: https://ruby-doc.org/stdlib-2.7.0/libdoc/time/rdoc/Time.html 20 | last_edit_time_format: '%b %e %Y at %I:%M %p' 21 | 22 | exclude: 23 | - configuration-footer.md 24 | - man/* 25 | - bin/* 26 | - htmltest.yml 27 | -------------------------------------------------------------------------------- /tools/gendoc/doc.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package main handles the automatic generation of documentation for the command line interface 22 | // and configuration format. 23 | package main 24 | -------------------------------------------------------------------------------- /docs/commands/cider_init.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: init 5 | nav_order: 1 6 | nav_exclude: false 7 | --- 8 | 9 | ## cider init 10 | 11 | Generates a .cider.yml file 12 | 13 | ### Synopsis 14 | 15 | Use to initialize a new Cider project. This will create a new configuration file 16 | in the current directory that should be checked into source control. 17 | 18 | ``` 19 | cider init [flags] 20 | ``` 21 | 22 | ### Examples 23 | 24 | ``` 25 | cider init 26 | ``` 27 | 28 | ### Options 29 | 30 | ``` 31 | -f, --config string Path of configuration file to create (default ".cider.yml") 32 | -h, --help help for init 33 | -y, --skip-prompt Skips onboarding prompts. This can result in an overwritten configuration file 34 | ``` 35 | 36 | ### Options inherited from parent commands 37 | 38 | ``` 39 | --debug Enable debug mode 40 | ``` 41 | 42 | ### SEE ALSO 43 | 44 | * [cider](/commands/cider/) - Submit your builds to the Apple App Store in seconds 45 | 46 | -------------------------------------------------------------------------------- /internal/client/project.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package client 22 | 23 | import ( 24 | "github.com/cidertool/cider/pkg/config" 25 | ) 26 | 27 | func (c *ascClient) Project() (project *config.Project, err error) { 28 | return project, err 29 | } 30 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | title: Home 4 | nav_order: 1 5 | description: 'Cider is a command-line application that makes it easy to submit your Apple App Store apps for review.' 6 | permalink: / 7 | --- 8 | 9 | Cider logo 10 | 11 | Cider is a tool managing the entire release process of an iOS, macOS or tvOS application, supported by official Apple APIs. It takes the builds you've uploaded to App Store Connect, updates their metadata, and submits them for review automatically using an expressive YAML configuration. Unlike Xcode or altool, Cider is designed to be useful on Linux and Windows, in addition to macOS. 12 | 13 | Cider is not a replacement for `altool`, the official command-line interface for uploading, validating, and notarizing archives to Apple. It's instead designed to complement `altool`, and by extension `xcodebuild`. With Cider, your pipeline can build, test, upload, and now release your app without any required manual action. 14 | -------------------------------------------------------------------------------- /docs/man/cider_init.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-INIT" "1" "Apr 2021" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-init \- Generates a .cider.yml file 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider init [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Use to initialize a new Cider project. This will create a new configuration file 17 | in the current directory that should be checked into source control. 18 | 19 | 20 | .SH OPTIONS 21 | .PP 22 | \fB\-f\fP, \fB\-\-config\fP=".cider.yml" 23 | Path of configuration file to create 24 | 25 | .PP 26 | \fB\-h\fP, \fB\-\-help\fP[=false] 27 | help for init 28 | 29 | .PP 30 | \fB\-y\fP, \fB\-\-skip\-prompt\fP[=false] 31 | Skips onboarding prompts. This can result in an overwritten configuration file 32 | 33 | 34 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 35 | .PP 36 | \fB\-\-debug\fP[=false] 37 | Enable debug mode 38 | 39 | 40 | .SH EXAMPLE 41 | .PP 42 | .RS 43 | 44 | .nf 45 | cider init 46 | 47 | .fi 48 | .RE 49 | 50 | 51 | .SH SEE ALSO 52 | .PP 53 | \fBcider(1)\fP 54 | -------------------------------------------------------------------------------- /pkg/config/doc.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | /* 22 | Package config contains types and helpers available to configure a Cider project. 23 | 24 | You can customize your project using a `.cider.yml` file either created from scratch 25 | or using [`cider init`](./commands/cider_init.md). 26 | */ 27 | package config 28 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | nav_order: 2 4 | --- 5 | 6 | # Installation 7 | 8 | Cider can be installed from a variety of sources. 9 | 10 | ### Homebrew (macOS/Linux) 11 | 12 | ```shell 13 | brew install cidertool/tap/cider 14 | ``` 15 | 16 | ### Scoop (Windows) 17 | 18 | ```shell 19 | scoop bucket add cider https://github.com/cidertool/scoop-bucket.git 20 | scoop install cider 21 | ``` 22 | 23 | ### Docker 24 | 25 | ```shell 26 | docker run --rm \ 27 | --volume $PWD:/app \ 28 | --workdir /app \ 29 | --env ASC_KEY_ID \ 30 | --env ASC_ISSUER_ID \ 31 | --env ASC_PRIVATE_KEY \ 32 | cidertool/cider release 33 | ``` 34 | 35 | ### Pre-built Binary 36 | 37 | Download the specific version for your platform on Cider's [releases page](https://github.com/cidertool/cider/releases). 38 | 39 | ### Compile from Source 40 | 41 | ```shell 42 | git clone git@github.com:cidertool/cider.git 43 | cd cider 44 | go build -o cider ./cmd/cider 45 | ``` 46 | 47 | From there, you can use your locally-built Cider binary for whatever purposes you need. Cider requires **Go 1.16 or higher** to be installed. 48 | 49 | ## Missing your favorite? 50 | 51 | Please [file an issue](https://github.com/cidertool/cider/issues/new)! 52 | -------------------------------------------------------------------------------- /internal/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package middleware contains middleware for handling error and logging propagation 22 | package middleware 23 | 24 | import "github.com/cidertool/cider/pkg/context" 25 | 26 | // Action is a function that takes a context and returns an error. 27 | // It is is used on Pipers, Defaulters and Publishers, although they are not 28 | // aware of this generalization. 29 | type Action func(ctx *context.Context) error 30 | -------------------------------------------------------------------------------- /internal/clicommand/error_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "errors" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | var errTestError = errors.New("TEST") 31 | 32 | func TestErrors(t *testing.T) { 33 | t.Parallel() 34 | 35 | err := wrapError(errTestError, "TEST") 36 | assert.Error(t, err) 37 | assert.Equal(t, "TEST", err.Error()) 38 | assert.Equal(t, 1, err.code) 39 | assert.Equal(t, "TEST", err.details) 40 | } 41 | -------------------------------------------------------------------------------- /internal/closer/closer.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package closer is a helper for closing io.Closers. 22 | package closer 23 | 24 | import ( 25 | "io" 26 | "log" 27 | ) 28 | 29 | type closeErrFunc func(error) 30 | 31 | // nolint:gochecknoglobals 32 | var onCloseErr closeErrFunc = func(err error) { log.Fatal(err) } 33 | 34 | // Close closes an io.Closer and handles the possible Close error. 35 | func Close(c io.Closer) { 36 | if err := c.Close(); err != nil { 37 | onCloseErr(err) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/clicommand/error.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | type exitError struct { 24 | err error 25 | code int 26 | details string 27 | } 28 | 29 | func wrapErrorWithCode(err error, code int, details string) *exitError { 30 | return &exitError{ 31 | err: err, 32 | code: code, 33 | details: details, 34 | } 35 | } 36 | 37 | func wrapError(err error, log string) *exitError { 38 | return wrapErrorWithCode(err, 1, log) 39 | } 40 | 41 | func (e *exitError) Error() string { 42 | return e.err.Error() 43 | } 44 | -------------------------------------------------------------------------------- /internal/clicommand/init_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "path/filepath" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | func TestInitCmd(t *testing.T) { 31 | t.Parallel() 32 | 33 | var folder = t.TempDir() 34 | 35 | var noDebug bool 36 | 37 | var cmd = newInitCmd(&noDebug).cmd 38 | 39 | var path = filepath.Join(folder, "foo.yaml") 40 | 41 | cmd.SetArgs([]string{"-f", path, "--skip-prompt"}) 42 | assert.NoError(t, cmd.Execute()) 43 | assert.FileExists(t, path) 44 | } 45 | -------------------------------------------------------------------------------- /internal/middleware/logging_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package middleware 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/pkg/config" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestLogging(t *testing.T) { 32 | t.Parallel() 33 | 34 | ctx := context.New(config.Project{}) 35 | 36 | wrapped := Logging("TEST", func(ctx *context.Context) error { 37 | return nil 38 | }, DefaultInitialPadding) 39 | 40 | err := wrapped(ctx) 41 | assert.NoError(t, err) 42 | } 43 | -------------------------------------------------------------------------------- /internal/clicommand/root_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestRootCmd(t *testing.T) { 30 | t.Parallel() 31 | 32 | exit := func(code int) { 33 | assert.Equal(t, 0, code) 34 | } 35 | 36 | Execute("TEST", exit, []string{"help", "--debug"}) 37 | } 38 | 39 | func TestRootCmd_Error(t *testing.T) { 40 | t.Parallel() 41 | 42 | exit := func(code int) { 43 | assert.NotEqual(t, 0, code) 44 | } 45 | 46 | Execute("TEST", exit, []string{"check"}) 47 | } 48 | -------------------------------------------------------------------------------- /internal/clicommand/clicommand.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package clicommand declares the command line interface for Cider. 22 | package clicommand 23 | 24 | import ( 25 | "os" 26 | 27 | "github.com/cidertool/cider/internal/log" 28 | "github.com/fatih/color" 29 | ) 30 | 31 | func newLogger(debugFlagValue *bool) *log.Log { 32 | logger := log.New() 33 | 34 | if os.Getenv("CI") != "" && color.NoColor { 35 | logger.SetColorMode(false) 36 | } 37 | 38 | if debugFlagValue != nil { 39 | logger.SetDebug(*debugFlagValue) 40 | logger.Debug("debug logs enabled") 41 | } 42 | 43 | return logger 44 | } 45 | -------------------------------------------------------------------------------- /internal/middleware/error.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package middleware 22 | 23 | import ( 24 | "github.com/cidertool/cider/internal/pipe" 25 | "github.com/cidertool/cider/pkg/context" 26 | ) 27 | 28 | // ErrHandler handles an action error, ignoring and logging pipe skipped 29 | // errors. 30 | func ErrHandler(action Action) Action { 31 | return func(ctx *context.Context) error { 32 | var err = action(ctx) 33 | if err == nil { 34 | return nil 35 | } 36 | 37 | if pipe.IsSkip(err) { 38 | ctx.Log.WithError(err).Warn("pipe skipped") 39 | 40 | return nil 41 | } 42 | 43 | return err 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /internal/log/log_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package log 22 | 23 | import ( 24 | "testing" 25 | 26 | alog "github.com/apex/log" 27 | "github.com/fatih/color" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestLog(t *testing.T) { 32 | t.Parallel() 33 | 34 | log := New() 35 | 36 | log.SetPadding(4) 37 | 38 | log.SetColorMode(false) 39 | assert.False(t, color.NoColor) 40 | log.SetColorMode(true) 41 | assert.True(t, color.NoColor) 42 | 43 | log.SetDebug(true) 44 | assert.Equal(t, log.Level, alog.DebugLevel) 45 | log.SetDebug(false) 46 | assert.Equal(t, log.Level, alog.InfoLevel) 47 | } 48 | -------------------------------------------------------------------------------- /internal/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package defaults is used by pipes to provide default values to a context configuration 22 | package defaults 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/cidertool/cider/pkg/context" 28 | ) 29 | 30 | // Defaulter can be implemented by a Piper to set default values for its 31 | // configuration. 32 | type Defaulter interface { 33 | fmt.Stringer 34 | 35 | // Default sets the configuration defaults 36 | Default(ctx *context.Context) error 37 | } 38 | 39 | // Defaulters is the list of defaulters 40 | // nolint: gochecknoglobals 41 | var Defaulters = []Defaulter{} 42 | -------------------------------------------------------------------------------- /pkg/context/interrupt_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package context 22 | 23 | import ( 24 | "context" 25 | "errors" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | var errTestError = errors.New("TEST") 32 | 33 | func TestInterruptOK(t *testing.T) { 34 | t.Parallel() 35 | 36 | assert.NoError(t, NewInterrupt().Run(context.Background(), func() error { 37 | return nil 38 | })) 39 | } 40 | 41 | func TestInterruptErrors(t *testing.T) { 42 | t.Parallel() 43 | 44 | assert.EqualError(t, NewInterrupt().Run(context.Background(), func() error { 45 | return errTestError 46 | }), errTestError.Error()) 47 | } 48 | -------------------------------------------------------------------------------- /internal/pipe/pipe_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package pipe 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestPipeSkip(t *testing.T) { 30 | t.Parallel() 31 | 32 | skip := Skip("TEST") 33 | 34 | var err error = ErrSkip{reason: "TEST"} 35 | 36 | assert.Error(t, skip) 37 | assert.Error(t, err) 38 | assert.True(t, IsSkip(err)) 39 | assert.Equal(t, err, skip) 40 | assert.EqualError(t, err, skip.Error()) 41 | } 42 | 43 | func TestErrMissingApp(t *testing.T) { 44 | t.Parallel() 45 | 46 | err := ErrMissingApp{Name: "TEST"} 47 | assert.Error(t, err) 48 | assert.EqualError(t, err, "no app defined in configuration matching the name TEST") 49 | } 50 | -------------------------------------------------------------------------------- /internal/clicommand/check_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | "testing" 27 | 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestCheckCmd(t *testing.T) { 33 | t.Parallel() 34 | 35 | var noDebug bool 36 | 37 | var cmd = newCheckCmd(&noDebug) 38 | 39 | var path = filepath.Join(t.TempDir(), "foo.yaml") 40 | 41 | var proj config.Project 42 | 43 | s, err := proj.String() 44 | assert.NoError(t, err) 45 | err = os.WriteFile(path, []byte(s), 0600) 46 | assert.NoError(t, err) 47 | 48 | err = cmd.cmd.Execute() 49 | assert.Error(t, err) 50 | 51 | cmd.config = path 52 | 53 | err = cmd.cmd.Execute() 54 | assert.NoError(t, err) 55 | } 56 | -------------------------------------------------------------------------------- /pkg/context/credentials_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package context 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestNewCredentials(t *testing.T) { 30 | t.Parallel() 31 | 32 | privateKey := []byte(` 33 | -----BEGIN PRIVATE KEY----- 34 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgTHOfkv1Dj2Yp8hyT 35 | cqY/BRRnYsoLzQoT04EW9d57dd+hRANCAAReUxyqGRpXQI2Fe833j7gQTnZ002VO 36 | FSEpv2QUFs0+dXz04SWVmmzFErM0/iQyCYom0V1IMOWgV/8xvFN6+AeX 37 | -----END PRIVATE KEY----- 38 | `) 39 | cred, err := NewCredentials("kid", "iss", privateKey) 40 | assert.NoError(t, err) 41 | assert.NotNil(t, cred) 42 | assert.NotNil(t, cred.Client()) 43 | 44 | _, err = NewCredentials("kid", "iss", []byte("nothing")) 45 | assert.Error(t, err) 46 | } 47 | -------------------------------------------------------------------------------- /internal/closer/closer_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package closer 22 | 23 | import ( 24 | "errors" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/assert" 28 | ) 29 | 30 | var errAlreadyClosed = errors.New("already closed") 31 | 32 | func TestClose(t *testing.T) { 33 | t.Parallel() 34 | 35 | var expectingErr bool 36 | 37 | onCloseErr = func(err error) { 38 | if !expectingErr { 39 | assert.FailNow(t, err.Error()) 40 | } 41 | } 42 | 43 | r := &resource{name: "a"} 44 | Close(r) 45 | 46 | r = &resource{name: "b"} 47 | Close(r) 48 | 49 | expectingErr = true 50 | 51 | Close(r) 52 | } 53 | 54 | type resource struct { 55 | name string 56 | closed bool 57 | } 58 | 59 | func (r *resource) Close() error { 60 | if r.closed { 61 | return errAlreadyClosed 62 | } 63 | 64 | r.closed = true 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/context/credentials.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package context 22 | 23 | import ( 24 | "fmt" 25 | "net/http" 26 | "time" 27 | 28 | "github.com/cidertool/asc-go/asc" 29 | ) 30 | 31 | const twentyMinuteTokenLifetime = time.Minute * 20 32 | 33 | // Credentials stores credentials used by clients. 34 | type Credentials interface { 35 | Client() *http.Client 36 | } 37 | 38 | type credentials struct { 39 | *asc.AuthTransport 40 | } 41 | 42 | // NewCredentials returns a new store object for App Store Connect credentials. 43 | func NewCredentials(keyID, issuerID string, privateKey []byte) (Credentials, error) { 44 | token, err := asc.NewTokenConfig(keyID, issuerID, twentyMinuteTokenLifetime, privateKey) 45 | if err != nil { 46 | err = fmt.Errorf("failed to authorize with App Store Connect: %w", err) 47 | } 48 | 49 | return credentials{token}, err 50 | } 51 | -------------------------------------------------------------------------------- /internal/clicommand/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "errors" 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/cidertool/cider/pkg/config" 29 | ) 30 | 31 | // ErrConfigNotFound happens if a config file could not be found at any of the default locations. 32 | var ErrConfigNotFound = errors.New("config file not found at any default path") 33 | 34 | func loadConfig(path string, wd string) (config.Project, error) { 35 | if path != "" { 36 | return config.Load(path) 37 | } 38 | 39 | for _, f := range [4]string{ 40 | ".cider.yml", 41 | ".cider.yaml", 42 | "cider.yml", 43 | "cider.yaml", 44 | } { 45 | proj, err := config.Load(filepath.Join(wd, f)) 46 | if err != nil && os.IsNotExist(err) { 47 | continue 48 | } 49 | 50 | return proj, err 51 | } 52 | 53 | return config.Project{}, ErrConfigNotFound 54 | } 55 | -------------------------------------------------------------------------------- /tools/gendoc/man.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | 27 | "github.com/apex/log" 28 | "github.com/cidertool/cider/internal/clicommand" 29 | "github.com/spf13/cobra" 30 | "github.com/spf13/cobra/doc" 31 | ) 32 | 33 | func runDocsManCmd(cmd *cobra.Command, args []string) error { 34 | var path string 35 | if len(args) == 0 { 36 | path = defaultDocsPath 37 | } else { 38 | path = args[0] 39 | } 40 | 41 | path = filepath.Join(path, "man") 42 | 43 | log.WithField("path", path).Info("generating man documentation") 44 | 45 | err := doc.GenManTreeFromOpts(clicommand.NewRoot("dev", os.Exit).Cmd, doc.GenManTreeOptions{ 46 | Path: path, 47 | }) 48 | if err != nil { 49 | log.Error("generation failed") 50 | } else { 51 | log.Info("generation completed successfully") 52 | } 53 | 54 | return err 55 | } 56 | -------------------------------------------------------------------------------- /internal/clicommand/completions_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestCompletionsCmd(t *testing.T) { 30 | t.Parallel() 31 | 32 | cmd := newCompletionsCmd() 33 | 34 | cmd.cmd.SetArgs([]string{}) 35 | err := cmd.cmd.Execute() 36 | assert.Error(t, err) 37 | 38 | cmd.cmd.SetArgs([]string{"bash"}) 39 | err = cmd.cmd.Execute() 40 | assert.NoError(t, err) 41 | 42 | cmd.cmd.SetArgs([]string{"zsh"}) 43 | err = cmd.cmd.Execute() 44 | assert.NoError(t, err) 45 | 46 | cmd.cmd.SetArgs([]string{"fish"}) 47 | err = cmd.cmd.Execute() 48 | assert.NoError(t, err) 49 | 50 | cmd.cmd.SetArgs([]string{"powershell"}) 51 | err = cmd.cmd.Execute() 52 | assert.NoError(t, err) 53 | 54 | cmd.cmd.SetArgs([]string{"oil"}) 55 | err = cmd.cmd.Execute() 56 | assert.Error(t, err) 57 | } 58 | -------------------------------------------------------------------------------- /internal/git/errors_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package git 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestErrDirtyMessage(t *testing.T) { 30 | t.Parallel() 31 | 32 | err := ErrDirty{"TEST"} 33 | expected := "git is currently in a dirty state, please check in your pipeline what can be changing the following files:\nTEST" 34 | assert.Equal(t, expected, err.Error()) 35 | } 36 | 37 | func TestErrWrongRefMessage(t *testing.T) { 38 | t.Parallel() 39 | 40 | err := ErrWrongRef{"TEST", "TEST"} 41 | expected := "git tag TEST was not made against commit TEST" 42 | assert.Equal(t, expected, err.Error()) 43 | } 44 | 45 | func TestErrNotRepositoryMessage(t *testing.T) { 46 | t.Parallel() 47 | 48 | err := ErrNotRepository{"TEST"} 49 | expected := "the directory at TEST is not a git repository" 50 | assert.Equal(t, expected, err.Error()) 51 | } 52 | -------------------------------------------------------------------------------- /internal/pipe/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package defaults runs all defaulter pipelines 22 | package defaults 23 | 24 | import ( 25 | "github.com/cidertool/cider/internal/defaults" 26 | "github.com/cidertool/cider/internal/middleware" 27 | "github.com/cidertool/cider/pkg/context" 28 | ) 29 | 30 | // Pipe that sets the defaults. 31 | type Pipe struct { 32 | defaulters []defaults.Defaulter 33 | } 34 | 35 | func (Pipe) String() string { 36 | return "setting defaults" 37 | } 38 | 39 | // Run the pipe. 40 | func (p Pipe) Run(ctx *context.Context) error { 41 | if len(p.defaulters) == 0 { 42 | p.defaulters = defaults.Defaulters 43 | } 44 | 45 | for _, defaulter := range p.defaulters { 46 | if err := middleware.Logging( 47 | defaulter.String(), 48 | middleware.ErrHandler(defaulter.Default), 49 | middleware.ExtraPadding, 50 | )(ctx); err != nil { 51 | return err 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /internal/pipe/semver/semver.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package semver is a pipe that parses a version string into semver components 22 | package semver 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/Masterminds/semver/v3" 28 | "github.com/cidertool/cider/pkg/context" 29 | ) 30 | 31 | // Pipe is a global hook pipe. 32 | type Pipe struct{} 33 | 34 | // String is the name of this pipe. 35 | func (Pipe) String() string { 36 | return "parsing version" 37 | } 38 | 39 | // Run executes the hooks. 40 | func (p Pipe) Run(ctx *context.Context) error { 41 | sv, err := semver.NewVersion(ctx.Version) 42 | if err != nil { 43 | return fmt.Errorf("failed to parse tag %s as semver: %w", ctx.Version, err) 44 | } 45 | 46 | ctx.Semver = context.Semver{ 47 | Major: sv.Major(), 48 | Minor: sv.Minor(), 49 | Patch: sv.Patch(), 50 | Prerelease: sv.Prerelease(), 51 | RawVersion: sv.Original(), 52 | } 53 | 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /internal/pipe/defaults/defaults_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package defaults 22 | 23 | import ( 24 | "errors" 25 | "testing" 26 | 27 | "github.com/cidertool/cider/internal/defaults" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | var errTestError = errors.New("TEST") 34 | 35 | func TestDefaults(t *testing.T) { 36 | t.Parallel() 37 | 38 | ctx := context.New(config.Project{}) 39 | 40 | pipe := Pipe{} 41 | 42 | var err error 43 | 44 | assert.Equal(t, "setting defaults", pipe.String()) 45 | 46 | err = pipe.Run(ctx) 47 | assert.NoError(t, err) 48 | 49 | pipe.defaulters = []defaults.Defaulter{ 50 | mockDefaulter{}, 51 | } 52 | err = pipe.Run(ctx) 53 | assert.Error(t, err) 54 | } 55 | 56 | type mockDefaulter struct{} 57 | 58 | func (d mockDefaulter) String() string { 59 | return "" 60 | } 61 | 62 | func (d mockDefaulter) Default(ctx *context.Context) error { 63 | return errTestError 64 | } 65 | -------------------------------------------------------------------------------- /internal/pipeline/pipeline.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package pipeline stores the top-level pipeline and Piper interface used by most pipes 22 | package pipeline 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/cidertool/cider/internal/pipe/defaults" 28 | "github.com/cidertool/cider/internal/pipe/env" 29 | "github.com/cidertool/cider/internal/pipe/git" 30 | "github.com/cidertool/cider/internal/pipe/publish" 31 | "github.com/cidertool/cider/internal/pipe/semver" 32 | "github.com/cidertool/cider/internal/pipe/template" 33 | "github.com/cidertool/cider/pkg/context" 34 | ) 35 | 36 | // Piper defines a pipe, which can be part of a pipeline (a serie of pipes). 37 | type Piper interface { 38 | fmt.Stringer 39 | 40 | // Run the pipe 41 | Run(ctx *context.Context) error 42 | } 43 | 44 | // Pipeline contains all pipe implementations in order 45 | // nolint: gochecknoglobals 46 | var Pipeline = []Piper{ 47 | env.Pipe{}, 48 | git.Pipe{}, 49 | semver.Pipe{}, 50 | template.Pipe{}, 51 | defaults.Pipe{}, 52 | publish.Pipe{}, 53 | } 54 | -------------------------------------------------------------------------------- /internal/template/template_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package template 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/pkg/config" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestTemplate(t *testing.T) { 32 | t.Parallel() 33 | 34 | tmpl, err := New(context.New(config.Project{})). 35 | WithEnv(map[string]string{ 36 | "DOG": "HAPPY", 37 | "CAT": "GRUMPY", 38 | }). 39 | WithShellEnv("HORSE=EVIL", "CAT=SPOOKY"). 40 | WithFields(Fields{ 41 | "customKey": 0, 42 | }). 43 | Apply(`My {{ .env.CAT }} cat fought {{ .customKey }} {{ .env.HORSE }} horses.`) 44 | assert.NoError(t, err) 45 | assert.Equal(t, "My SPOOKY cat fought 0 EVIL horses.", tmpl) 46 | } 47 | 48 | func TestInvalidTemplate(t *testing.T) { 49 | t.Parallel() 50 | 51 | _, err := New(context.New(config.Project{})).Apply(`{{ .timestamp`) 52 | assert.Error(t, err) 53 | } 54 | 55 | func TestEmptyTemplate(t *testing.T) { 56 | t.Parallel() 57 | 58 | tmpl, err := New(context.New(config.Project{})).Apply("") 59 | assert.NoError(t, err) 60 | assert.Empty(t, tmpl) 61 | } 62 | -------------------------------------------------------------------------------- /cmd/cider/main.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | 27 | "github.com/cidertool/cider/internal/clicommand" 28 | ) 29 | 30 | const licenseDisclaimer = ` 31 | Copyright (C) 2020 Aaron Sky 32 | License GPLv3+: GNU GPL version 3 or later 33 | 34 | This is free software; you are free to change and redistribute it. 35 | There is NO WARRANTY, to the extent permitted by law.` 36 | 37 | // nolint: gochecknoglobals 38 | var ( 39 | version = "dev" 40 | commit = "" 41 | date = "" 42 | builtBy = "" 43 | ) 44 | 45 | func main() { 46 | clicommand.Execute( 47 | buildVersion(version, commit, date, builtBy), 48 | os.Exit, 49 | os.Args[1:], 50 | ) 51 | } 52 | 53 | func buildVersion(version, commit, date, builtBy string) string { 54 | var result = version 55 | 56 | if commit != "" { 57 | result = fmt.Sprintf("%s\ncommit: %s", result, commit) 58 | } 59 | 60 | if date != "" { 61 | result = fmt.Sprintf("%s\nbuilt at: %s", result, date) 62 | } 63 | 64 | if builtBy != "" { 65 | result = fmt.Sprintf("%s\nbuilt by: %s", result, builtBy) 66 | } 67 | 68 | return result + licenseDisclaimer 69 | } 70 | -------------------------------------------------------------------------------- /internal/middleware/error_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package middleware 22 | 23 | import ( 24 | "errors" 25 | "testing" 26 | 27 | "github.com/cidertool/cider/internal/pipe" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | var errTestError = errors.New("TEST") 34 | 35 | func TestErrHandler_WrapsError(t *testing.T) { 36 | t.Parallel() 37 | 38 | ctx := context.New(config.Project{}) 39 | 40 | wrapped := ErrHandler(func(ctx *context.Context) error { 41 | return errTestError 42 | }) 43 | 44 | err := wrapped(ctx) 45 | assert.Error(t, err) 46 | } 47 | 48 | func TestErrHandler_IgnoresNoError(t *testing.T) { 49 | t.Parallel() 50 | 51 | ctx := context.New(config.Project{}) 52 | 53 | wrapped := ErrHandler(func(ctx *context.Context) error { 54 | return nil 55 | }) 56 | 57 | err := wrapped(ctx) 58 | assert.NoError(t, err) 59 | } 60 | 61 | func TestErrHandler_HandlesSkip(t *testing.T) { 62 | t.Parallel() 63 | 64 | ctx := context.New(config.Project{}) 65 | 66 | wrapped := ErrHandler(func(ctx *context.Context) error { 67 | return pipe.Skip("TEST") 68 | }) 69 | 70 | err := wrapped(ctx) 71 | assert.NoError(t, err) 72 | } 73 | -------------------------------------------------------------------------------- /internal/pipe/semver/semver_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package semver 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/pkg/config" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestSemver(t *testing.T) { 32 | t.Parallel() 33 | 34 | ctx := context.New(config.Project{}) 35 | pipe := Pipe{} 36 | 37 | var err error 38 | 39 | assert.Equal(t, "parsing version", pipe.String()) 40 | 41 | err = pipe.Run(ctx) 42 | assert.Error(t, err) 43 | assert.Empty(t, ctx.Semver) 44 | 45 | ctx.Version = "1.0.1" 46 | err = pipe.Run(ctx) 47 | assert.NoError(t, err) 48 | assert.Equal(t, context.Semver{ 49 | Major: 1, 50 | Minor: 0, 51 | Patch: 1, 52 | RawVersion: "1.0.1", 53 | }, ctx.Semver) 54 | 55 | ctx.Version = "1.1.1-patch90" 56 | err = pipe.Run(ctx) 57 | assert.NoError(t, err) 58 | assert.Equal(t, context.Semver{ 59 | Major: 1, 60 | Minor: 1, 61 | Patch: 1, 62 | RawVersion: "1.1.1-patch90", 63 | Prerelease: "patch90", 64 | }, ctx.Semver) 65 | 66 | ctx.Version = "aa.ee.bb" 67 | ctx.Semver = context.Semver{} 68 | err = pipe.Run(ctx) 69 | assert.Error(t, err) 70 | assert.Empty(t, ctx.Semver) 71 | } 72 | -------------------------------------------------------------------------------- /internal/clicommand/config_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | "testing" 27 | 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestConfig_Happy_CustomPath(t *testing.T) { 33 | t.Parallel() 34 | 35 | var path = filepath.Join(t.TempDir(), "foo.yaml") 36 | 37 | var proj config.Project 38 | 39 | s, err := proj.String() 40 | assert.NoError(t, err) 41 | err = os.WriteFile(path, []byte(s), 0600) 42 | assert.NoError(t, err) 43 | cfg, err := loadConfig(path, "") 44 | assert.NoError(t, err) 45 | assert.Empty(t, cfg) 46 | } 47 | 48 | func TestConfig_Happy_DefaultPath(t *testing.T) { 49 | t.Parallel() 50 | 51 | var folder = t.TempDir() 52 | 53 | var path = filepath.Join(folder, "cider.yaml") 54 | 55 | var proj config.Project 56 | 57 | s, err := proj.String() 58 | assert.NoError(t, err) 59 | err = os.WriteFile(path, []byte(s), 0600) 60 | assert.NoError(t, err) 61 | cfg, err := loadConfig("", folder) 62 | assert.NoError(t, err) 63 | assert.Empty(t, cfg) 64 | } 65 | 66 | func TestConfig_Err_DoesntExist(t *testing.T) { 67 | t.Parallel() 68 | 69 | cfg, err := loadConfig("", t.TempDir()) 70 | assert.Error(t, err) 71 | assert.Empty(t, cfg) 72 | } 73 | -------------------------------------------------------------------------------- /internal/clicommand/completions.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "errors" 25 | "os" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | // ErrUnsupportedShell happens when a shell that Cobra does not support is 31 | // passed for the completions command. 32 | var ErrUnsupportedShell = errors.New("shell for completions is unsupported") 33 | 34 | type completionsCmd struct { 35 | cmd *cobra.Command 36 | } 37 | 38 | func newCompletionsCmd() *completionsCmd { 39 | var root = &completionsCmd{} 40 | 41 | var cmd = &cobra.Command{ 42 | Use: "completions [bash|zsh|fish|powershell]", 43 | Short: "Generate shell completions", 44 | ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, 45 | Args: cobra.ExactValidArgs(1), 46 | RunE: func(cmd *cobra.Command, args []string) error { 47 | switch args[0] { 48 | case "bash": 49 | return cmd.Root().GenBashCompletion(os.Stdout) 50 | case "zsh": 51 | return cmd.Root().GenZshCompletion(os.Stdout) 52 | case "fish": 53 | return cmd.Root().GenFishCompletion(os.Stdout, true) 54 | case "powershell": 55 | return cmd.Root().GenPowerShellCompletion(os.Stdout) 56 | } 57 | 58 | return ErrUnsupportedShell 59 | }, 60 | } 61 | 62 | root.cmd = cmd 63 | 64 | return root 65 | } 66 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: FAQ 4 | nav_order: 6 5 | --- 6 | 7 | # Frequently Asked Questions 8 | 9 | ## Can I use Cider to upload builds? 10 | 11 | Cider is not designed to replace `altool`, the recommended Apple-provided tool for validating and uploading archives to Xcode. That tool has an exceptionally well-documented manpage and comes with stability guarantees from the platform-holder. Please use Cider to submit your builds once they've been uploaded and completed processing. 12 | 13 | ## Can I use an Apple ID to authenticate? 14 | 15 | No. Cider is built on the backbone of Apple's official App Store Connect API, and a valid [JSON Web Token](https://tools.ietf.org/html/rfc7519) generated from an issuer ID, key ID and private key are essential components in generating a compliant token. Create a key for a user on your team with the **App Manager** role by [following Apple's documentation](https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api) and use those credentials in your CI environment. If you ever need to revoke these credentials, you can do so by following [these instructions](https://developer.apple.com/documentation/appstoreconnectapi/revoking_api_keys). 16 | 17 | ## How is this different from Fastlane/Spaceship? 18 | 19 | Spaceship, and by extension Fastlane, are designed to be customizable for a variety of features and functions. You can do largely do anything, but that comes with the inherent overhead that "anything" entails. Spaceship has served Fastlane and the broader Apple development community well for years, but the investment cost can't be denied. Additionally, Spaceship was originally designed around Apple's private iTunes Connect API, and its migration to the official App Store Connect API has been slow. Cider has been designed with simplicity and portability in mind, which has required limiting its scope from "anything". In addition, Cider has been built around the App Store Connect API from the very beginning. What you get is a tool that is useful out-of-the-box, with simple configuration options, that runs quickly anywhere. 20 | -------------------------------------------------------------------------------- /internal/middleware/logging.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package middleware 22 | 23 | import ( 24 | "github.com/cidertool/cider/pkg/context" 25 | "github.com/fatih/color" 26 | ) 27 | 28 | // Padding is a logging initial padding. 29 | type Padding int 30 | 31 | // DefaultInitialPadding is the default padding in the log library. 32 | const DefaultInitialPadding Padding = 3 33 | 34 | // ExtraPadding is the double of the DefaultInitialPadding. 35 | const ExtraPadding Padding = DefaultInitialPadding * 2 36 | 37 | // Logging pretty prints the given action and its title. 38 | // You can have different padding levels by providing different initial 39 | // paddings. The middleware will print the title in the given padding and the 40 | // action logs in padding+default padding. 41 | // The default padding in the log library is 3. 42 | // The middleware always resets to the default padding. 43 | func Logging(title string, next Action, padding Padding) Action { 44 | return func(ctx *context.Context) error { 45 | if ctx.Log == nil { 46 | return next(ctx) 47 | } 48 | 49 | defer func() { 50 | ctx.Log.SetPadding(int(DefaultInitialPadding)) 51 | }() 52 | 53 | ctx.Log.SetPadding(int(padding)) 54 | 55 | ctx.Log.Info(color.New(color.Bold).Sprint(title)) 56 | 57 | ctx.Log.SetPadding(int(padding + DefaultInitialPadding)) 58 | 59 | return next(ctx) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cmd/cider/main_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestVersion(t *testing.T) { 30 | t.Parallel() 31 | 32 | testCases := []struct { 33 | name string 34 | version, commit, date, builtBy string 35 | out string 36 | }{ 37 | { 38 | name: "all empty", 39 | out: "", 40 | }, 41 | { 42 | name: "complete", 43 | version: "1.2.3", 44 | date: "12/12/12", 45 | commit: "aaaa", 46 | builtBy: "me", 47 | out: "1.2.3\ncommit: aaaa\nbuilt at: 12/12/12\nbuilt by: me", 48 | }, 49 | { 50 | name: "only version", 51 | version: "1.2.3", 52 | out: "1.2.3", 53 | }, 54 | { 55 | name: "version and date", 56 | version: "1.2.3", 57 | date: "12/12/12", 58 | out: "1.2.3\nbuilt at: 12/12/12", 59 | }, 60 | { 61 | name: "version, date, built by", 62 | version: "1.2.3", 63 | date: "12/12/12", 64 | builtBy: "me", 65 | out: "1.2.3\nbuilt at: 12/12/12\nbuilt by: me", 66 | }, 67 | } 68 | 69 | for _, tt := range testCases { 70 | tt := tt 71 | 72 | t.Run(tt.name, func(t *testing.T) { 73 | t.Parallel() 74 | 75 | assert.Equal(t, tt.out+licenseDisclaimer, buildVersion(tt.version, tt.commit, tt.date, tt.builtBy)) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /internal/shell/shell_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package shell 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/pkg/config" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestExec(t *testing.T) { 32 | t.Parallel() 33 | 34 | sh := New(context.New(config.Project{})) 35 | cmd := sh.NewCommand("echo", "dogs") 36 | ps, err := sh.Exec(cmd) 37 | assert.NoError(t, err) 38 | assert.Equal(t, "dogs", ps.Stdout) 39 | } 40 | 41 | func TestExec_Error(t *testing.T) { 42 | t.Parallel() 43 | 44 | sh := New(context.New(config.Project{})) 45 | cmd := sh.NewCommand("exit", "1") 46 | ps, err := sh.Exec(cmd) 47 | assert.Error(t, err) 48 | assert.NotNil(t, ps) 49 | } 50 | 51 | func TestEscapeArgs(t *testing.T) { 52 | t.Parallel() 53 | 54 | original := []string{"dan", "wears", "big jorts"} 55 | expected := []string{"dan", "wears", "'big jorts'"} 56 | actual := escapeArgs(original) 57 | assert.Equal(t, expected, actual) 58 | } 59 | 60 | func TestExists(t *testing.T) { 61 | t.Parallel() 62 | 63 | sh := New(context.New(config.Project{})) 64 | assert.True(t, sh.Exists("git")) 65 | assert.False(t, sh.Exists("nonexistent_program.exe")) 66 | } 67 | 68 | func TestCurrentDirectory(t *testing.T) { 69 | t.Parallel() 70 | 71 | ctx := context.New(config.Project{}) 72 | ctx.CurrentDirectory = "TEST" 73 | sh := New(ctx) 74 | assert.Equal(t, ctx.CurrentDirectory, sh.CurrentDirectory()) 75 | } 76 | -------------------------------------------------------------------------------- /pkg/context/interrupt.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package context 22 | 23 | import ( 24 | "context" 25 | "fmt" 26 | "os" 27 | "os/signal" 28 | "syscall" 29 | ) 30 | 31 | type errReceivedSignal struct { 32 | Signal os.Signal 33 | } 34 | 35 | func (e errReceivedSignal) Error() string { 36 | return fmt.Sprintf("received: %s", e.Signal) 37 | } 38 | 39 | // Task is function that can be executed by an interrupt. 40 | type Task func() error 41 | 42 | // Interrupt tracks signals from the OS to determine whether to interrupt. 43 | type Interrupt struct { 44 | signals chan os.Signal 45 | errs chan error 46 | } 47 | 48 | // NewInterrupt creates an interrupt instance. 49 | func NewInterrupt() *Interrupt { 50 | return &Interrupt{ 51 | signals: make(chan os.Signal, 1), 52 | errs: make(chan error, 1), 53 | } 54 | } 55 | 56 | // Run executes a given task with a given context, dealing with its timeouts, 57 | // cancels and SIGTERM and SIGINT signals. 58 | // It will return an error if the context is canceled, if deadline exceeds, 59 | // if a SIGTERM or SIGINT is received and of course if the task itself fails. 60 | func (i *Interrupt) Run(ctx context.Context, task Task) error { 61 | go func() { 62 | i.errs <- task() 63 | }() 64 | signal.Notify(i.signals, syscall.SIGINT, syscall.SIGTERM) 65 | select { 66 | case err := <-i.errs: 67 | return err 68 | case <-ctx.Done(): 69 | return ctx.Err() 70 | case sig := <-i.signals: 71 | return errReceivedSignal{Signal: sig} 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /internal/parallel/group_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package parallel 22 | 23 | import ( 24 | "errors" 25 | "sync" 26 | "testing" 27 | "time" 28 | 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | var errTestError = errors.New("TEST") 33 | 34 | func TestGroup(t *testing.T) { 35 | t.Parallel() 36 | 37 | var groupMu sync.Mutex 38 | 39 | var g = New(4) 40 | 41 | var counter int 42 | 43 | for i := 0; i < 10; i++ { 44 | g.Go(func() error { 45 | time.Sleep(10 * time.Millisecond) 46 | groupMu.Lock() 47 | counter++ 48 | groupMu.Unlock() 49 | 50 | return nil 51 | }) 52 | } 53 | assert.NoError(t, g.Wait()) 54 | assert.Equal(t, counter, 10) 55 | } 56 | 57 | func TestGroupOrder(t *testing.T) { 58 | t.Parallel() 59 | 60 | var num = 10 61 | 62 | var g = New(1) 63 | 64 | var output = []int{} 65 | 66 | for i := 0; i < num; i++ { 67 | i := i 68 | 69 | g.Go(func() error { 70 | output = append(output, i) 71 | 72 | return nil 73 | }) 74 | } 75 | assert.NoError(t, g.Wait()) 76 | assert.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, output) 77 | } 78 | 79 | func TestGroupOrderError(t *testing.T) { 80 | t.Parallel() 81 | 82 | var g = New(1) 83 | 84 | var output = []int{} 85 | 86 | for i := 0; i < 10; i++ { 87 | i := i 88 | 89 | g.Go(func() error { 90 | output = append(output, i) 91 | 92 | return errTestError 93 | }) 94 | } 95 | assert.EqualError(t, g.Wait(), errTestError.Error()) 96 | assert.Equal(t, []int{0}, output) 97 | } 98 | -------------------------------------------------------------------------------- /internal/shell/shelltest/shelltest_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package shelltest 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/pkg/config" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestShell(t *testing.T) { 32 | t.Parallel() 33 | 34 | ctx := context.New(config.Project{}) 35 | ctx.CurrentDirectory = "TEST" 36 | sh := Shell{ 37 | Context: ctx, 38 | Commands: []Command{ 39 | { 40 | ReturnCode: 0, 41 | Stdout: "TEST", 42 | Stderr: "TEST", 43 | }, 44 | { 45 | ReturnCode: 128, 46 | Stdout: "TEST", 47 | Stderr: "TEST", 48 | }, 49 | }, 50 | } 51 | 52 | dir := sh.CurrentDirectory() 53 | assert.Equal(t, dir, ctx.CurrentDirectory) 54 | 55 | exists := sh.Exists("echo") 56 | assert.True(t, exists) 57 | 58 | sh.SupportedPrograms = map[string]bool{ 59 | "echo": false, 60 | } 61 | exists = sh.Exists("echo") 62 | assert.False(t, exists) 63 | 64 | cmd := sh.NewCommand("echo", "true") 65 | assert.NotNil(t, cmd) 66 | 67 | proc, err := sh.Exec(cmd) 68 | assert.NoError(t, err) 69 | assert.NotNil(t, proc) 70 | 71 | proc, err = sh.Exec(cmd) 72 | assert.EqualError(t, err, "128") 73 | assert.NotNil(t, proc) 74 | 75 | sh.expectOverflowError = true 76 | expectedErr := ErrCommandOverflow{ 77 | Index: 2, 78 | Len: 2, 79 | Command: cmd.String(), 80 | } 81 | _, err = sh.Exec(cmd) 82 | assert.EqualError(t, err, expectedErr.Error()) 83 | } 84 | -------------------------------------------------------------------------------- /internal/git/errors.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package git 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | "path/filepath" 27 | ) 28 | 29 | const errDirtyHeading = "git is currently in a dirty state, please check in your pipeline what can be changing the following files" 30 | 31 | // ErrDirty happens when the repo has uncommitted/unstashed changes. 32 | type ErrDirty struct { 33 | Status string 34 | } 35 | 36 | func (e ErrDirty) Error() string { 37 | return fmt.Sprintf("%s:\n%v", errDirtyHeading, e.Status) 38 | } 39 | 40 | // ErrWrongRef happens when the HEAD reference is different from the tag being built. 41 | type ErrWrongRef struct { 42 | Commit, Tag string 43 | } 44 | 45 | func (e ErrWrongRef) Error() string { 46 | return fmt.Sprintf("git tag %v was not made against commit %v", e.Tag, e.Commit) 47 | } 48 | 49 | // ErrNoTag happens if the underlying git repository doesn't contain any tags. 50 | var ErrNoTag = errors.New("git doesn't contain any tags") 51 | 52 | // ErrNotRepository happens if you try to run Cider against a folder 53 | // which is not a git repository. 54 | type ErrNotRepository struct { 55 | Dir string 56 | } 57 | 58 | func (e ErrNotRepository) Error() string { 59 | return fmt.Sprintf("the directory at %s is not a git repository", filepath.Clean(e.Dir)) 60 | } 61 | 62 | // ErrNoGit happens when git is not present in PATH. 63 | var ErrNoGit = errors.New("git not present in PATH") 64 | 65 | // ErrNoRemoteOrigin happens when the repository has no remote named "origin". 66 | var ErrNoRemoteOrigin = errors.New("repository doesn't have an `origin` remote") 67 | -------------------------------------------------------------------------------- /internal/log/log.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package log is a substitute for the global apex/log that is not thread safe. 22 | package log 23 | 24 | import ( 25 | "os" 26 | "sync" 27 | 28 | "github.com/apex/log" 29 | "github.com/apex/log/handlers/cli" 30 | "github.com/fatih/color" 31 | ) 32 | 33 | // Interface is an extension of log.Interface. 34 | type Interface interface { 35 | log.Interface 36 | SetColorMode(v bool) 37 | SetDebug(v bool) 38 | SetPadding(v int) 39 | } 40 | 41 | // Fields re-exports log.Fields from github.com/apex/log. 42 | type Fields = log.Fields 43 | 44 | // Log is a thread-safe wrapper for log.Logger. 45 | type Log struct { 46 | log.Logger 47 | mu sync.RWMutex 48 | } 49 | 50 | // New creates a new Log instance. 51 | func New() *Log { 52 | return &Log{ 53 | Logger: log.Logger{ 54 | Handler: cli.New(os.Stderr), 55 | Level: log.InfoLevel, 56 | }, 57 | } 58 | } 59 | 60 | // SetColorMode sets the global color mode for the logger. 61 | func (l *Log) SetColorMode(v bool) { 62 | l.mu.Lock() 63 | defer l.mu.Unlock() 64 | 65 | color.NoColor = v 66 | } 67 | 68 | // SetDebug sets the log level to Debug or Info. 69 | func (l *Log) SetDebug(v bool) { 70 | l.mu.Lock() 71 | defer l.mu.Unlock() 72 | 73 | if v { 74 | l.Level = log.DebugLevel 75 | } else { 76 | l.Level = log.InfoLevel 77 | } 78 | } 79 | 80 | // SetPadding sets the padding of the log handler in a thread-safe way. 81 | func (l *Log) SetPadding(v int) { 82 | l.mu.Lock() 83 | defer l.mu.Unlock() 84 | 85 | if handler, ok := l.Handler.(*cli.Handler); ok { 86 | handler.Padding = v 87 | l.Handler = handler 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /internal/client/assets_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package client 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/asc-go/asc" 27 | "github.com/cidertool/cider/pkg/config" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | // Test UploadRoutingCoverage 32 | 33 | func TestUploadRoutingCoverage_Happy(t *testing.T) { 34 | t.Parallel() 35 | 36 | asset := newTestAsset(t, "TEST") 37 | ctx, client := newTestContext( 38 | response{ 39 | Response: asc.RoutingAppCoverageResponse{ 40 | Data: asc.RoutingAppCoverage{ 41 | Attributes: &asc.RoutingAppCoverageAttributes{ 42 | UploadOperations: []asc.UploadOperation{}, 43 | }, 44 | }, 45 | }, 46 | }, 47 | response{ 48 | Response: asc.RoutingAppCoverageResponse{ 49 | Data: asc.RoutingAppCoverage{ 50 | Attributes: &asc.RoutingAppCoverageAttributes{ 51 | UploadOperations: []asc.UploadOperation{}, 52 | }, 53 | }, 54 | }, 55 | }, 56 | response{ 57 | Response: asc.RoutingAppCoverageResponse{ 58 | Data: asc.RoutingAppCoverage{ 59 | Attributes: &asc.RoutingAppCoverageAttributes{ 60 | UploadOperations: []asc.UploadOperation{}, 61 | }, 62 | }, 63 | }, 64 | }, 65 | response{ 66 | Response: asc.RoutingAppCoverageResponse{ 67 | Data: asc.RoutingAppCoverage{ 68 | Attributes: &asc.RoutingAppCoverageAttributes{ 69 | UploadOperations: []asc.UploadOperation{}, 70 | }, 71 | }, 72 | }, 73 | }, 74 | ) 75 | 76 | defer ctx.Close() 77 | 78 | err := client.UploadRoutingCoverage(ctx.Context, "TEST", config.File{ 79 | Path: asset.Name, 80 | }) 81 | assert.NoError(t, err) 82 | } 83 | -------------------------------------------------------------------------------- /internal/clicommand/check.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "fmt" 25 | 26 | "github.com/cidertool/cider/internal/pipe/defaults" 27 | "github.com/cidertool/cider/pkg/context" 28 | "github.com/fatih/color" 29 | "github.com/spf13/cobra" 30 | ) 31 | 32 | type checkCmd struct { 33 | cmd *cobra.Command 34 | debugFlagValue *bool 35 | config string 36 | } 37 | 38 | func newCheckCmd(debugFlagValue *bool) *checkCmd { 39 | var root = &checkCmd{debugFlagValue: debugFlagValue} 40 | 41 | var cmd = &cobra.Command{ 42 | Use: "check", 43 | Short: "Checks if the configuration is valid", 44 | Long: `Use to validate your configuration file.`, 45 | Example: "cider check", 46 | SilenceUsage: true, 47 | SilenceErrors: true, 48 | RunE: root.Run, 49 | } 50 | 51 | cmd.Flags().StringVarP(&root.config, "config", "f", "", "Configuration file to check") 52 | 53 | root.cmd = cmd 54 | 55 | return root 56 | } 57 | 58 | func (cmd *checkCmd) Run(c *cobra.Command, args []string) error { 59 | logger := newLogger(cmd.debugFlagValue) 60 | 61 | cfg, err := loadConfig(cmd.config, "") 62 | if err != nil { 63 | return err 64 | } 65 | 66 | var ctx = context.New(cfg) 67 | 68 | if err := context.NewInterrupt().Run(ctx, func() error { 69 | logger.Info(color.New(color.Bold).Sprint("checking config:")) 70 | 71 | return defaults.Pipe{}.Run(ctx) 72 | }); err != nil { 73 | logger.WithError(err).Error(color.New(color.Bold).Sprintf("config is invalid")) 74 | 75 | return fmt.Errorf("invalid config: %w", err) 76 | } 77 | 78 | logger.Info(color.New(color.Bold).Sprintf("config is valid")) 79 | 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /internal/parallel/group.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package parallel wraps a error group with a semaphore with configurable 22 | // size, so you can control the number of tasks being executed simultaneously. 23 | package parallel 24 | 25 | import ( 26 | "sync" 27 | 28 | "golang.org/x/sync/errgroup" 29 | ) 30 | 31 | // Group is the Semphore ErrorGroup itself. 32 | type Group interface { 33 | Go(func() error) 34 | Wait() error 35 | } 36 | 37 | // New returns a new Group of a given size. 38 | func New(size int) Group { 39 | if size <= 1 { 40 | return &serialGroup{} 41 | } 42 | 43 | return ¶llelGroup{ 44 | ch: make(chan bool, size), 45 | g: errgroup.Group{}, 46 | } 47 | } 48 | 49 | type parallelGroup struct { 50 | ch chan bool 51 | g errgroup.Group 52 | } 53 | 54 | // Go execs one function respecting the group and semaphore. 55 | func (s *parallelGroup) Go(fn func() error) { 56 | s.g.Go(func() error { 57 | s.ch <- true 58 | defer func() { 59 | <-s.ch 60 | }() 61 | 62 | return fn() 63 | }) 64 | } 65 | 66 | // Wait waits for the group to complete and return an error if any. 67 | func (s *parallelGroup) Wait() error { 68 | return s.g.Wait() 69 | } 70 | 71 | type serialGroup struct { 72 | err error 73 | errOnce sync.Once 74 | } 75 | 76 | // Go execs runs `fn` and saves the result if no error has been encountered. 77 | func (s *serialGroup) Go(fn func() error) { 78 | if s.err != nil { 79 | return 80 | } 81 | 82 | if err := fn(); err != nil { 83 | s.errOnce.Do(func() { 84 | s.err = err 85 | }) 86 | } 87 | } 88 | 89 | // Wait waits for Go to complete and returns the first error encountered. 90 | func (s *serialGroup) Wait() error { 91 | return s.err 92 | } 93 | -------------------------------------------------------------------------------- /pkg/context/context_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package context 22 | 23 | import ( 24 | "testing" 25 | "time" 26 | 27 | "github.com/cidertool/cider/pkg/config" 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | func TestNew(t *testing.T) { 32 | t.Parallel() 33 | 34 | ctx := New(config.Project{}) 35 | assert.Equal(t, 1, ctx.MaxProcesses) 36 | } 37 | 38 | func TestNewWithTimeout(t *testing.T) { 39 | t.Parallel() 40 | 41 | ctx, cancel := NewWithTimeout(config.Project{}, time.Second) 42 | assert.NotEmpty(t, ctx.Env) 43 | cancel() 44 | <-ctx.Done() 45 | assert.EqualError(t, ctx.Err(), `context canceled`) 46 | } 47 | 48 | func TestEnv(t *testing.T) { 49 | t.Parallel() 50 | 51 | var env = Env{"DOG": "FRIEND"} 52 | anotherEnv := env.Copy() 53 | assert.Equal(t, env, anotherEnv) 54 | assert.NotSame(t, &env, &anotherEnv) 55 | assert.Equal(t, []string{"DOG=FRIEND"}, env.Strings()) 56 | } 57 | 58 | func TestPublishMode(t *testing.T) { 59 | t.Parallel() 60 | 61 | var mode PublishMode 62 | mode = PublishModeAppStore 63 | assert.Equal(t, "appstore", mode.String()) 64 | assert.Equal(t, "{appstore,testflight}", mode.Type()) 65 | mode = PublishModeTestflight 66 | assert.Equal(t, "testflight", mode.String()) 67 | assert.Equal(t, "{appstore,testflight}", mode.Type()) 68 | mode = PublishMode("bad") 69 | assert.Equal(t, "bad", mode.String()) 70 | assert.Equal(t, "{appstore,testflight}", mode.Type()) 71 | 72 | var err error 73 | err = mode.Set("appstore") 74 | assert.NoError(t, err) 75 | assert.Equal(t, PublishModeAppStore, mode) 76 | err = mode.Set("testflight") 77 | assert.NoError(t, err) 78 | assert.Equal(t, PublishModeTestflight, mode) 79 | err = mode.Set("bad") 80 | assert.Error(t, err) 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Cider logo 7 |

Submit to the App Store in seconds!

8 |

9 | 10 | --- 11 | 12 | Cider is a tool managing the entire release process of an iOS, macOS or tvOS application, supported by official Apple APIs. It takes the builds you've uploaded to App Store Connect, updates their metadata, and submits them for review automatically using an expressive YAML configuration. Unlike Xcode or altool, Cider is designed to be useful on Linux and Windows, in addition to macOS. 13 | 14 | ## Documentation 15 | 16 | Documentation is hosted at . Check out our installation and quick start documentation! 17 | 18 | ## Integrations 19 | 20 | - [GitHub Action](https://github.com/marketplace/actions/cider-action) 21 | - [Buildkite Plugin](https://github.com/cidertool/cider-buildkite-plugin) 22 | 23 | ## Badges 24 | 25 | ![build](https://github.com/cidertool/cider/workflows/build/badge.svg) 26 | [![codecov](https://codecov.io/gh/cidertool/cider/branch/main/graph/badge.svg)](https://codecov.io/gh/cidertool/cider) 27 | [![License](https://img.shields.io/github/license/cidertool/cider)](/COPYING) 28 | [![Release](https://img.shields.io/github/release/cidertool/cider.svg)](https://github.com/cidertool/cider/releases/latest) 29 | [![Docker](https://img.shields.io/docker/pulls/cidertool/cider)](https://hub.docker.com/r/cidertool/cider) 30 | [![Github Releases Stats of Cider](https://img.shields.io/github/downloads/cidertool/cider/total.svg?logo=github)](https://somsubhra.com/github-release-stats/?username=cidertool&repository=cider) 31 | 32 | ## Contributing 33 | 34 | This project's primary goal is to simplify the process to release on the App Store, and enable the entire build + test + release process to be executable in the command line. Until the package's version stabilizes with v1, there isn't a strong roadmap beyond those stated goals. However, contributions are always welcome. If you want to get involved or you just want to offer feedback, please see [`CONTRIBUTING.md`](https://github.com/cidertool/.github/blob/main/CONTRIBUTING.md) for details. 35 | 36 | ## Credits 37 | 38 | Special thanks to: 39 | 40 | - [GoReleaser](https://goreleaser.com/) for inspiring the architecture and open sourcing several components used in Cider 41 | 42 | ## License 43 | 44 | This library is licensed under the GNU General Public License v3.0 or later 45 | 46 | See [COPYING](./COPYING) to see the full text. 47 | -------------------------------------------------------------------------------- /docs/configuration-footer.md: -------------------------------------------------------------------------------- 1 | ## Locales 2 | 3 | The App Store operates in a variety of locales and territories. When referring to localized resources in Cider such as [AppLocalizations](#applocalizations), [VersionLocalizations](#versionlocalizations), or [TestflightLocalizations](#testflightlocalizations), use ISO 639-1 identifiers where possible, in the style of `"en-US"` where possible. If an ISO 639-1 code does not exist, use the appropriate ISO 639-2 code. 4 | 5 | ## App Categories 6 | 7 | App categories provided and supported by the App Store Connect API are fluid and difficult to create a consistent format for. The App Store adds categories regularly, and it represents a challenge for both metadata maintainers and maintainers of Cider to support. Therefore, the choice has been made to accept any string as a category ID, and let the API respond with whether or not it's valid. 8 | 9 | Here are some known category IDs, with subcategories broken out where applicable, that you can use in your configuration: 10 | 11 | - `"BOOKS"` 12 | - `"BUSINESS"` 13 | - `"DEVELOPER_TOOLS"` 14 | - `"EDUCATION"` 15 | - `"ENTERTAINMENT"` 16 | - `"FINANCE"` 17 | - `"FOOD_AND_DRINK"` 18 | - `"GAMES"` 19 | - `"GAMES_SPORTS"` 20 | - `"GAMES_WORD"` 21 | - `"GAMES_MUSIC"` 22 | - `"GAMES_ADVENTURE"` 23 | - `"GAMES_ACTION"` 24 | - `"GAMES_ROLE_PLAYING"` 25 | - `"GAMES_CASUAL"` 26 | - `"GAMES_BOARD"` 27 | - `"GAMES_TRIVIA"` 28 | - `"GAMES_CARD"` 29 | - `"GAMES_PUZZLE"` 30 | - `"GAMES_CASINO"` 31 | - `"GAMES_STRATEGY"` 32 | - `"GAMES_SIMULATION"` 33 | - `"GAMES_RACING"` 34 | - `"GAMES_FAMILY"` 35 | - `"HEALTH_AND_FITNESS"` 36 | - `"LIFESTYLE"` 37 | - `"MAGAZINES_AND_NEWSPAPERS"` 38 | - `"MEDICAL"` 39 | - `"PRODUCTIVITY"` 40 | - `"REFERENCE"` 41 | - `"SHOPPING"` 42 | - `"SOCIAL_NETWORKING"` 43 | - `"SPORTS"` 44 | - `"STICKERS"` 45 | - `"STICKERS_PLACES_AND_OBJECTS"` 46 | - `"STICKERS_EMOJI_AND_EXPRESSIONS"` 47 | - `"STICKERS_CELEBRATIONS"` 48 | - `"STICKERS_CELEBRITIES"` 49 | - `"STICKERS_MOVIES_AND_TV"` 50 | - `"STICKERS_SPORTS_AND_ACTIVITIES"` 51 | - `"STICKERS_EATING_AND_DRINKING"` 52 | - `"STICKERS_CHARACTERS"` 53 | - `"STICKERS_ANIMALS"` 54 | - `"STICKERS_FASHION"` 55 | - `"STICKERS_ART"` 56 | - `"STICKERS_GAMING"` 57 | - `"STICKERS_KIDS_AND_FAMILY"` 58 | - `"STICKERS_PEOPLE"` 59 | - `"STICKERS_MUSIC"` 60 | - `"MUSIC"` 61 | - `"TRAVEL"` 62 | - `"UTILITIES"` 63 | - `"WEATHER"` 64 | 65 | For more information on categories, see [Choosing a category](https://developer.apple.com/app-store/categories/) on the Apple Developer Portal. 66 | -------------------------------------------------------------------------------- /internal/pipe/pipe.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package pipe declares utilities and errors for pipes 22 | package pipe 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | ) 28 | 29 | // ErrSkipGitEnabled happens if --skip-git is set. It means that the part of a Piper that 30 | // extracts metadata from the Git repository was not run. 31 | var ErrSkipGitEnabled = Skip("inspecting git state is disabled") 32 | 33 | // ErrSkipNoAppsToPublish happens when there are no apps in the configuration to publish 34 | // or update metadata for. It will be raised when the configuration is effectively empty. 35 | var ErrSkipNoAppsToPublish = Skip("no apps selected to publish") 36 | 37 | // ErrSkipSubmitEnabled happens if --skip-submit is set. 38 | // It means that the part of a Piper that submits to Apple for review was not run. 39 | var ErrSkipSubmitEnabled = Skip("submission is disabled") 40 | 41 | // ErrMissingApp happens when an app is selected in the interface that is not defined in the configuration. 42 | type ErrMissingApp struct { 43 | Name string 44 | } 45 | 46 | func (e ErrMissingApp) Error() string { 47 | return fmt.Sprintf("no app defined in configuration matching the name %s", e.Name) 48 | } 49 | 50 | // IsSkip returns true if the error is an ErrSkip. 51 | func IsSkip(err error) bool { 52 | var serr ErrSkip 53 | ok := errors.As(err, &serr) 54 | 55 | return ok 56 | } 57 | 58 | // ErrSkip occurs when a pipe is skipped for some reason. 59 | type ErrSkip struct { 60 | reason string 61 | } 62 | 63 | // Error implements the error interface. returns the reason the pipe was skipped. 64 | func (e ErrSkip) Error() string { 65 | return e.reason 66 | } 67 | 68 | // Skip skips this pipe with the given reason. 69 | func Skip(reason string) ErrSkip { 70 | return ErrSkip{reason: reason} 71 | } 72 | -------------------------------------------------------------------------------- /docs/quick-start.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | nav_order: 3 4 | --- 5 | 6 | # Quick Start 7 | 8 | Once you've [installed](./install.md) Cider, you can get started setting it up for your project. 9 | 10 | Run `cider init` to create a new `.cider.yml` file in the current directory: 11 | 12 | ```shell 13 | cider init 14 | ``` 15 | 16 | This will run through a series of prompts where you will get to set some default values for your project. See [configuration.md](./configuration.md) for additional options and documentation on the entire project specification. This file should be checked in to source control. 17 | 18 | Once this file is set up, you can either proceed to run `cider` [locally](#local), or set it up in [CI](#ci). 19 | 20 | ## Local 21 | 22 | The most simple invocation of Cider to submit an app is as follows: 23 | 24 | ``` 25 | cider release --mode appstore 26 | ``` 27 | 28 | Cider contains a host of options enabling you to customize its runtime. Follow the guide on the [`release` command](./commands/cider_release.md). 29 | 30 | ## CI 31 | 32 | ### GitHub Actions 33 | 34 | Cider can also be run autonomously using the official [Cider Action](https://github.com/marketplace/actions/cider-action) hosted on the GitHub Marketplace. The Action is versioned independently of Cider, and all of Cider's commands and internal capabilities are available. 35 | 36 | #### Usage 37 | 38 | ```yaml 39 | - uses: actions/checkout@v2 40 | - uses: cidertool/cider-action@v0 41 | with: 42 | version: latest 43 | args: release --mode appstore --set-version ${{ env.VERSION }} 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }} 47 | ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} 48 | ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }} 49 | ``` 50 | 51 | You can run this job in any context you see fit to use Cider to update app metadata or submit new versions of your apps to the App Store. 52 | 53 | ### Buildkite 54 | 55 | If you're using Buildkite, you can use the [Cider Buildkite Plugin](https://github.com/cidertool/cider-buildkite-plugin). Similarly to the GitHub Action, the plugin is versioned independently of Cider and any function available in the Cider command line can be used. This plugin requires Docker. 56 | 57 | #### Usage 58 | 59 | ```yaml 60 | steps: 61 | - label: ':apple: Release with Cider' 62 | plugins: 63 | - cidertool/cider#v0.1.0: 64 | args: release --mode appstore 65 | env: 66 | ASC_KEY_ID: '...' 67 | ASC_ISSUER_ID: '...' 68 | ASC_PRIVATE_KEY: '...' 69 | ``` 70 | -------------------------------------------------------------------------------- /pkg/config/testdata/valid.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Wayfair: 3 | id: com.sky.ProjectApp 4 | localizations: 5 | en-US: 6 | name: My App 7 | subtitle: congratulations 8 | privacyPolicyText: go away 9 | privacyPolicyURL: https://google.com 10 | ja: 11 | name: 僕のアップ 12 | subtitle: おめでとう 13 | privacyPolicyText: 消えろ 14 | privacyPolicyURL: https://google.co.jp 15 | versions: 16 | platform: iOS 17 | copyright: 2020 Wayfair LLC 18 | earliestReleaseDate: 2020-08-07T14:25:00Z 19 | releaseType: afterApproval 20 | enablePhasedRelease: true 21 | localizations: 22 | en-US: 23 | description: '' 24 | keywords: '' 25 | marketingURL: https://google.com 26 | promotionalText: '' 27 | supportURL: https://google.com 28 | whatsNew: '' 29 | previewSets: 30 | iphone65: 31 | - path: '' 32 | mimeType: '' 33 | previewFrameTimeCode: '' 34 | screenshotSets: 35 | iphone65: 36 | - path: '' 37 | idfaDeclaration: 38 | attributesActionWithPreviousAd: true 39 | attributesAppInstallationToPreviousAd: true 40 | honorsLimitedAdTracking: true 41 | servesAds: true 42 | routingCoverage: 43 | path: '' 44 | reviewDetails: 45 | contact: 46 | email: '' 47 | firstName: '' 48 | lastName: '' 49 | phone: '' 50 | demoAccount: 51 | name: '' 52 | password: '' 53 | isRequired: false 54 | notes: '' 55 | attachments: 56 | - path: '' 57 | testflight: 58 | enableAutoNotify: false 59 | licenseAgreement: '' 60 | betaGroups: 61 | - group: 'My Colleagues' 62 | testers: 63 | - email: 'jeff@mail.com' 64 | - email: 'geoff@mail.com' 65 | - group: 'My Friends' 66 | testers: 67 | - email: 'jeff@mail.com' 68 | - email: 'geoff@mail.com' 69 | betaTesters: 70 | - email: 'jeff@mail.com' 71 | - email: 'geoff@mail.com' 72 | localizations: 73 | en-US: 74 | description: '' 75 | feedbackEmail: '' 76 | marketingURL: https://google.com 77 | privacyPolicyURL: https://google.com 78 | tvOSPrivacyPolicy: '' 79 | whatsNew: '' 80 | reviewDetails: 81 | contact: 82 | email: '' 83 | firstName: '' 84 | lastName: '' 85 | phone: '' 86 | demoAccount: 87 | name: '' 88 | password: '' 89 | isRequired: false 90 | notes: '' 91 | -------------------------------------------------------------------------------- /internal/pipe/testflight/testflight_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package testflight 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/internal/client/clienttest" 27 | "github.com/cidertool/cider/internal/pipe" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func TestTestflight_Happy(t *testing.T) { 34 | t.Parallel() 35 | 36 | ctx := context.New(config.Project{ 37 | "TEST": { 38 | BundleID: "com.test.TEST", 39 | Testflight: config.Testflight{ 40 | ReviewDetails: &config.ReviewDetails{ 41 | Contact: &config.ContactPerson{ 42 | Email: "test@example.com", 43 | FirstName: "Person", 44 | LastName: "Personson", 45 | Phone: "1555555555", 46 | }, 47 | DemoAccount: &config.DemoAccount{}, 48 | Notes: "TEST", 49 | Attachments: []config.File{ 50 | {Path: "TEST"}, 51 | }, 52 | }, 53 | }, 54 | }, 55 | }) 56 | ctx.AppsToRelease = []string{"TEST"} 57 | 58 | p := Pipe{} 59 | p.Client = &clienttest.Client{} 60 | 61 | assert.Equal(t, "committing to testflight", p.String()) 62 | 63 | err := p.Publish(ctx) 64 | assert.NoError(t, err) 65 | } 66 | 67 | func TestTestflight_Happy_Skips(t *testing.T) { 68 | t.Parallel() 69 | 70 | ctx := context.New(config.Project{ 71 | "TEST": { 72 | BundleID: "com.test.TEST", 73 | }, 74 | }) 75 | ctx.AppsToRelease = []string{"TEST"} 76 | ctx.SkipUpdateMetadata = true 77 | ctx.SkipSubmit = true 78 | 79 | p := Pipe{} 80 | p.Client = &clienttest.Client{} 81 | 82 | err := p.Publish(ctx) 83 | assert.EqualError(t, err, pipe.ErrSkipSubmitEnabled.Error()) 84 | } 85 | 86 | func TestTestflight_Happy_NoApps(t *testing.T) { 87 | t.Parallel() 88 | 89 | ctx := context.New(config.Project{}) 90 | ctx.Credentials = &clienttest.Credentials{} 91 | 92 | p := Pipe{} 93 | 94 | err := p.Publish(ctx) 95 | assert.NoError(t, err) 96 | } 97 | -------------------------------------------------------------------------------- /internal/clicommand/root.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clicommand 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | // Execute is the primary function to initiate the command line interface for Cider. 31 | func Execute(version string, exit func(int), args []string) { 32 | // nolint: forbidigo 33 | fmt.Println() 34 | // nolint: forbidigo 35 | defer fmt.Println() 36 | 37 | NewRoot(version, exit).Execute(args) 38 | } 39 | 40 | // Root defines a rough structure for a root command type. 41 | type Root struct { 42 | Cmd *cobra.Command 43 | exit func(int) 44 | } 45 | 46 | // NewRoot creates a new instance of the root command for the cider executable. 47 | func NewRoot(version string, exit func(int)) *Root { 48 | var root = &Root{ 49 | exit: exit, 50 | } 51 | 52 | var debug bool 53 | 54 | var cmd = &cobra.Command{ 55 | Use: "cider", 56 | Short: "Submit your builds to the Apple App Store in seconds", 57 | Version: version, 58 | SilenceUsage: true, 59 | SilenceErrors: true, 60 | DisableAutoGenTag: true, 61 | } 62 | 63 | cmd.PersistentFlags().BoolVar(&debug, "debug", false, "Enable debug mode") 64 | 65 | cmd.AddCommand( 66 | newInitCmd(&debug).cmd, 67 | newCheckCmd(&debug).cmd, 68 | newReleaseCmd(&debug).cmd, 69 | newCompletionsCmd().cmd, 70 | ) 71 | 72 | root.Cmd = cmd 73 | 74 | return root 75 | } 76 | 77 | // Execute executes the root command. 78 | func (cmd *Root) Execute(args []string) { 79 | cmd.Cmd.SetArgs(args) 80 | 81 | if err := cmd.Cmd.Execute(); err != nil { 82 | var code = 1 83 | 84 | var msg = "command failed" 85 | 86 | var eerr *exitError 87 | 88 | if ok := errors.As(err, &eerr); ok { 89 | code = eerr.code 90 | 91 | if eerr.details != "" { 92 | msg = eerr.details 93 | } 94 | } 95 | 96 | newLogger(nil).WithError(err).Error(msg) 97 | 98 | cmd.exit(code) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /internal/pipe/publish/publish.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package publish is a pipe that runs the testflight or store pipes depending on publish mode 22 | package publish 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/cidertool/cider/internal/client" 28 | "github.com/cidertool/cider/internal/middleware" 29 | "github.com/cidertool/cider/internal/pipe" 30 | "github.com/cidertool/cider/internal/pipe/store" 31 | "github.com/cidertool/cider/internal/pipe/testflight" 32 | "github.com/cidertool/cider/pkg/context" 33 | ) 34 | 35 | // errUnsupportedPublishMode happens when an unsupported publish mode is provided to the pipe. 36 | type errUnsupportedPublishMode struct { 37 | mode context.PublishMode 38 | } 39 | 40 | func (e errUnsupportedPublishMode) Error() string { 41 | return fmt.Sprintf("failed to publish: unsupported publish mode %s", e.mode) 42 | } 43 | 44 | // Pipe that publishes artifacts. 45 | type Pipe struct { 46 | client client.Client 47 | } 48 | 49 | func (Pipe) String() string { 50 | return "publishing from app store connect" 51 | } 52 | 53 | // Publisher should be implemented by pipes that want to publish artifacts. 54 | type Publisher interface { 55 | fmt.Stringer 56 | 57 | // Default sets the configuration defaults 58 | Publish(ctx *context.Context) error 59 | } 60 | 61 | // Run the pipe. 62 | func (p Pipe) Run(ctx *context.Context) error { 63 | if len(ctx.AppsToRelease) == 0 { 64 | return pipe.ErrSkipNoAppsToPublish 65 | } 66 | 67 | var publisher Publisher 68 | 69 | switch ctx.PublishMode { 70 | case context.PublishModeTestflight: 71 | publisher = &testflight.Pipe{Client: p.client} 72 | case context.PublishModeAppStore: 73 | publisher = &store.Pipe{Client: p.client} 74 | default: 75 | return errUnsupportedPublishMode{ctx.PublishMode} 76 | } 77 | 78 | if err := middleware.Logging( 79 | publisher.String(), 80 | middleware.ErrHandler(publisher.Publish), 81 | middleware.ExtraPadding, 82 | )(ctx); err != nil { 83 | return fmt.Errorf("%s: failed to publish: %w", publisher.String(), err) 84 | } 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/pipe/env/env.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package env is a pipe that loads environment variables 22 | package env 23 | 24 | import ( 25 | "errors" 26 | "fmt" 27 | "io" 28 | "os" 29 | "path/filepath" 30 | 31 | "github.com/cidertool/cider/pkg/context" 32 | ) 33 | 34 | // ErrMissingEnvVar indicates an error when a required variable is missing in the environment. 35 | var ErrMissingEnvVar = errors.New("missing required environment variable") 36 | 37 | // Pipe is a global hook pipe. 38 | type Pipe struct{} 39 | 40 | // String is the name of this pipe. 41 | func (Pipe) String() string { 42 | return "loading environment variables" 43 | } 44 | 45 | // Run executes the hooks. 46 | func (p Pipe) Run(ctx *context.Context) error { 47 | keyID, err := loadEnv("ASC_KEY_ID", true) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | issuerID, err := loadEnv("ASC_ISSUER_ID", true) 53 | 54 | if err != nil { 55 | return err 56 | } 57 | 58 | privateKey, err := loadEnv("ASC_PRIVATE_KEY", true) 59 | 60 | if err != nil { 61 | privateKey, err = loadEnvFromPath("ASC_PRIVATE_KEY_PATH", true) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | 67 | creds, err := context.NewCredentials(keyID, issuerID, []byte(privateKey)) 68 | 69 | if err != nil { 70 | return err 71 | } 72 | 73 | ctx.Credentials = creds 74 | 75 | return nil 76 | } 77 | 78 | func loadEnv(env string, required bool) (string, error) { 79 | val := os.Getenv(env) 80 | if val == "" && required { 81 | return "", fmt.Errorf("key %s not found: %w", env, ErrMissingEnvVar) 82 | } 83 | 84 | return val, nil 85 | } 86 | 87 | func loadEnvFromPath(env string, required bool) (string, error) { 88 | val, err := loadEnv(env, required) 89 | if err != nil { 90 | return "", err 91 | } 92 | 93 | f, err := os.Open(filepath.Clean(val)) 94 | 95 | if err != nil { 96 | if required { 97 | return "", err 98 | } 99 | 100 | return "", nil 101 | } 102 | 103 | bytes, err := io.ReadAll(f) 104 | 105 | return string(bytes), err 106 | } 107 | -------------------------------------------------------------------------------- /internal/pipe/store/store_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package store 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/internal/client/clienttest" 27 | "github.com/cidertool/cider/internal/pipe" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func TestStore_Happy(t *testing.T) { 34 | t.Parallel() 35 | 36 | ctx := context.New(config.Project{ 37 | "TEST": { 38 | BundleID: "com.test.TEST", 39 | Versions: config.Version{ 40 | PhasedReleaseEnabled: true, 41 | IDFADeclaration: &config.IDFADeclaration{ 42 | HonorsLimitedAdTracking: true, 43 | }, 44 | RoutingCoverage: &config.File{ 45 | Path: "TEST", 46 | }, 47 | ReviewDetails: &config.ReviewDetails{ 48 | Contact: &config.ContactPerson{ 49 | Email: "test@example.com", 50 | FirstName: "Person", 51 | LastName: "Personson", 52 | Phone: "1555555555", 53 | }, 54 | DemoAccount: &config.DemoAccount{}, 55 | Notes: "TEST", 56 | Attachments: []config.File{ 57 | {Path: "TEST"}, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }) 63 | ctx.AppsToRelease = []string{"TEST"} 64 | 65 | p := Pipe{} 66 | p.Client = &clienttest.Client{} 67 | 68 | assert.Equal(t, "committing to app store", p.String()) 69 | 70 | err := p.Publish(ctx) 71 | assert.NoError(t, err) 72 | } 73 | 74 | func TestStore_Happy_Skips(t *testing.T) { 75 | t.Parallel() 76 | 77 | ctx := context.New(config.Project{ 78 | "TEST": { 79 | BundleID: "com.test.TEST", 80 | }, 81 | }) 82 | ctx.AppsToRelease = []string{"TEST"} 83 | ctx.SkipUpdatePricing = true 84 | ctx.SkipUpdateMetadata = true 85 | ctx.SkipSubmit = true 86 | 87 | p := Pipe{} 88 | p.Client = &clienttest.Client{} 89 | 90 | err := p.Publish(ctx) 91 | assert.EqualError(t, err, pipe.ErrSkipSubmitEnabled.Error()) 92 | } 93 | 94 | func TestStore_Happy_NoApps(t *testing.T) { 95 | t.Parallel() 96 | 97 | ctx := context.New(config.Project{}) 98 | ctx.Credentials = &clienttest.Credentials{} 99 | 100 | p := Pipe{} 101 | 102 | err := p.Publish(ctx) 103 | assert.NoError(t, err) 104 | } 105 | -------------------------------------------------------------------------------- /tools/gendoc/md.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | 29 | "github.com/apex/log" 30 | "github.com/cidertool/cider/internal/clicommand" 31 | "github.com/spf13/cobra" 32 | "github.com/spf13/cobra/doc" 33 | ) 34 | 35 | const docsMdFrontmatterTemplate = `--- 36 | layout: page 37 | parent: Commands 38 | title: %s 39 | nav_order: %d 40 | nav_exclude: %t 41 | --- 42 | 43 | ` 44 | 45 | type pageNavField struct { 46 | order int 47 | exclude bool 48 | } 49 | 50 | func runDocsMdCmd(cmd *cobra.Command, args []string) error { 51 | var orderRoot, orderInit, orderRelease, orderCheck, orderCompletions = 0, 1, 2, 3, 4 52 | 53 | var pageNavFields = map[string]pageNavField{ 54 | "cider.md": {order: orderRoot}, 55 | "cider_init.md": {order: orderInit}, 56 | "cider_release.md": {order: orderRelease}, 57 | "cider_check.md": {order: orderCheck}, 58 | "cider_completions.md": {order: orderCompletions}, 59 | } 60 | 61 | var dir string 62 | if len(args) == 0 { 63 | dir = defaultDocsPath 64 | } else { 65 | dir = args[0] 66 | } 67 | 68 | dir = filepath.Join(dir, "commands") 69 | 70 | prepender := func(filename string) string { 71 | base := filepath.Base(filename) 72 | 73 | return fmt.Sprintf(docsMdFrontmatterTemplate, pageTitle(base), pageNavFields[base].order, pageNavFields[base].exclude) 74 | } 75 | 76 | linkHandler := func(name string) string { 77 | base := strings.TrimSuffix(name, filepath.Ext(name)) 78 | 79 | return "/commands/" + strings.ToLower(base) + "/" 80 | } 81 | 82 | log.WithField("path", dir).Info("generating Markdown documentation") 83 | 84 | err := doc.GenMarkdownTreeCustom(clicommand.NewRoot("dev", os.Exit).Cmd, dir, prepender, linkHandler) 85 | if err != nil { 86 | log.Error("generation failed") 87 | } else { 88 | log.Info("generation completed successfully") 89 | } 90 | 91 | return err 92 | } 93 | 94 | func pageTitle(s string) string { 95 | s = strings.TrimSuffix(s, filepath.Ext(s)) 96 | if s != "cider" { 97 | s = strings.ReplaceAll(s, "cider", "") 98 | } 99 | 100 | s = strings.ReplaceAll(s, "_", " ") 101 | s = strings.TrimSpace(s) 102 | 103 | return s 104 | } 105 | -------------------------------------------------------------------------------- /tools/licensing/main.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "os" 25 | "path/filepath" 26 | "strings" 27 | 28 | "github.com/apex/log" 29 | "github.com/spf13/cobra" 30 | ) 31 | 32 | const licenseHeader = `/** 33 | Copyright (C) 2020 Aaron Sky. 34 | 35 | This file is part of Cider, a tool for automating submission 36 | of apps to Apple's App Stores. 37 | 38 | Cider is free software: you can redistribute it and/or modify 39 | it under the terms of the GNU General Public License as published by 40 | the Free Software Foundation, either version 3 of the License, or 41 | (at your option) any later version. 42 | 43 | Cider is distributed in the hope that it will be useful, 44 | but WITHOUT ANY WARRANTY; without even the implied warranty of 45 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 46 | GNU General Public License for more details. 47 | 48 | You should have received a copy of the GNU General Public License 49 | along with Cider. If not, see . 50 | */` 51 | 52 | func main() { 53 | var cmd = &cobra.Command{ 54 | Use: "licensing", 55 | Short: "Ensure every source file in the repo contains a license header", 56 | Args: cobra.MaximumNArgs(1), 57 | DisableAutoGenTag: true, 58 | RunE: runLicensing, 59 | } 60 | 61 | cmd.SetArgs(os.Args[1:]) 62 | 63 | if err := cmd.Execute(); err != nil { 64 | var code = 1 65 | 66 | var msg = "command failed" 67 | 68 | log.WithError(err).Error(msg) 69 | 70 | os.Exit(code) 71 | } 72 | } 73 | 74 | func runLicensing(cmd *cobra.Command, args []string) error { 75 | return filepath.Walk(".", checkFile) 76 | } 77 | 78 | func checkFile(path string, info os.FileInfo, err error) error { 79 | if err != nil { 80 | return err 81 | } 82 | 83 | if filepath.Ext(path) != ".go" { 84 | return nil 85 | } 86 | 87 | f, err := os.ReadFile(path) // #nosec 88 | if err != nil { 89 | return err 90 | } 91 | 92 | source := string(f) 93 | 94 | if !strings.HasPrefix(source, licenseHeader) { 95 | source = licenseHeader + "\n\n" + source 96 | 97 | err := os.WriteFile(path, []byte(source), info.Mode()) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | log.Info(path) 103 | } 104 | 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /tools/gendoc/main.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "os" 25 | 26 | "github.com/apex/log" 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | const defaultDocsPath = "docs" 31 | 32 | type rootCmd struct { 33 | cmd *cobra.Command 34 | exit func(int) 35 | } 36 | 37 | func main() { 38 | var root = &rootCmd{ 39 | exit: os.Exit, 40 | } 41 | 42 | var cmd = &cobra.Command{ 43 | Use: "gendoc", 44 | Short: "Generate documentation for Cider", 45 | Args: cobra.MaximumNArgs(1), 46 | SilenceUsage: true, 47 | SilenceErrors: true, 48 | DisableAutoGenTag: true, 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | for _, sub := range cmd.Commands() { 51 | if sub.Name() == "help" { 52 | continue 53 | } 54 | if err := sub.RunE(sub, args); err != nil { 55 | return err 56 | } 57 | } 58 | 59 | return nil 60 | }, 61 | } 62 | 63 | cmd.AddCommand( 64 | cmdConfig(), 65 | cmdMan(), 66 | cmdMarkdown(), 67 | ) 68 | 69 | root.cmd = cmd 70 | 71 | root.Execute(os.Args[1:]) 72 | } 73 | 74 | func (cmd *rootCmd) Execute(args []string) { 75 | cmd.cmd.SetArgs(args) 76 | 77 | if err := cmd.cmd.Execute(); err != nil { 78 | var code = 1 79 | 80 | var msg = "command failed" 81 | 82 | log.WithError(err).Error(msg) 83 | 84 | cmd.exit(code) 85 | } 86 | } 87 | 88 | // CmdConfig returns the cobra.Command for the man subcommand. 89 | func cmdConfig() *cobra.Command { 90 | return &cobra.Command{ 91 | Use: "config", 92 | Short: "Generate configuration file documentation for Cider.", 93 | Args: cobra.MaximumNArgs(1), 94 | RunE: runDocsConfigCmd, 95 | } 96 | } 97 | 98 | // CmdMan returns the cobra.Command for the man subcommand. 99 | func cmdMan() *cobra.Command { 100 | return &cobra.Command{ 101 | Use: "man", 102 | Short: "Generate man documentation for Cider.", 103 | Args: cobra.MaximumNArgs(1), 104 | RunE: runDocsManCmd, 105 | } 106 | } 107 | 108 | // CmdMarkdown returns the cobra.Command for the man subcommand. 109 | func cmdMarkdown() *cobra.Command { 110 | return &cobra.Command{ 111 | Use: "md", 112 | Short: "Generate Markdown documentation for Cider.", 113 | Args: cobra.MaximumNArgs(1), 114 | RunE: runDocsMdCmd, 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | tags: 7 | - v* 8 | paths-ignore: 9 | - '**.md' 10 | pull_request: 11 | paths-ignore: 12 | - '**.md' 13 | 14 | env: 15 | GO111MODULE: on 16 | 17 | jobs: 18 | tests: 19 | name: unit tests 20 | strategy: 21 | matrix: 22 | platform: 23 | - ubuntu-latest 24 | - windows-latest 25 | runs-on: ${{ matrix.platform }} 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - uses: actions/setup-go@v1 30 | with: 31 | go-version: '1.16' 32 | 33 | - name: Cache Go Modules 34 | uses: actions/cache@v2 35 | with: 36 | path: ~/go/pkg/mod 37 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 38 | restore-keys: | 39 | ${{ runner.os }}-go- 40 | 41 | - name: Run Tests 42 | run: go test -race -coverprofile coverage.out -covermode atomic ./... 43 | 44 | - name: Upload Coverage to Codecov 45 | if: success() 46 | uses: codecov/codecov-action@v1 47 | with: 48 | file: ./coverage.out 49 | 50 | lint: 51 | name: lint 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v2 55 | 56 | - uses: actions/setup-go@v1 57 | with: 58 | go-version: '1.16' 59 | 60 | - name: golangci-lint 61 | uses: golangci/golangci-lint-action@master 62 | with: 63 | version: v1.39 64 | skip-go-installation: true 65 | 66 | verify_doc_tools: 67 | name: verify doc tools 68 | runs-on: ubuntu-latest 69 | steps: 70 | - uses: actions/checkout@v2 71 | 72 | - uses: actions/setup-go@v1 73 | with: 74 | go-version: '1.16' 75 | 76 | - name: Run gendoc tool 77 | run: go run ./tools/gendoc 78 | 79 | - name: Run licensing tool 80 | run: go run ./tools/licensing 81 | 82 | - name: Check working copy state 83 | run: git diff-index --quiet HEAD || git status --short 84 | 85 | release: 86 | name: release 87 | if: startsWith(github.ref, 'refs/tags/') 88 | needs: 89 | - tests 90 | - lint 91 | runs-on: ubuntu-latest 92 | steps: 93 | - uses: actions/checkout@v2 94 | with: 95 | fetch-depth: 0 96 | 97 | - uses: actions/setup-go@v1 98 | with: 99 | go-version: '1.16' 100 | 101 | - name: Login to Docker Hub 102 | run: | 103 | echo "${DOCKER_HUB_TOKEN}" | \ 104 | docker login --username "${DOCKER_HUB_LOGIN}" --password-stdin 105 | env: 106 | DOCKER_HUB_LOGIN: ${{ secrets.DOCKER_HUB_LOGIN }} 107 | DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} 108 | 109 | - name: GoReleaser 110 | uses: goreleaser/goreleaser-action@v2 111 | with: 112 | version: latest 113 | args: release --rm-dist 114 | env: 115 | GITHUB_TOKEN: ${{ secrets.BREW_GITHUB_TOKEN }} 116 | 117 | - name: Clear 118 | if: always() 119 | run: rm -f ${HOME}/.docker/config.json 120 | -------------------------------------------------------------------------------- /internal/template/template.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package template provides an interface for text templates to be used during pipes 22 | package template 23 | 24 | import ( 25 | "bytes" 26 | "path/filepath" 27 | "strings" 28 | "text/template" 29 | "time" 30 | 31 | "github.com/cidertool/cider/pkg/context" 32 | ) 33 | 34 | const ( 35 | versionKey = "version" 36 | envKey = "env" 37 | dateKey = "date" 38 | timestampKey = "timestamp" 39 | ) 40 | 41 | // Template is used to apply text templates to strings to dynamically configure API values. See the documentation of 42 | // text/template to see the valid template format. 43 | type Template struct { 44 | fields Fields 45 | } 46 | 47 | // Fields is a heterogenous map type keyed by strings. 48 | type Fields map[string]interface{} 49 | 50 | // New returns a new template instance. 51 | func New(ctx *context.Context) *Template { 52 | return &Template{ 53 | Fields{ 54 | versionKey: ctx.Version, 55 | envKey: ctx.Env, 56 | dateKey: ctx.Date.UTC().Format(time.RFC3339), 57 | timestampKey: ctx.Date.UTC().Unix(), 58 | }, 59 | } 60 | } 61 | 62 | // WithFields merges the template's configured fields with the given Fields. 63 | func (t *Template) WithFields(fields Fields) *Template { 64 | for key, value := range fields { 65 | t.fields[key] = value 66 | } 67 | 68 | return t 69 | } 70 | 71 | // WithEnv replaces the configured env of the template with the given key-value map. 72 | func (t *Template) WithEnv(env map[string]string) *Template { 73 | t.fields[envKey] = env 74 | 75 | return t 76 | } 77 | 78 | // WithShellEnv replaces the configured env of the template with the given sequence of shell-style, e.g. "KEY=VALUE", 79 | // strings. 80 | func (t *Template) WithShellEnv(envs ...string) *Template { 81 | env := make(map[string]string) 82 | 83 | for _, e := range envs { 84 | parts := strings.SplitN(e, "=", 2) 85 | env[parts[0]] = parts[1] 86 | } 87 | 88 | return t.WithEnv(env) 89 | } 90 | 91 | // Apply takes the template string and processes it into its product string. 92 | func (t *Template) Apply(s string) (string, error) { 93 | var out bytes.Buffer 94 | 95 | tmpl, err := template.New("tmpl"). 96 | Option("missingkey=error"). 97 | Funcs(template.FuncMap{ 98 | "replace": strings.ReplaceAll, 99 | "lowercased": strings.ToLower, 100 | "uppercased": strings.ToUpper, 101 | "titlecased": strings.ToTitle, 102 | "dir": filepath.Dir, 103 | "abs": filepath.Abs, 104 | "rel": filepath.Rel, 105 | }). 106 | Parse(s) 107 | if err != nil { 108 | return "", err 109 | } 110 | 111 | err = tmpl.Execute(&out, t.fields) 112 | 113 | return out.String(), err 114 | } 115 | -------------------------------------------------------------------------------- /internal/pipe/publish/publish_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package publish 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/internal/client/clienttest" 27 | "github.com/cidertool/cider/internal/pipe" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func TestPublish_String(t *testing.T) { 34 | t.Parallel() 35 | 36 | p := Pipe{} 37 | assert.Equal(t, "publishing from app store connect", p.String()) 38 | } 39 | 40 | func TestPublish_Happy_Testflight(t *testing.T) { 41 | t.Parallel() 42 | 43 | ctx := context.New(config.Project{ 44 | "TEST": {}, 45 | }) 46 | ctx.AppsToRelease = []string{"TEST"} 47 | ctx.Credentials = &clienttest.Credentials{} 48 | ctx.PublishMode = context.PublishModeTestflight 49 | 50 | p := Pipe{} 51 | p.client = &clienttest.Client{} 52 | 53 | err := p.Run(ctx) 54 | assert.NoError(t, err) 55 | } 56 | 57 | func TestPublish_Happy_Store(t *testing.T) { 58 | t.Parallel() 59 | 60 | ctx := context.New(config.Project{ 61 | "TEST": {}, 62 | }) 63 | ctx.AppsToRelease = []string{"TEST"} 64 | ctx.Credentials = &clienttest.Credentials{} 65 | ctx.PublishMode = context.PublishModeAppStore 66 | 67 | p := Pipe{} 68 | p.client = &clienttest.Client{} 69 | 70 | err := p.Run(ctx) 71 | assert.NoError(t, err) 72 | } 73 | 74 | func TestPublish_Happy_NoApps(t *testing.T) { 75 | t.Parallel() 76 | 77 | ctx := context.New(config.Project{}) 78 | 79 | p := Pipe{} 80 | 81 | err := p.Run(ctx) 82 | assert.EqualError(t, err, pipe.ErrSkipNoAppsToPublish.Error()) 83 | } 84 | 85 | func TestPublish_Err_NoPublishMode(t *testing.T) { 86 | t.Parallel() 87 | 88 | ctx := context.New(config.Project{}) 89 | ctx.AppsToRelease = []string{"TEST"} 90 | 91 | p := Pipe{} 92 | 93 | err := p.Run(ctx) 94 | assert.EqualError(t, err, errUnsupportedPublishMode{context.PublishMode("")}.Error()) 95 | } 96 | 97 | func TestPublish_Err_AppMismatchTestflight(t *testing.T) { 98 | t.Parallel() 99 | 100 | ctx := context.New(config.Project{ 101 | "TEST_": {}, 102 | }) 103 | ctx.AppsToRelease = []string{"_TEST"} 104 | ctx.Credentials = &clienttest.Credentials{} 105 | ctx.PublishMode = context.PublishModeTestflight 106 | 107 | p := Pipe{} 108 | p.client = &clienttest.Client{} 109 | 110 | err := p.Run(ctx) 111 | assert.Error(t, err) 112 | } 113 | 114 | func TestPublish_Err_AppMismatchStore(t *testing.T) { 115 | t.Parallel() 116 | 117 | ctx := context.New(config.Project{ 118 | "TEST_": {}, 119 | }) 120 | ctx.AppsToRelease = []string{"_TEST"} 121 | ctx.Credentials = &clienttest.Credentials{} 122 | ctx.PublishMode = context.PublishModeAppStore 123 | 124 | p := Pipe{} 125 | p.client = &clienttest.Client{} 126 | 127 | err := p.Run(ctx) 128 | assert.Error(t, err) 129 | } 130 | -------------------------------------------------------------------------------- /pkg/config/config_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package config 22 | 23 | import ( 24 | "errors" 25 | "strings" 26 | "testing" 27 | 28 | "github.com/stretchr/testify/assert" 29 | ) 30 | 31 | var errTestError = errors.New("test error") 32 | 33 | func TestValidConfiguration(t *testing.T) { 34 | t.Parallel() 35 | 36 | f, err := Load("testdata/valid.yml") 37 | assert.NoError(t, err) 38 | assert.Len(t, f, 1) 39 | } 40 | 41 | func TestMissingConfiguration(t *testing.T) { 42 | t.Parallel() 43 | 44 | _, err := Load("testdata/doesnotexist.yml") 45 | assert.Error(t, err) 46 | } 47 | 48 | func TestInvalidConfiguration(t *testing.T) { 49 | t.Parallel() 50 | 51 | _, err := Load("testdata/invalid.yml") 52 | assert.Error(t, err) 53 | } 54 | 55 | func TestMarshalledIsValidConfiguration(t *testing.T) { 56 | t.Parallel() 57 | 58 | f, err := Load("testdata/valid.yml") 59 | assert.NoError(t, err) 60 | str, err := f.String() 61 | assert.NoError(t, err) 62 | f2, err := LoadReader(strings.NewReader(str)) 63 | assert.NoError(t, err) 64 | assert.Equal(t, f, f2) 65 | } 66 | 67 | func TestBrokenFile(t *testing.T) { 68 | t.Parallel() 69 | 70 | _, err := LoadReader(errReader(0)) 71 | assert.Error(t, err) 72 | } 73 | 74 | type errReader int 75 | 76 | func (errReader) Read(p []byte) (int, error) { 77 | return 0, errTestError 78 | } 79 | 80 | func TestCopy(t *testing.T) { 81 | t.Parallel() 82 | 83 | p := Project{ 84 | "App1": {}, 85 | "App2": {}, 86 | "App3": {}, 87 | } 88 | pPrime, err := p.Copy() 89 | assert.NoError(t, err) 90 | assert.Equal(t, p, pPrime) 91 | assert.NotSame(t, p, pPrime) 92 | } 93 | 94 | func TestCopy_Err(t *testing.T) { 95 | t.Parallel() 96 | 97 | p := Project{ 98 | "App1": {}, 99 | "App2": {}, 100 | "App3": {}, 101 | } 102 | pPrime, err := p.Copy() 103 | assert.NoError(t, err) 104 | assert.Equal(t, p, pPrime) 105 | assert.NotSame(t, p, pPrime) 106 | } 107 | 108 | func TestAppsMatching(t *testing.T) { 109 | t.Parallel() 110 | 111 | p := Project{ 112 | "App1": {}, 113 | "App2": {}, 114 | "App3": {}, 115 | } 116 | 117 | var matches []string 118 | matches = p.AppsMatching([]string{"App1", "App2", "App3"}, false) 119 | assert.ElementsMatch(t, matches, []string{"App1", "App2", "App3"}) 120 | matches = p.AppsMatching([]string{"App1", "App2"}, false) 121 | assert.ElementsMatch(t, matches, []string{"App1", "App2"}) 122 | matches = p.AppsMatching([]string{"App1", "App2", "App4"}, false) 123 | assert.ElementsMatch(t, matches, []string{"App1", "App2"}) 124 | matches = p.AppsMatching([]string{"", ""}, false) 125 | assert.ElementsMatch(t, matches, []string{}) 126 | matches = p.AppsMatching([]string{"App1", "App2", "App4"}, true) 127 | assert.ElementsMatch(t, matches, []string{"App1", "App2", "App3"}) 128 | matches = p.AppsMatching([]string{}, true) 129 | assert.ElementsMatch(t, matches, []string{"App1", "App2", "App3"}) 130 | } 131 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod download 4 | 5 | builds: 6 | - main: ./cmd/cider 7 | env: 8 | - CGO_ENABLED=0 9 | goos: 10 | - darwin 11 | - freebsd 12 | - linux 13 | - openbsd 14 | - windows 15 | goarch: 16 | - amd64 17 | - arm64 18 | mod_timestamp: '{{ .CommitTimestamp }}' 19 | flags: 20 | - -trimpath 21 | ldflags: 22 | - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} -X main.builtBy=cidertool 23 | 24 | checksum: 25 | name_template: 'checksums.txt' 26 | 27 | snapshot: 28 | name_template: '{{ .Tag }}-next' 29 | 30 | changelog: 31 | sort: asc 32 | filters: 33 | exclude: 34 | - '^docs:' 35 | - '^test:' 36 | - Merge pull request 37 | - Merge branch 38 | - go mod tidy 39 | 40 | dockers: 41 | - dockerfile: build/Dockerfile 42 | image_templates: 43 | - cidertool/cider:{{ .Tag }} 44 | - cidertool/cider:v{{ .Major }} 45 | - cidertool/cider:v{{ .Major }}.{{ .Minor }} 46 | - cidertool/cider:latest 47 | ids: 48 | - cider 49 | extra_files: 50 | - build/entrypoint.sh 51 | build_flag_templates: 52 | - --pull 53 | - --label=org.opencontainers.image.created={{ .Date }} 54 | - --label=org.opencontainers.image.name={{ .ProjectName }} 55 | - --label=org.opencontainers.image.revision={{ .FullCommit }} 56 | - --label=org.opencontainers.image.version={{ .Version }} 57 | - --label=org.opencontainers.image.source={{ .GitURL }} 58 | - --label=repository=http://github.com/cidertool/cider 59 | - --label=homepage=http://cidertool.github.io/cider 60 | - --label=maintainer=Aaron Sky 61 | 62 | archives: 63 | - name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 64 | replacements: 65 | amd64: x86_64 66 | format_overrides: 67 | - goos: windows 68 | format: zip 69 | 70 | brews: 71 | - name: cider 72 | description: Submit your builds to the Apple App Store in seconds 73 | homepage: https://cidertool.github.io/cider 74 | tap: 75 | owner: cidertool 76 | name: homebrew-tap 77 | folder: Formula 78 | commit_author: 79 | name: cider-bot 80 | email: cider@skyaaron.com 81 | test: | 82 | system "#{bin}/cider", "-v" 83 | # - name: cider-core 84 | # description: Submit your builds to the Apple App Store in seconds 85 | # homepage: https://cidertool.github.io/cider 86 | # tap: 87 | # owner: cidertool 88 | # name: homebrew-tap 89 | # folder: Formula 90 | # commit_author: 91 | # name: cider-bot 92 | # email: cider@skyaaron.com 93 | # dependencies: 94 | # - name: go 95 | # type: build 96 | # install: | 97 | # system "go", "build", "-ldflags", 98 | # "-s -w -X main.version=#{version} -X main.commit=#{stable.specs[:revision]} -X main.builtBy=homebrew", 99 | # *std_go_args 100 | # man1.install "docs/man/cider.1" 101 | # man1.install "docs/man/cider_init.1" 102 | # man1.install "docs/man/cider_release.1" 103 | # man1.install "docs/man/cider_check.1" 104 | # man1.install "docs/man/cider_completions.1" 105 | # test: | 106 | # system "#{bin}/cider", "-v" 107 | 108 | scoop: 109 | description: Submit your builds to the Apple App Store in seconds 110 | homepage: https://cidertool.github.io/cider 111 | license: GPL-3.0-or-later 112 | bucket: 113 | owner: cidertool 114 | name: scoop-bucket 115 | commit_author: 116 | name: cider-bot 117 | email: cider@skyaaron.com 118 | -------------------------------------------------------------------------------- /internal/client/clienttest/clienttest_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package clienttest_test 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/internal/client/clienttest" 27 | "github.com/cidertool/cider/pkg/config" 28 | "github.com/cidertool/cider/pkg/context" 29 | "github.com/stretchr/testify/assert" 30 | ) 31 | 32 | func TestClient(t *testing.T) { 33 | t.Parallel() 34 | 35 | ctx := context.New(config.Project{}) 36 | 37 | c := clienttest.Client{} 38 | 39 | app, err := c.GetAppForBundleID(ctx, "TEST") 40 | assert.NoError(t, err) 41 | assert.NotNil(t, app) 42 | 43 | info, err := c.GetAppInfo(ctx, "TEST") 44 | assert.NoError(t, err) 45 | assert.NotNil(t, info) 46 | 47 | build, err := c.GetBuild(ctx, nil) 48 | assert.NoError(t, err) 49 | assert.NotNil(t, build) 50 | 51 | initial, err := c.ReleaseForAppIsInitial(ctx, "TEST") 52 | assert.NoError(t, err) 53 | assert.False(t, initial) 54 | 55 | err = c.UpdateBetaAppLocalizations(ctx, "TEST", config.TestflightLocalizations{}) 56 | assert.NoError(t, err) 57 | 58 | err = c.UpdateBetaBuildDetails(ctx, "TEST", config.Testflight{}) 59 | assert.NoError(t, err) 60 | 61 | err = c.UpdateBetaBuildLocalizations(ctx, "TEST", config.TestflightLocalizations{}) 62 | assert.NoError(t, err) 63 | 64 | err = c.UpdateBetaLicenseAgreement(ctx, "TEST", config.Testflight{}) 65 | assert.NoError(t, err) 66 | 67 | err = c.AssignBetaGroups(ctx, "TEST", "TEST", []config.BetaGroup{}) 68 | assert.NoError(t, err) 69 | 70 | err = c.AssignBetaTesters(ctx, "TEST", "TEST", []config.BetaTester{}) 71 | assert.NoError(t, err) 72 | 73 | err = c.UpdateBetaReviewDetails(ctx, "TEST", config.ReviewDetails{}) 74 | assert.NoError(t, err) 75 | 76 | err = c.SubmitBetaApp(ctx, "TEST") 77 | assert.NoError(t, err) 78 | 79 | err = c.UpdateApp(ctx, "TEST", "TEST", "TEST", config.App{}) 80 | assert.NoError(t, err) 81 | 82 | err = c.UpdateAppLocalizations(ctx, "TEST", config.AppLocalizations{}) 83 | assert.NoError(t, err) 84 | 85 | version, err := c.CreateVersionIfNeeded(ctx, "TEST", "TEST", config.Version{}) 86 | assert.NoError(t, err) 87 | assert.NotNil(t, version) 88 | 89 | err = c.UpdateVersionLocalizations(ctx, "TEST", config.VersionLocalizations{}) 90 | assert.NoError(t, err) 91 | 92 | err = c.UpdateIDFADeclaration(ctx, "TEST", config.IDFADeclaration{}) 93 | assert.NoError(t, err) 94 | 95 | err = c.UploadRoutingCoverage(ctx, "TEST", config.File{}) 96 | assert.NoError(t, err) 97 | 98 | err = c.UpdateReviewDetails(ctx, "TEST", config.ReviewDetails{}) 99 | assert.NoError(t, err) 100 | 101 | err = c.EnablePhasedRelease(ctx, "TEST") 102 | assert.NoError(t, err) 103 | 104 | err = c.SubmitApp(ctx, "TEST") 105 | assert.NoError(t, err) 106 | 107 | proj, err := c.Project() 108 | assert.NoError(t, err) 109 | assert.NotNil(t, proj) 110 | } 111 | 112 | func TestCredentials(t *testing.T) { 113 | t.Parallel() 114 | 115 | c := clienttest.Credentials{} 116 | assert.NotNil(t, c.Client()) 117 | } 118 | -------------------------------------------------------------------------------- /internal/pipe/env/env_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package env 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | "testing" 27 | 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func TestEnv(t *testing.T) { 34 | t.Parallel() 35 | 36 | ctx := context.New(config.Project{}) 37 | 38 | pipe := Pipe{} 39 | 40 | var err error 41 | 42 | assert.Equal(t, "loading environment variables", pipe.String()) 43 | 44 | err = pipe.Run(ctx) 45 | assert.Error(t, err) 46 | 47 | // Pass with ASC_KEY_ID but fail on ASC_ISSUER_ID 48 | err = os.Setenv("ASC_KEY_ID", "TEST") 49 | assert.NoError(t, err) 50 | err = pipe.Run(ctx) 51 | assert.Error(t, err) 52 | 53 | // Pass with ASC_ISSUER_ID but fail on ASC_PRIVATE_KEY_PATH 54 | err = os.Setenv("ASC_ISSUER_ID", "TEST") 55 | assert.NoError(t, err) 56 | err = pipe.Run(ctx) 57 | assert.Error(t, err) 58 | 59 | // Check ASC_PRIVATE_KEY_PATH but fail because nothing exists at that path 60 | err = os.Setenv("ASC_PRIVATE_KEY_PATH", "TEST") 61 | assert.NoError(t, err) 62 | err = pipe.Run(ctx) 63 | assert.Error(t, err) 64 | 65 | // Check ASC_PRIVATE_KEY_PATH but silently fail because it isn't required 66 | err = os.Setenv("TEST", "path/to/no/file") 67 | assert.NoError(t, err) 68 | env, err := loadEnvFromPath("TEST", false) 69 | assert.NoError(t, err) 70 | assert.Empty(t, env) 71 | 72 | file, err := os.CreateTemp("", "fake_key") 73 | if err != nil { 74 | assert.FailNow(t, "temp file creation produced an error", err) 75 | } 76 | 77 | defer rmFile(file) 78 | 79 | // Check ASC_PRIVATE_KEY_PATH but fail because the contents of the file do not contain a real key 80 | err = os.Setenv("ASC_PRIVATE_KEY_PATH", file.Name()) 81 | assert.NoError(t, err) 82 | err = pipe.Run(ctx) 83 | assert.Error(t, err) 84 | 85 | // This key is a mock key generated by the following command: 86 | // 87 | // openssl ecparam -name prime256v1 -genkey -noout | openssl pkcs8 -topk8 -nocrypt -out key.pem 88 | // 89 | // This will generate the ASN.1 PKCS#8 representation of the private key needed 90 | // to create a valid token. If you are looking at this test to see how to make a key, 91 | // reference Apple's documentation on this subject instead. 92 | // 93 | // https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api 94 | key := ` 95 | -----BEGIN PRIVATE KEY----- 96 | MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgELiCwZa9oGedoUR7 97 | 8Vr36M6WOkEBGZh2YsUVL0kCIJ6hRANCAASQtP/ZdZBW6UdwJeyz09ws2nx5OOUA 98 | tra43bY9mLeVK0zrTn/3jvjTHEdD3HcRJgau1jshXG4IHXSW9yXj9x3V 99 | -----END PRIVATE KEY----- 100 | ` 101 | _, err = file.WriteString(key) 102 | assert.NoError(t, err) 103 | 104 | // Pass with ASC_PRIVATE_KEY_PATH, and create the credentials object 105 | err = pipe.Run(ctx) 106 | assert.NoError(t, err) 107 | assert.NotNil(t, ctx.Credentials) 108 | } 109 | 110 | // rmFile closes an open descriptor. 111 | func rmFile(f *os.File) { 112 | if err := os.Remove(f.Name()); err != nil { 113 | // nolint: forbidigo 114 | fmt.Println(err) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /internal/shell/shelltest/shelltest.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package shelltest provides utilities for mocking the login shell. 22 | package shelltest 23 | 24 | import ( 25 | "fmt" 26 | "os/exec" 27 | "strconv" 28 | "testing" 29 | 30 | "github.com/cidertool/cider/internal/shell" 31 | "github.com/cidertool/cider/pkg/context" 32 | "github.com/stretchr/testify/assert" 33 | ) 34 | 35 | // ErrCommandOverflow happens when the command that is trying to be run would result in a 36 | // buffer overflow in the Shell mock. 37 | type ErrCommandOverflow struct { 38 | Index int 39 | Len int 40 | Command string 41 | } 42 | 43 | func (e ErrCommandOverflow) Error() string { 44 | return fmt.Sprintf("command out of bounds: i=%d,len=%d (command: `%s`)", e.Index, e.Len, e.Command) 45 | } 46 | 47 | // Shell is a type that conforms to shell.Shell. 48 | type Shell struct { 49 | T *testing.T 50 | Context *context.Context 51 | SupportedPrograms map[string]bool 52 | Commands []Command 53 | index int 54 | expectOverflowError bool 55 | } 56 | 57 | // Command represents the result of some executed command. 58 | type Command struct { 59 | ReturnCode int 60 | Stdout string 61 | Stderr string 62 | } 63 | 64 | // NewCommand takes a program and arguments and constructs a new exec.Cmd instance. 65 | func (sh *Shell) NewCommand(name string, arg ...string) *exec.Cmd { 66 | return exec.Command(name, arg...) // #nosec 67 | } 68 | 69 | // Exec executes the command. 70 | func (sh *Shell) Exec(cmd *exec.Cmd) (*shell.CompletedProcess, error) { 71 | if sh.index >= len(sh.Commands) { 72 | err := ErrCommandOverflow{ 73 | Index: sh.index, 74 | Len: len(sh.Commands), 75 | Command: cmd.String(), 76 | } 77 | 78 | if !sh.expectOverflowError { 79 | assert.FailNow(sh.T, err.Error()) 80 | } 81 | 82 | return nil, err 83 | } 84 | 85 | currentCommand := sh.Commands[sh.index] 86 | ps := shell.CompletedProcess{ 87 | Name: cmd.Path, 88 | Args: cmd.Args, 89 | ReturnCode: currentCommand.ReturnCode, 90 | Stdout: currentCommand.Stdout, 91 | Stderr: currentCommand.Stderr, 92 | } 93 | sh.index++ 94 | 95 | var err error 96 | if currentCommand.ReturnCode != 0 { 97 | err = &shellError{ 98 | Process: ps, 99 | } 100 | } 101 | 102 | return &ps, err 103 | } 104 | 105 | // Exists returns whether the given program exists. 106 | // 107 | // This implementation of the method returns true if the SupportedPrograms 108 | // field is empty, otherwise it checks the value of the key in that map. 109 | // Use SupportedPrograms to mock the existence of a program in the PATH. 110 | func (sh *Shell) Exists(program string) bool { 111 | if len(sh.SupportedPrograms) == 0 { 112 | return true 113 | } 114 | 115 | return sh.SupportedPrograms[program] 116 | } 117 | 118 | // CurrentDirectory returns the current directory. 119 | func (sh *Shell) CurrentDirectory() string { 120 | return sh.Context.CurrentDirectory 121 | } 122 | 123 | type shellError struct { 124 | Process shell.CompletedProcess 125 | } 126 | 127 | func (err *shellError) Error() string { 128 | return strconv.Itoa(err.Process.ReturnCode) 129 | } 130 | -------------------------------------------------------------------------------- /internal/shell/shell.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package shell wraps shell execution 22 | package shell 23 | 24 | import ( 25 | "bytes" 26 | "os/exec" 27 | "strings" 28 | 29 | "github.com/alessio/shellescape" 30 | "github.com/cidertool/cider/internal/log" 31 | "github.com/cidertool/cider/pkg/context" 32 | ) 33 | 34 | // Shell is an abstraction for shell-program execution meant to make 35 | // testing and client design easier. 36 | type Shell interface { 37 | NewCommand(program string, args ...string) *exec.Cmd 38 | Exec(cmd *exec.Cmd) (*CompletedProcess, error) 39 | Exists(program string) bool 40 | CurrentDirectory() string 41 | } 42 | 43 | // New returns a new shell bound to the provided context. 44 | func New(ctx *context.Context) Shell { 45 | return &loginShell{ctx} 46 | } 47 | 48 | // CompletedProcess represents a subshell execution that finished, and includes its arguments, return code, and 49 | // standard buffers as strings. 50 | type CompletedProcess struct { 51 | Name string 52 | Args []string 53 | ReturnCode int 54 | Stdout string 55 | Stderr string 56 | } 57 | 58 | func newCompletedProcess(cmd *exec.Cmd) *CompletedProcess { 59 | stdout, ok := cmd.Stdout.(*bytes.Buffer) 60 | if !ok { 61 | return nil 62 | } 63 | 64 | stderr, ok := cmd.Stderr.(*bytes.Buffer) 65 | if !ok { 66 | return nil 67 | } 68 | 69 | var stdoutString, stderrString string 70 | 71 | if stdout != nil { 72 | stdoutString = strings.TrimSpace(stdout.String()) 73 | } 74 | 75 | if stderr != nil { 76 | stderrString = strings.TrimSpace(stderr.String()) 77 | } 78 | 79 | return &CompletedProcess{ 80 | Name: cmd.Path, 81 | Args: cmd.Args, 82 | ReturnCode: cmd.ProcessState.ExitCode(), 83 | Stdout: stdoutString, 84 | Stderr: stderrString, 85 | } 86 | } 87 | 88 | // loginShell is an empty struct that implements shell.Shell with default 89 | // os.Exec subshell execution logic. 90 | type loginShell struct { 91 | *context.Context 92 | } 93 | 94 | // NewCommand takes a program name and series of arguments and constructs an 95 | // exec.Cmd object that can be manipulated and fed to Exec(). 96 | func (sh *loginShell) NewCommand(program string, arg ...string) *exec.Cmd { 97 | cmd := exec.CommandContext(sh.Context, program, escapeArgs(arg)...) // #nosec 98 | 99 | var stdout, stderr bytes.Buffer 100 | cmd.Stdout = &stdout 101 | cmd.Stderr = &stderr 102 | cmd.Dir = sh.Context.CurrentDirectory 103 | 104 | return cmd 105 | } 106 | 107 | func escapeArgs(args []string) []string { 108 | cp := make([]string, len(args)) 109 | 110 | for i, arg := range args { 111 | cp[i] = shellescape.Quote(arg) 112 | } 113 | 114 | return cp 115 | } 116 | 117 | // Exec executes a command. 118 | func (sh *loginShell) Exec(cmd *exec.Cmd) (proc *CompletedProcess, err error) { 119 | sh.Context.Log.WithField("args", cmd.Args).Debug(cmd.Path) 120 | 121 | err = cmd.Run() 122 | proc = newCompletedProcess(cmd) 123 | 124 | if proc == nil { 125 | sh.Context.Log.Debugf("last process failed to complete coherently") 126 | } else { 127 | sh.Context.Log.WithFields(log.Fields{ 128 | "code": proc.ReturnCode, 129 | "stdout": strings.TrimSpace(proc.Stdout), 130 | "stderr": strings.TrimSpace(proc.Stderr), 131 | }).Debugf("%s result", proc.Name) 132 | } 133 | 134 | return proc, err 135 | } 136 | 137 | // Exists returns whether or not a given program is installed. 138 | func (sh *loginShell) Exists(program string) bool { 139 | path, err := exec.LookPath(program) 140 | if err != nil { 141 | return false 142 | } 143 | 144 | return path != "" 145 | } 146 | 147 | func (sh *loginShell) CurrentDirectory() string { 148 | return sh.Context.CurrentDirectory 149 | } 150 | -------------------------------------------------------------------------------- /internal/client/util_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package client 22 | 23 | import ( 24 | "encoding/json" 25 | "fmt" 26 | "net/http" 27 | "net/http/httptest" 28 | "net/url" 29 | "os" 30 | "path" 31 | "path/filepath" 32 | "testing" 33 | 34 | "github.com/cidertool/cider/internal/log" 35 | "github.com/cidertool/cider/pkg/config" 36 | "github.com/cidertool/cider/pkg/context" 37 | "github.com/stretchr/testify/assert" 38 | ) 39 | 40 | type response struct { 41 | StatusCode int 42 | RawResponse string 43 | Response interface{} 44 | } 45 | 46 | type testContext struct { 47 | Context *context.Context 48 | Responses []response 49 | CurrentResponseIndex int 50 | server *httptest.Server 51 | } 52 | 53 | type mockCredentials struct { 54 | url string 55 | client *http.Client 56 | } 57 | 58 | type mockTransport struct { 59 | URL *url.URL 60 | Transport http.RoundTripper 61 | } 62 | 63 | type testAsset struct { 64 | Name string 65 | Size int64 66 | } 67 | 68 | func newTestContext(resp ...response) (*testContext, Client) { 69 | ctx := testContext{} 70 | 71 | ctx.Context = context.New(config.Project{}) 72 | 73 | server := httptest.NewServer(&ctx) 74 | ctx.Context.Credentials = &mockCredentials{ 75 | url: server.URL, 76 | client: server.Client(), 77 | } 78 | ctx.Responses = resp 79 | ctx.server = server 80 | client := New(ctx.Context) 81 | 82 | return &ctx, client 83 | } 84 | 85 | func (c *testContext) Close() { 86 | c.server.Close() 87 | } 88 | 89 | func (c *testContext) ServeHTTP(w http.ResponseWriter, r *http.Request) { 90 | if c.CurrentResponseIndex >= len(c.Responses) { 91 | c.Context.Log.WithFields(log.Fields{ 92 | "currentResponseIndex": c.CurrentResponseIndex, 93 | "responsesCount": len(c.Responses), 94 | "url": r.URL, 95 | }).Fatal("index out of bounds") 96 | } 97 | 98 | c.Context.Log.WithFields(log.Fields{ 99 | "progress": fmt.Sprintf("(%d/%d)", c.CurrentResponseIndex, len(c.Responses)), 100 | "req_method": r.Method, 101 | "req_url": r.URL, 102 | }).Info("responding") 103 | 104 | resp := c.Responses[c.CurrentResponseIndex] 105 | 106 | if resp.StatusCode == 0 { 107 | resp.StatusCode = http.StatusOK 108 | } 109 | 110 | w.WriteHeader(resp.StatusCode) 111 | 112 | var body = resp.RawResponse 113 | 114 | if resp.Response != nil { 115 | b, err := json.Marshal(resp.Response) 116 | if err == nil { 117 | body = string(b) 118 | } 119 | } 120 | 121 | if body == "" { 122 | body = `{}` 123 | } 124 | 125 | fmt.Fprintln(w, body) 126 | c.CurrentResponseIndex++ 127 | } 128 | 129 | func (c *testContext) SetResponses(resp ...response) { 130 | c.Responses = resp 131 | c.CurrentResponseIndex = 0 132 | } 133 | 134 | func (c *testContext) URL(rawpath string) (*url.URL, error) { 135 | return url.Parse(c.server.URL + "/" + rawpath) 136 | } 137 | 138 | func (c *testAsset) URL(ctx *testContext, id string) (*url.URL, error) { 139 | return ctx.URL(id) 140 | } 141 | 142 | func (c *mockCredentials) Client() *http.Client { 143 | url, _ := url.Parse(c.url) 144 | c.client.Transport = &mockTransport{URL: url, Transport: c.client.Transport} 145 | 146 | return c.client 147 | } 148 | 149 | func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 150 | newURL := *t.URL 151 | newURL.Path = path.Join(newURL.Path, req.URL.Path) 152 | req.URL = &newURL 153 | 154 | var transport http.RoundTripper 155 | if t.Transport == nil { 156 | transport = http.DefaultTransport 157 | } else { 158 | transport = t.Transport 159 | } 160 | 161 | return transport.RoundTrip(req) 162 | } 163 | 164 | func newTestAsset(t *testing.T, name string) *testAsset { 165 | t.Helper() 166 | 167 | var path = filepath.Join(t.TempDir(), name) 168 | 169 | err := os.WriteFile(path, []byte("TEST"), 0600) 170 | assert.NoError(t, err) 171 | 172 | info, err := os.Stat(path) 173 | assert.NoError(t, err) 174 | 175 | return &testAsset{ 176 | Name: path, 177 | Size: info.Size(), 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /docs/man/cider_release.1: -------------------------------------------------------------------------------- 1 | .nh 2 | .TH "CIDER\-RELEASE" "1" "Apr 2021" "Auto generated by spf13/cobra" "" 3 | 4 | .SH NAME 5 | .PP 6 | cider\-release \- Release the selected apps in the current project 7 | 8 | 9 | .SH SYNOPSIS 10 | .PP 11 | \fBcider release [path] [flags]\fP 12 | 13 | 14 | .SH DESCRIPTION 15 | .PP 16 | Release the selected apps in the current project. 17 | 18 | .PP 19 | You can provide a path to a project directory as an argument to be the root directory 20 | of all relative path expansions in the program, such as the Git repository, preview sets, 21 | and screenshot resources. The only exception to this is if you provide a custom configuration 22 | file path with the \fB\fC\-\-config\fR flag. Instead, that file will be loaded relative to 23 | the working directory of the Cider process itself. 24 | 25 | .PP 26 | Additionally, Cider requires a few environment variables to be set in order to operate. 27 | They each correspond to an element of authorization described by the Apple Developer Documentation. 28 | 29 | .RS 30 | .IP \(bu 2 31 | \fB\fCASC\_KEY\_ID\fR: The key's ID. 32 | .IP \(bu 2 33 | \fB\fCASC\_ISSUER\_ID\fR: Your team's issuer ID. 34 | .IP \(bu 2 35 | \fB\fCASC\_PRIVATE\_KEY\fR or \fB\fCASC\_PRIVATE\_KEY\_PATH\fR: The .p8 private key issued by Apple. Must belong to an App Manager, Admin, or Account Holder. 36 | 37 | .RE 38 | 39 | .PP 40 | These three values each have varying degrees of sensetivity and should be treated as secrets. Store 41 | them securely in your environment so Cider can leverage them safely. 42 | 43 | .PP 44 | More info: https://developer.apple.com/documentation/appstoreconnectapi/creating\_api\_keys\_for\_app\_store\_connect\_api 45 | 46 | 47 | .SH OPTIONS 48 | .PP 49 | \fB\-A\fP, \fB\-\-all\-apps\fP[=false] 50 | Process all apps in the configuration file. Supercedes any usage of the \fB\fC\-\-app\fR flag. 51 | 52 | .PP 53 | \fB\-a\fP, \fB\-\-app\fP=[] 54 | Process the given app, providing the app key name used in your configuration file. 55 | 56 | .PP 57 | This flag can be provided repeatedly for each app you want to process. You can omit 58 | this flag if your configuration file has only one app defined. 59 | 60 | .PP 61 | \fB\-f\fP, \fB\-\-config\fP="" 62 | Load configuration from file 63 | 64 | .PP 65 | \fB\-h\fP, \fB\-\-help\fP[=false] 66 | help for release 67 | 68 | .PP 69 | \fB\-p\fP, \fB\-\-max\-processes\fP=1 70 | Run certain metadata syncing and asset uploading logic in parallel with 71 | the maximum allowable concurrency. 72 | 73 | .PP 74 | \fB\-\-mode\fP= 75 | Mode used to declare the publishing target for submission. 76 | 77 | .PP 78 | The default is "testflight" for submitting to Testflight, and the other alternative 79 | option is "appstore" for submitting to the App Store. 80 | 81 | .PP 82 | \fB\-\-set\-beta\-group\fP=[] 83 | Provide names of beta groups to release to instead of using 84 | the configuration file. 85 | 86 | .PP 87 | \fB\-\-set\-beta\-tester\fP=[] 88 | Provide email addresses of beta testers to release to instead of 89 | using the configuration file. 90 | 91 | .PP 92 | \fB\-B\fP, \fB\-\-set\-build\fP="" 93 | Build override to use instead of "latest". Corresponds to the CFBundleVersion 94 | of your build. 95 | 96 | .PP 97 | The default behavior without this flag is to select the latest build. In both cases, 98 | if the selected build has an invalid processing state, Cider will abort with an error 99 | to ensure your release is handled safely. 100 | 101 | .PP 102 | \fB\-V\fP, \fB\-\-set\-version\fP="" 103 | Version string override to use instead of parsing Git tags. Corresponds to the 104 | CFBundleShortVersionString of your build. 105 | 106 | .PP 107 | Cider expects this string to follow the Major.Minor.Patch semantics outlined in Apple documentation 108 | and Semantic Versioning (semver). If this flag is omitted, Git will be leveraged to determine the 109 | latest tag. The tag will be used to calculate the version string under the same constraints. 110 | 111 | .PP 112 | \fB\-\-skip\-git\fP[=false] 113 | Skips deriving version information from Git. Must only be used in conjunction with the \fB\fC\-\-set\-version\fR flag. 114 | 115 | .PP 116 | \fB\-\-skip\-submit\fP[=false] 117 | Skips submitting for review 118 | 119 | .PP 120 | \fB\-\-skip\-update\-metadata\fP[=false] 121 | Skips updating metadata (app info, localizations, assets, review details, etc.) 122 | 123 | .PP 124 | \fB\-\-skip\-update\-pricing\fP[=false] 125 | Skips updating app pricing 126 | 127 | .PP 128 | \fB\-\-timeout\fP=30m0s 129 | Timeout for the entire release process. 130 | 131 | .PP 132 | If the command takes longer than this amount of time to run, Cider will abort. 133 | 134 | 135 | .SH OPTIONS INHERITED FROM PARENT COMMANDS 136 | .PP 137 | \fB\-\-debug\fP[=false] 138 | Enable debug mode 139 | 140 | 141 | .SH EXAMPLE 142 | .PP 143 | .RS 144 | 145 | .nf 146 | cider release \-\-mode=appstore \-\-set\-version="1.0" 147 | 148 | .fi 149 | .RE 150 | 151 | 152 | .SH SEE ALSO 153 | .PP 154 | \fBcider(1)\fP 155 | -------------------------------------------------------------------------------- /tools/gendoc/config.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/apex/log" 29 | "github.com/cidertool/asc-go/asc" 30 | "github.com/cidertool/cider/internal/closer" 31 | "github.com/cidertool/cider/pkg/config" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | const ( 36 | docsConfigFrontmatterTemplate = `--- 37 | layout: page 38 | nav_order: %d 39 | --- 40 | 41 | # Configuration 42 | {: .no_toc } 43 | 44 | ` 45 | docsConfigTableOfContents = ` 46 |
47 | 48 | Table of Contents 49 | 50 | {: .text-delta } 51 | - TOC 52 | {:toc} 53 |
54 | 55 | ` 56 | docsConfigTerminologyDisclaimer = ` 57 | - [x] An X here means the field is required. 58 | - [ ] This field is optional and can be omitted. 59 | 60 | ` 61 | ) 62 | 63 | // nolint: gochecknoglobals 64 | var docsConfigExampleProject = config.Project{ 65 | "My App": { 66 | BundleID: "com.myproject.MyApp", 67 | PrimaryLocale: "en-US", 68 | UsesThirdPartyContent: asc.Bool(false), 69 | Availability: &config.Availability{ 70 | AvailableInNewTerritories: asc.Bool(false), 71 | Pricing: []config.PriceSchedule{ 72 | {Tier: "0"}, 73 | }, 74 | Territories: []string{"USA"}, 75 | }, 76 | Categories: &config.Categories{ 77 | Primary: "SOCIAL_NETWORKING", 78 | Secondary: "GAMES", 79 | SecondarySubcategories: [2]string{ 80 | "GAMES_SIMULATION", 81 | "GAMES_RACING", 82 | }, 83 | }, 84 | Localizations: config.AppLocalizations{ 85 | "en-US": { 86 | Name: "My App", 87 | Subtitle: "Not Your App", 88 | }, 89 | }, 90 | Versions: config.Version{ 91 | Platform: config.PlatformiOS, 92 | Copyright: "2020 Me", 93 | EarliestReleaseDate: nil, 94 | ReleaseType: config.ReleaseTypeAfterApproval, 95 | PhasedReleaseEnabled: true, 96 | IDFADeclaration: nil, 97 | Localizations: config.VersionLocalizations{ 98 | "en-US": { 99 | Description: "My App for cool people", 100 | Keywords: "Apps, Cool, Mine", 101 | WhatsNewText: `Thank you for using My App! I bring you updates every week so this continues to be my app.`, 102 | PreviewSets: config.PreviewSets{ 103 | config.PreviewTypeiPhone65: []config.Preview{ 104 | { 105 | File: config.File{ 106 | Path: "assets/store/iphone65/preview.mp4", 107 | }, 108 | }, 109 | }, 110 | }, 111 | ScreenshotSets: config.ScreenshotSets{ 112 | config.ScreenshotTypeiPhone65: []config.File{ 113 | {Path: "assets/store/iphone65/app.jpg"}, 114 | }, 115 | }, 116 | }, 117 | }, 118 | }, 119 | Testflight: config.Testflight{ 120 | EnableAutoNotify: true, 121 | Localizations: config.TestflightLocalizations{ 122 | "en-US": { 123 | Description: "My App for cool people using the beta", 124 | }, 125 | }, 126 | }, 127 | }, 128 | } 129 | 130 | func runDocsConfigCmd(cmd *cobra.Command, args []string) error { 131 | var path string 132 | if len(args) == 0 { 133 | path = defaultDocsPath 134 | } else { 135 | path = args[0] 136 | } 137 | 138 | path = filepath.Join(path, "configuration.md") 139 | log.WithField("path", path).Info("generating configuration documentation") 140 | 141 | err := genConfigMarkdown(path) 142 | if err != nil { 143 | log.Error("generation failed") 144 | } else { 145 | log.Info("generation completed successfully") 146 | } 147 | 148 | return err 149 | } 150 | 151 | func genConfigMarkdown(path string) error { 152 | f, err := os.Create(path) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | defer closer.Close(f) 158 | 159 | r, err := newRenderer() 160 | if err != nil { 161 | return err 162 | } 163 | 164 | r.Header = func() string { 165 | return fmt.Sprintf(docsConfigFrontmatterTemplate, 4) 166 | } 167 | 168 | r.Footer = func() string { 169 | contents, err := os.ReadFile(filepath.Join(filepath.Dir(path), "configuration-footer.md")) 170 | if err != nil { 171 | log.Error(err.Error()) 172 | 173 | return "" 174 | } 175 | 176 | return string(contents) 177 | } 178 | 179 | return r.Render(f) 180 | } 181 | -------------------------------------------------------------------------------- /internal/git/git.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package git provides an integration with the git command 22 | package git 23 | 24 | import ( 25 | "fmt" 26 | "path/filepath" 27 | "strings" 28 | 29 | "github.com/cidertool/cider/internal/shell" 30 | "github.com/cidertool/cider/pkg/context" 31 | ) 32 | 33 | // Repo represents any kind of repo (github, gitlab, etc). 34 | type Repo struct { 35 | Owner string 36 | Name string 37 | } 38 | 39 | // Git wraps a shell.Shell provider to provide an interface over 40 | // the git program in the PATH. 41 | type Git struct { 42 | shell.Shell 43 | } 44 | 45 | // New constructs a new Git instance with a default shell provider 46 | // based on the login shell. 47 | func New(ctx *context.Context) *Git { 48 | return &Git{ 49 | Shell: shell.New(ctx), 50 | } 51 | } 52 | 53 | // Run runs a Git command and returns its output or errors. 54 | func (git *Git) Run(args ...string) (*shell.CompletedProcess, error) { 55 | return git.RunInEnv(nil, args...) 56 | } 57 | 58 | // RunInEnv runs a Git command with the specified env vars and returns its output or errors. 59 | func (git *Git) RunInEnv(env map[string]string, args ...string) (*shell.CompletedProcess, error) { 60 | var extraArgs = []string{ 61 | "-c", "log.showSignature=false", 62 | } 63 | 64 | cd := git.CurrentDirectory() 65 | if cd != "" && cd != "." { 66 | extraArgs = append(extraArgs, "-C", filepath.Clean(cd)) 67 | } 68 | 69 | args = append(extraArgs, args...) 70 | 71 | var cmd = git.Shell.NewCommand("git", args...) 72 | 73 | if env != nil { 74 | cmd.Env = []string{} 75 | for k, v := range env { 76 | cmd.Env = append(cmd.Env, k+"="+v) 77 | } 78 | } 79 | 80 | return git.Shell.Exec(cmd) 81 | } 82 | 83 | // IsRepo returns true if current folder is a Git repository. 84 | func (git *Git) IsRepo() bool { 85 | proc, err := git.Run("rev-parse", "--is-inside-work-tree") 86 | 87 | return err == nil && strings.TrimSpace(proc.Stdout) == "true" 88 | } 89 | 90 | // SanitizedError wraps the contents of a sanitized Git error. 91 | type SanitizedError struct { 92 | stderr string 93 | } 94 | 95 | func (e SanitizedError) Error() string { 96 | return e.stderr 97 | } 98 | 99 | // SanitizeProcess cleans up the output. 100 | func (git *Git) SanitizeProcess(proc *shell.CompletedProcess, err error) (string, error) { 101 | var out string 102 | 103 | if proc != nil { 104 | firstline := strings.Split(proc.Stdout, "\n")[0] 105 | out = strings.ReplaceAll(firstline, "'", "") 106 | 107 | if err != nil { 108 | err = SanitizedError{strings.TrimSuffix(proc.Stderr, "\n")} 109 | } 110 | } 111 | 112 | return out, err 113 | } 114 | 115 | // MARK: Helpers 116 | 117 | // Show returns the requested information for the commit pointed to by HEAD. 118 | func (git *Git) Show(spec string) (string, error) { 119 | return git.ShowRef(spec, "HEAD") 120 | } 121 | 122 | // ShowRef returns the requested information for the given ref. 123 | func (git *Git) ShowRef(spec, ref string) (string, error) { 124 | return git.SanitizeProcess(git.Run("show", fmt.Sprintf("--format=%s", spec), ref, "--quiet")) 125 | } 126 | 127 | // ExtractRepoFromConfig gets the repo name from the Git config. 128 | func (git *Git) ExtractRepoFromConfig() (result Repo, err error) { 129 | if !git.IsRepo() { 130 | return result, ErrNotRepository{git.CurrentDirectory()} 131 | } 132 | 133 | proc, err := git.Run("config", "--get", "remote.origin.url") 134 | if err != nil { 135 | return result, ErrNoRemoteOrigin 136 | } 137 | 138 | return ExtractRepoFromURL(proc.Stdout), nil 139 | } 140 | 141 | // ExtractRepoFromURL gets the repo name from the remote URL. 142 | func ExtractRepoFromURL(s string) Repo { 143 | // removes the .git suffix and any new lines 144 | s = strings.NewReplacer( 145 | ".git", "", 146 | "\n", "", 147 | ).Replace(s) 148 | // if the URL contains a :, indicating a SSH config, 149 | // remove all chars until it, including itself 150 | // on HTTP and HTTPS URLs it will remove the http(s): prefix, 151 | // which is ok. On SSH URLs the whole user@server will be removed, 152 | // which is required. 153 | s = s[strings.LastIndex(s, ":")+1:] 154 | // split by /, the last to parts should be the owner and name 155 | ss := strings.Split(s, "/") 156 | 157 | return Repo{ 158 | Owner: ss[len(ss)-2], 159 | Name: ss[len(ss)-1], 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /internal/git/git_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | package git 22 | 23 | import ( 24 | "testing" 25 | 26 | "github.com/cidertool/cider/internal/shell" 27 | "github.com/cidertool/cider/internal/shell/shelltest" 28 | "github.com/cidertool/cider/pkg/config" 29 | "github.com/cidertool/cider/pkg/context" 30 | "github.com/stretchr/testify/assert" 31 | ) 32 | 33 | func newMockGit(t *testing.T, commands ...shelltest.Command) *Git { 34 | t.Helper() 35 | 36 | ctx := context.New(config.Project{}) 37 | 38 | return newMockGitWithContext(t, ctx, commands...) 39 | } 40 | 41 | func newMockGitWithContext(t *testing.T, ctx *context.Context, commands ...shelltest.Command) *Git { 42 | t.Helper() 43 | 44 | return &Git{ 45 | Shell: &shelltest.Shell{ 46 | T: t, 47 | Context: ctx, 48 | Commands: commands, 49 | }, 50 | } 51 | } 52 | 53 | func TestNew(t *testing.T) { 54 | t.Parallel() 55 | 56 | ctx := context.New(config.Project{}) 57 | 58 | client := New(ctx) 59 | ok := client.IsRepo() 60 | assert.True(t, ok) 61 | } 62 | 63 | func TestSanitizeProcess(t *testing.T) { 64 | t.Parallel() 65 | 66 | runFunc := func(client *Git) (*shell.CompletedProcess, error) { 67 | return client.RunInEnv(map[string]string{ 68 | "TEST": "TEST", 69 | }, "test") 70 | } 71 | 72 | ctx := context.New(config.Project{}) 73 | 74 | ctx.CurrentDirectory = "test" 75 | client := newMockGitWithContext( 76 | t, 77 | ctx, 78 | shelltest.Command{Stdout: "true", Stderr: "false"}, 79 | shelltest.Command{ReturnCode: 1, Stdout: "true", Stderr: "false"}, 80 | ) 81 | 82 | // Test out sanitize 83 | proc, err := runFunc(client) 84 | assert.NoError(t, err) 85 | assert.Equal(t, []string{"git", "-c", "log.showSignature=false", "-C", "test", "test"}, proc.Args) 86 | out, err := client.SanitizeProcess(proc, err) 87 | assert.Equal(t, "true", out) 88 | assert.NoError(t, err) 89 | 90 | // Test error sanitize 91 | proc, err = runFunc(client) 92 | assert.Error(t, err) 93 | out, err = client.SanitizeProcess(proc, err) 94 | assert.Equal(t, "true", out) 95 | assert.Error(t, err) 96 | assert.EqualError(t, err, "false") 97 | } 98 | 99 | func TestShowRef(t *testing.T) { 100 | t.Parallel() 101 | 102 | // Selected the initial commit of this repo, because I needed a sha1 hash. 103 | expected := "eac16d260ebf8af83873c9704169cf40a5501f84" 104 | client := newMockGit( 105 | t, 106 | shelltest.Command{Stdout: expected}, 107 | ) 108 | got, err := client.Show("%H") 109 | assert.NoError(t, err) 110 | assert.Equal(t, expected, got) 111 | } 112 | 113 | func TestExtractRemoteFromConfig_Happy(t *testing.T) { 114 | t.Parallel() 115 | 116 | expected := Repo{ 117 | Name: "cider", 118 | Owner: "cidertool", 119 | } 120 | 121 | client := newMockGit( 122 | t, 123 | shelltest.Command{Stdout: "true"}, 124 | shelltest.Command{Stdout: "git@github.com:cidertool/cider.git"}, 125 | ) 126 | repo, err := client.ExtractRepoFromConfig() 127 | assert.NoError(t, err) 128 | assert.Equal(t, expected, repo) 129 | } 130 | 131 | func TestExtractRemoteFromConfig_ErrIsNotRepo(t *testing.T) { 132 | t.Parallel() 133 | 134 | client := newMockGit( 135 | t, 136 | shelltest.Command{Stdout: "false"}, 137 | ) 138 | repo, err := client.ExtractRepoFromConfig() 139 | assert.Error(t, err) 140 | assert.Empty(t, repo) 141 | } 142 | 143 | func TestExtractRemoteFromConfig_ErrNoRemoteNamedOrigin(t *testing.T) { 144 | t.Parallel() 145 | 146 | client := newMockGit( 147 | t, 148 | shelltest.Command{Stdout: "true"}, 149 | shelltest.Command{ReturnCode: 1, Stderr: "no repo"}, 150 | ) 151 | repo, err := client.ExtractRepoFromConfig() 152 | assert.Error(t, err) 153 | assert.Empty(t, repo) 154 | } 155 | 156 | func TestExtractRepoFromURL(t *testing.T) { 157 | t.Parallel() 158 | 159 | var repo Repo 160 | 161 | expected := Repo{ 162 | Name: "cider", 163 | Owner: "cidertool", 164 | } 165 | repo = ExtractRepoFromURL("https://github.com/cidertool/cider") 166 | assert.Equal(t, expected, repo) 167 | repo = ExtractRepoFromURL("https://github.com/cidertool/cider.git") 168 | assert.Equal(t, expected, repo) 169 | repo = ExtractRepoFromURL("ssh://github.com/cidertool/cider.git") 170 | assert.Equal(t, expected, repo) 171 | repo = ExtractRepoFromURL("ssh://git@github.com/cidertool/cider.git") 172 | assert.Equal(t, expected, repo) 173 | repo = ExtractRepoFromURL("git@github.com:cidertool/cider.git") 174 | assert.Equal(t, expected, repo) 175 | } 176 | -------------------------------------------------------------------------------- /internal/pipe/testflight/testflight.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package testflight is a pipe that processes an app's release to Testflight 22 | package testflight 23 | 24 | import ( 25 | "fmt" 26 | 27 | "github.com/cidertool/asc-go/asc" 28 | "github.com/cidertool/cider/internal/client" 29 | "github.com/cidertool/cider/internal/log" 30 | "github.com/cidertool/cider/internal/pipe" 31 | "github.com/cidertool/cider/pkg/config" 32 | "github.com/cidertool/cider/pkg/context" 33 | ) 34 | 35 | // Pipe is a global hook pipe. 36 | type Pipe struct { 37 | Client client.Client 38 | } 39 | 40 | // String is the name of this pipe. 41 | func (Pipe) String() string { 42 | return "committing to testflight" 43 | } 44 | 45 | // Publish to Testflight. 46 | func (p *Pipe) Publish(ctx *context.Context) error { 47 | if p.Client == nil { 48 | p.Client = client.New(ctx) 49 | } 50 | 51 | for _, name := range ctx.AppsToRelease { 52 | app, ok := ctx.Config[name] 53 | if !ok { 54 | return pipe.ErrMissingApp{Name: name} 55 | } 56 | 57 | ctx.Log.WithField("name", name).Info("preparing") 58 | 59 | err := p.doRelease(ctx, app) 60 | if err != nil { 61 | return err 62 | } 63 | } 64 | 65 | return nil 66 | } 67 | 68 | func (p *Pipe) doRelease(ctx *context.Context, config config.App) error { 69 | app, err := p.Client.GetAppForBundleID(ctx, config.BundleID) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | build, err := p.Client.GetBuild(ctx, app) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | buildVersionLog := fmt.Sprintf("%s (%s)", ctx.Version, *build.Attributes.Version) 80 | 81 | ctx.Log.WithFields(log.Fields{ 82 | "app": *app.Attributes.BundleID, 83 | "build": buildVersionLog, 84 | }).Info("found resources") 85 | 86 | if ctx.SkipUpdateMetadata { 87 | ctx.Log.Warn("skipping updating metdata") 88 | } else { 89 | ctx.Log.Info("updating metadata") 90 | if err := p.updateBetaDetails(ctx, config, app, build); err != nil { 91 | return err 92 | } 93 | } 94 | 95 | if !ctx.SkipUpdateMetadata || ctx.OverrideBetaGroups { 96 | if err := p.updateBetaGroups(ctx, config, app, build); err != nil { 97 | return err 98 | } 99 | } 100 | 101 | if !ctx.SkipUpdateMetadata || ctx.OverrideBetaTesters { 102 | if err := p.updateBetaTesters(ctx, config, app, build); err != nil { 103 | return err 104 | } 105 | } 106 | 107 | if ctx.SkipSubmit { 108 | return pipe.ErrSkipSubmitEnabled 109 | } 110 | 111 | ctx.Log. 112 | WithField("build", buildVersionLog). 113 | Info("submitting to testflight") 114 | 115 | return p.Client.SubmitBetaApp(ctx, build.ID) 116 | } 117 | 118 | func (p *Pipe) updateBetaDetails(ctx *context.Context, config config.App, app *asc.App, build *asc.Build) error { 119 | ctx.Log.Infof("updating %d beta app localizations", len(config.Testflight.Localizations)) 120 | 121 | if err := p.Client.UpdateBetaAppLocalizations(ctx, app.ID, config.Testflight.Localizations); err != nil { 122 | return err 123 | } 124 | 125 | ctx.Log.Info("updating beta build details") 126 | 127 | if err := p.Client.UpdateBetaBuildDetails(ctx, build.ID, config.Testflight); err != nil { 128 | return err 129 | } 130 | 131 | ctx.Log.Infof("updating %d beta build localizations", len(config.Testflight.Localizations)) 132 | 133 | if err := p.Client.UpdateBetaBuildLocalizations(ctx, build.ID, config.Testflight.Localizations); err != nil { 134 | return err 135 | } 136 | 137 | ctx.Log.Info("updating beta license agreement") 138 | 139 | if err := p.Client.UpdateBetaLicenseAgreement(ctx, app.ID, config.Testflight); err != nil { 140 | return err 141 | } 142 | 143 | if config.Testflight.ReviewDetails != nil { 144 | ctx.Log.Info("updating beta review details") 145 | 146 | if err := p.Client.UpdateBetaReviewDetails(ctx, app.ID, *config.Testflight.ReviewDetails); err != nil { 147 | return err 148 | } 149 | } 150 | 151 | return nil 152 | } 153 | 154 | func (p *Pipe) updateBetaGroups(ctx *context.Context, config config.App, app *asc.App, build *asc.Build) error { 155 | ctx.Log.Info("updating build beta groups") 156 | 157 | return p.Client.AssignBetaGroups(ctx, app.ID, build.ID, config.Testflight.BetaGroups) 158 | } 159 | 160 | func (p *Pipe) updateBetaTesters(ctx *context.Context, config config.App, app *asc.App, build *asc.Build) error { 161 | ctx.Log.Info("updating build beta testers") 162 | 163 | return p.Client.AssignBetaTesters(ctx, app.ID, build.ID, config.Testflight.BetaTesters) 164 | } 165 | -------------------------------------------------------------------------------- /docs/commands/cider_release.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | parent: Commands 4 | title: release 5 | nav_order: 2 6 | nav_exclude: false 7 | --- 8 | 9 | ## cider release 10 | 11 | Release the selected apps in the current project 12 | 13 | ### Synopsis 14 | 15 | Release the selected apps in the current project. 16 | 17 | You can provide a path to a project directory as an argument to be the root directory 18 | of all relative path expansions in the program, such as the Git repository, preview sets, 19 | and screenshot resources. The only exception to this is if you provide a custom configuration 20 | file path with the `--config` flag. Instead, that file will be loaded relative to 21 | the working directory of the Cider process itself. 22 | 23 | Additionally, Cider requires a few environment variables to be set in order to operate. 24 | They each correspond to an element of authorization described by the Apple Developer Documentation. 25 | 26 | - `ASC_KEY_ID`: The key's ID. 27 | - `ASC_ISSUER_ID`: Your team's issuer ID. 28 | - `ASC_PRIVATE_KEY` or `ASC_PRIVATE_KEY_PATH`: The .p8 private key issued by Apple. Must belong to an App Manager, Admin, or Account Holder. 29 | 30 | These three values each have varying degrees of sensetivity and should be treated as secrets. Store 31 | them securely in your environment so Cider can leverage them safely. 32 | 33 | More info: https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api 34 | 35 | ``` 36 | cider release [path] [flags] 37 | ``` 38 | 39 | ### Examples 40 | 41 | ``` 42 | cider release --mode=appstore --set-version="1.0" 43 | ``` 44 | 45 | ### Options 46 | 47 | ``` 48 | -A, --all-apps --app Process all apps in the configuration file. Supercedes any usage of the --app flag. 49 | -a, --app stringArray Process the given app, providing the app key name used in your configuration file. 50 | 51 | This flag can be provided repeatedly for each app you want to process. You can omit 52 | this flag if your configuration file has only one app defined. 53 | -f, --config string Load configuration from file 54 | -h, --help help for release 55 | -p, --max-processes int Run certain metadata syncing and asset uploading logic in parallel with 56 | the maximum allowable concurrency. (default 1) 57 | --mode {appstore,testflight} Mode used to declare the publishing target for submission. 58 | 59 | The default is "testflight" for submitting to Testflight, and the other alternative 60 | option is "appstore" for submitting to the App Store. 61 | --set-beta-group stringArray Provide names of beta groups to release to instead of using 62 | the configuration file. 63 | --set-beta-tester stringArray Provide email addresses of beta testers to release to instead of 64 | using the configuration file. 65 | -B, --set-build string Build override to use instead of "latest". Corresponds to the CFBundleVersion 66 | of your build. 67 | 68 | The default behavior without this flag is to select the latest build. In both cases, 69 | if the selected build has an invalid processing state, Cider will abort with an error 70 | to ensure your release is handled safely. 71 | -V, --set-version string Version string override to use instead of parsing Git tags. Corresponds to the 72 | CFBundleShortVersionString of your build. 73 | 74 | Cider expects this string to follow the Major.Minor.Patch semantics outlined in Apple documentation 75 | and Semantic Versioning (semver). If this flag is omitted, Git will be leveraged to determine the 76 | latest tag. The tag will be used to calculate the version string under the same constraints. 77 | --skip-git --set-version Skips deriving version information from Git. Must only be used in conjunction with the --set-version flag. 78 | --skip-submit Skips submitting for review 79 | --skip-update-metadata Skips updating metadata (app info, localizations, assets, review details, etc.) 80 | --skip-update-pricing Skips updating app pricing 81 | --timeout duration Timeout for the entire release process. 82 | 83 | If the command takes longer than this amount of time to run, Cider will abort. (default 30m0s) 84 | ``` 85 | 86 | ### Options inherited from parent commands 87 | 88 | ``` 89 | --debug Enable debug mode 90 | ``` 91 | 92 | ### SEE ALSO 93 | 94 | * [cider](/commands/cider/) - Submit your builds to the Apple App Store in seconds 95 | 96 | -------------------------------------------------------------------------------- /pkg/context/context.go: -------------------------------------------------------------------------------- 1 | /** 2 | Copyright (C) 2020 Aaron Sky. 3 | 4 | This file is part of Cider, a tool for automating submission 5 | of apps to Apple's App Stores. 6 | 7 | Cider is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | Cider is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with Cider. If not, see . 19 | */ 20 | 21 | // Package context manages the state of the pipeline 22 | package context 23 | 24 | import ( 25 | ctx "context" 26 | "fmt" 27 | "os" 28 | "strings" 29 | "time" 30 | 31 | "github.com/cidertool/cider/internal/log" 32 | "github.com/cidertool/cider/pkg/config" 33 | ) 34 | 35 | // PublishMode describes which review destination to publish to. 36 | type PublishMode string 37 | 38 | const ( 39 | // PublishModeTestflight publishes to Testflight via beta app review. 40 | PublishModeTestflight PublishMode = "testflight" 41 | // PublishModeAppStore publishes for App Store review. 42 | PublishModeAppStore PublishMode = "appstore" 43 | ) 44 | 45 | type errInvalidPublishMode struct { 46 | Value string 47 | } 48 | 49 | func (e errInvalidPublishMode) Error() string { 50 | return fmt.Sprintf("invalid value %s for publish mode", e.Value) 51 | } 52 | 53 | // Context carries along some data through the pipes. 54 | type Context struct { 55 | ctx.Context 56 | Config config.Project 57 | RawConfig config.Project 58 | Env Env 59 | Date time.Time 60 | Git GitInfo 61 | CurrentDirectory string 62 | Credentials Credentials 63 | AppsToRelease []string 64 | PublishMode PublishMode 65 | Log log.Interface 66 | MaxProcesses int 67 | SkipGit bool 68 | SkipUpdatePricing bool 69 | SkipUpdateMetadata bool 70 | SkipSubmit bool 71 | OverrideBetaGroups bool 72 | OverrideBetaTesters bool 73 | VersionIsInitialRelease bool 74 | Version string 75 | Build string 76 | Semver Semver 77 | } 78 | 79 | // Env is the environment variables. 80 | type Env map[string]string 81 | 82 | // GitInfo includes tags and refs. 83 | type GitInfo struct { 84 | CurrentTag string 85 | Commit string 86 | ShortCommit string 87 | FullCommit string 88 | CommitDate time.Time 89 | URL string 90 | } 91 | 92 | // Semver represents a semantic version. 93 | type Semver struct { 94 | Major uint64 95 | Minor uint64 96 | Patch uint64 97 | Prerelease string 98 | RawVersion string 99 | } 100 | 101 | // New context. 102 | func New(config config.Project) *Context { 103 | return Wrap(ctx.Background(), config) 104 | } 105 | 106 | // NewWithTimeout new context with the given timeout. 107 | func NewWithTimeout(config config.Project, timeout time.Duration) (*Context, ctx.CancelFunc) { 108 | ctx, cancel := ctx.WithTimeout(ctx.Background(), timeout) 109 | 110 | return Wrap(ctx, config), cancel 111 | } 112 | 113 | // Wrap wraps an existing context. 114 | func Wrap(ctx ctx.Context, config config.Project) *Context { 115 | return &Context{ 116 | Context: ctx, 117 | Config: config, 118 | RawConfig: config, 119 | Env: splitEnv(os.Environ()), 120 | Date: time.Now(), 121 | Log: log.New(), 122 | MaxProcesses: 1, 123 | } 124 | } 125 | 126 | // Copy returns a copy of the environment. 127 | func (e Env) Copy() Env { 128 | var out = Env{} 129 | for k, v := range e { 130 | out[k] = v 131 | } 132 | 133 | return out 134 | } 135 | 136 | // Strings returns the current environment as a list of strings, suitable for 137 | // os executions. 138 | func (e Env) Strings() []string { 139 | var result = make([]string, 0, len(e)) 140 | for k, v := range e { 141 | result = append(result, k+"="+v) 142 | } 143 | 144 | return result 145 | } 146 | 147 | func splitEnv(env []string) map[string]string { 148 | r := map[string]string{} 149 | 150 | for _, e := range env { 151 | p := strings.SplitN(e, "=", 2) 152 | r[p[0]] = p[1] 153 | } 154 | 155 | return r 156 | } 157 | 158 | // String returns the string value of the mode. 159 | func (m PublishMode) String() string { 160 | return string(m) 161 | } 162 | 163 | // Set the mode to an allowed value, or return an error. 164 | func (m *PublishMode) Set(value string) error { 165 | switch value { 166 | case "appstore": 167 | *m = PublishModeAppStore 168 | 169 | return nil 170 | case "testflight": 171 | *m = PublishModeTestflight 172 | 173 | return nil 174 | } 175 | 176 | return errInvalidPublishMode{Value: value} 177 | } 178 | 179 | // Type returns a representation of permissible values. 180 | func (m PublishMode) Type() string { 181 | return "{appstore,testflight}" 182 | } 183 | --------------------------------------------------------------------------------