Skip to content

Commit

Permalink
Feature: Add actions to app-store-connect to manage App Store Versi…
Browse files Browse the repository at this point in the history
…on submissions (#85)

* Add option to group CLI actions into categories

* Add client actions to manage App Store Version submissions

* Bump version and update changelog

* Update error response model to respect error meta and associated errors

* Fix do not include meta in error serialization if it is not defined

* update docs with new generation

* remove not needed file

Co-authored-by: Stanislav Bondarenko <[email protected]>
  • Loading branch information
priitlatt and BondarenkoStas authored May 17, 2021
1 parent 7e08bf9 commit e5e8f00
Show file tree
Hide file tree
Showing 17 changed files with 510 additions and 40 deletions.
11 changes: 8 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ Version 0.6.0

**New features**

- Add action group support for tools.
- Add action `get-profile` to `app-store-connect` to show provisioning profile based on resource identifier.
- Add action `app-store-connect app-store-version-submissions create` to submit App Store Version to review.
- Add action `app-store-connect app-store-version-submissions delete` to remove App Store Version from review.

**Development / Docs**

- Add documentation for new action `app-store-connect get-profile`.
- Add `SERVICES` as valid value to `--platform` option in `app-store-connect` actions.
- Update `--profile` option default values for action `xcode-project use-profiles`.
- Update `--profile` option default value in action `xcode-project use-profiles` docs.
- Generate documentation for action groups and list groups under tool documentation pages.
- Add documentation for action `app-store-connect get-profile`.
- Add documentation for action `app-store-connect app-store-version-submissions create`.
- Add documentation for action `app-store-connect app-store-version-submissions delete`.

Version 0.5.9
-------------
Expand Down
127 changes: 93 additions & 34 deletions doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Iterable
from typing import List
from typing import NamedTuple
from typing import Optional

from mdutils.mdutils import MdUtils
from mdutils.tools.Table import Table
Expand Down Expand Up @@ -40,6 +41,12 @@ class Action(NamedTuple):
optional_args: List[SerializedArgument]


class ActionGroup(NamedTuple):
name: str
description: str
actions: List[Action]


class ArgumentKwargs(NamedTuple):
nargs: bool
required: bool
Expand Down Expand Up @@ -129,34 +136,68 @@ def __init__(self, tool, main_dir: str):
self.tool_required_args = class_args_serializer.required_args
self.tool_options = self._serialize_default_options(self.tool)
self.tool_serialized_actions = self._serialize_actions(self.tool)
self.tool_serialized_action_groups = self._serialize_action_groups(self.tool)

def generate(self):
def _write_tool_command_arguments_and_options():
writer.write_arguments(f'command `{self.tool_command}`', self.tool_optional_args, self.tool_required_args)
writer.write_options(self.tool_options)
self._write_tool_page()

# docs/<tool-name>/README.md
for action in self.tool_serialized_actions:
self._write_action_page(action)

for group in self.tool_serialized_action_groups:
self._write_action_group_page(group)

def _write_tool_command_arguments_and_options(self, writer):
writer.write_arguments(f'command `{self.tool_command}`', self.tool_optional_args, self.tool_required_args)
writer.write_options(self.tool_options)

def _write_tool_page(self):
os.makedirs(self.tool_prefix, exist_ok=True)
md = MdUtils(file_name=f'{self.tool_prefix}/README', title=self.tool_command)
writer = Writer(md)
writer.write_description(self.tool.__doc__)
writer.write_tool_command_usage(self)
_write_tool_command_arguments_and_options()
writer.write_command_usage(self)
self._write_tool_command_arguments_and_options(writer)
writer.write_actions_table(self.tool_serialized_actions)
writer.write_action_groups_table(self.tool_serialized_action_groups)
md.create_md_file()

for action in self.tool_serialized_actions:
# docs/<tool-name>/<action-name>.md
md = MdUtils(file_name=f'{self.tool_prefix}/{action.action_name}', title=action.action_name)
writer = Writer(md)
writer.write_description(action.description)
writer.write_action_command_usage(self, action)
writer.write_arguments(f'action `{action.action_name}`', action.optional_args, action.required_args)
_write_tool_command_arguments_and_options()
md.create_md_file()
def _write_action_group_page(self, action_group: ActionGroup):
group_path = f'{self.tool_prefix}/{action_group.name}'
md = MdUtils(file_name=group_path, title=action_group.name)
writer = Writer(md)
writer.write_description(action_group.description)
writer.write_command_usage(self, action_group=action_group)
self._write_tool_command_arguments_and_options(writer)
writer.write_actions_table(action_group.actions, action_group=action_group)
md.create_md_file()
os.makedirs(group_path, exist_ok=True)
for action in action_group.actions:
self._write_action_page(action, action_group=action_group)

def _write_action_page(self, action: Action, action_group: Optional[ActionGroup] = None):
group_str = f'{action_group.name}/' if action_group else ''
md = MdUtils(file_name=f'{self.tool_prefix}/{group_str}{action.action_name}', title=action.action_name)
writer = Writer(md)
writer.write_description(action.description)
writer.write_command_usage(self, action_group=action_group, action=action)
writer.write_arguments(f'action `{action.action_name}`', action.optional_args, action.required_args)
self._write_tool_command_arguments_and_options(writer)
md.create_md_file()

@classmethod
def _serialize_actions(cls, tool: cli.CliApp) -> List[Action]:
def _serialize_action_groups(cls, tool: cli.CliApp) -> List[ActionGroup]:
def _serialize_action_group(group) -> ActionGroup:
return ActionGroup(
name=group.name,
description=group.description,
actions=cls._serialize_actions(tool, action_group=group),
)

return list(map(_serialize_action_group, tool.list_class_action_groups()))

@classmethod
def _serialize_actions(cls, tool: cli.CliApp, action_group=None) -> List[Action]:
def _serialize_action(action: Callable) -> Action:
action_args_serializer = ArgumentsSerializer(action.arguments).serialize()
return Action(
Expand All @@ -167,7 +208,7 @@ def _serialize_action(action: Callable) -> Action:
optional_args=action_args_serializer.optional_args,
)

return list(map(_serialize_action, tool.iter_class_cli_actions()))
return list(map(_serialize_action, tool.iter_class_cli_actions(action_group=action_group)))

@classmethod
def _serialize_default_options(cls, tool: cli.CliApp) -> List[SerializedArgument]:
Expand Down Expand Up @@ -195,19 +236,21 @@ class CommandUsageGenerator:
def __init__(self, doc_generator: ToolDocumentationGenerator):
self.doc_generator = doc_generator

def get_tool_command_usage(self) -> List[str]:
return [
f'{self.doc_generator.tool_command} {self._get_opt_common_flags()}',
*self._get_tool_arguments_and_flags(),
'ACTION',
]
def get_command_usage(self,
action_group: Optional[ActionGroup] = None,
action: Optional[Action] = None) -> List[str]:
action_group_str = f' {action_group.name}' if action_group else ''
action_str = f' {action.action_name}' if action else ''

def get_action_command_usage(self, action: Action) -> List[str]:
return [
f'{self.doc_generator.tool_command} {action.action_name} {self._get_opt_common_flags()}',
*self._get_tool_arguments_and_flags(),
action_args = [
*map(self._get_formatted_flag, action.optional_args),
*map(self._get_formatted_flag, action.required_args),
] if action else ['ACTION']

return [
f'{self.doc_generator.tool_command}{action_group_str}{action_str} {self._get_opt_common_flags()}',
*self._get_tool_arguments_and_flags(),
*action_args,
]

def _get_opt_common_flags(self) -> str:
Expand Down Expand Up @@ -239,11 +282,12 @@ def write_description(self, content: str):
content = str_plain(content)
self.file.new_paragraph(f'**{content}**')

def write_tool_command_usage(self, generator: ToolDocumentationGenerator):
self._write_command_usage(self.file, CommandUsageGenerator(generator).get_tool_command_usage())

def write_action_command_usage(self, generator: ToolDocumentationGenerator, action: Action):
self._write_command_usage(self.file, CommandUsageGenerator(generator).get_action_command_usage(action))
def write_command_usage(self,
generator: ToolDocumentationGenerator,
action_group: Optional[ActionGroup] = None,
action: Optional[Action] = None):
lines = CommandUsageGenerator(generator).get_command_usage(action_group=action_group, action=action)
self._write_command_usage(self.file, lines)

def write_table(self, content: List[List[str]], header: List[str]):
flat_content: List[str] = sum(content, [])
Expand All @@ -264,13 +308,28 @@ def _get_tool_doc(tool: cli.CliApp) -> List[str]:

self.write_table(list(map(_get_tool_doc, tools)), ['Tool name', 'Description'])

def write_actions_table(self, actions: List[Action]):
def write_actions_table(self, actions: List[Action], action_group: Optional[ActionGroup] = None):
if not actions:
return

def _get_action_doc(action: Action) -> List[str]:
return [f'[`{action.action_name}`]({action.action_name}.md)', str_plain(action.description)]
action_group_str = f'{action_group.name}/' if action_group else ''
action_link = f'{action_group_str}{action.action_name}.md'
return [f'[`{action.action_name}`]({action_link})', str_plain(action.description)]

self.file.new_header(level=3, title='Actions', add_table_of_contents='n')
self.write_table(list(map(_get_action_doc, actions)), ['Action', 'Description'])

def write_action_groups_table(self, groups: List[ActionGroup]):
if not groups:
return

def _get_group_doc(group: ActionGroup) -> List[str]:
return [f'[`{group.name}`]({group.name}.md)', str_plain(group.description)]

self.file.new_header(level=3, title='Action groups', add_table_of_contents='n')
self.write_table(list(map(_get_group_doc, groups)), ['Action group', 'Description'])

def write_arguments(self, obj: str, optional: List[SerializedArgument], required: List[SerializedArgument]):
self._write_arguments(self.file, f'Required arguments for {obj}', required)
self._write_arguments(self.file, f'Optional arguments for {obj}', optional)
Expand Down
6 changes: 6 additions & 0 deletions docs/app-store-connect/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,9 @@ Enable verbose logging for commands
|[`list-certificates`](list-certificates.md)|List Signing Certificates from Apple Developer Portal matching given constraints|
|[`list-devices`](list-devices.md)|List Devices from Apple Developer portal matching given constraints|
|[`list-profiles`](list-profiles.md)|List Profiles from Apple Developer portal matching given constraints|

### Action groups

|Action group|Description|
| :--- | :--- |
|[`app-store-version-submissions`](app-store-version-submissions.md)|Manage your application's App Store version review process|
80 changes: 80 additions & 0 deletions docs/app-store-connect/app-store-version-submissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

app-store-version-submissions
=============================


**Manage your application's App Store version review process**
### Usage
```bash
app-store-connect app-store-version-submissions [-h] [--log-stream STREAM] [--no-color] [--version] [-s] [-v]
[--log-api-calls]
[--json]
[--issuer-id ISSUER_ID]
[--key-id KEY_IDENTIFIER]
[--private-key PRIVATE_KEY]
[--certificates-dir CERTIFICATES_DIRECTORY]
[--profiles-dir PROFILES_DIRECTORY]
ACTION
```
### Optional arguments for command `app-store-connect`

##### `--log-api-calls`


Turn on logging for App Store Connect API HTTP requests
##### `--json`


Whether to show the resource in JSON format
##### `--issuer-id=ISSUER_ID`


App Store Connect API Key Issuer ID. Identifies the issuer who created the authentication token. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_ISSUER_ID`. Alternatively to entering` ISSUER_ID `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--key-id=KEY_IDENTIFIER`


App Store Connect API Key ID. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_KEY_IDENTIFIER`. Alternatively to entering` KEY_IDENTIFIER `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--private-key=PRIVATE_KEY`


App Store Connect API private key. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_PRIVATE_KEY`. Alternatively to entering` PRIVATE_KEY `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--certificates-dir=CERTIFICATES_DIRECTORY`


Directory where the code signing certificates will be saved. Default:&nbsp;`$HOME/Library/MobileDevice/Certificates`
##### `--profiles-dir=PROFILES_DIRECTORY`


Directory where the provisioning profiles will be saved. Default:&nbsp;`$HOME/Library/MobileDevice/Provisioning Profiles`
### Common options

##### `-h, --help`


show this help message and exit
##### `--log-stream=stderr | stdout`


Log output stream. Default `stderr`
##### `--no-color`


Do not use ANSI colors to format terminal output
##### `--version`


Show tool version and exit
##### `-s, --silent`


Disable log output for commands
##### `-v, --verbose`


Enable verbose logging for commands
### Actions

|Action|Description|
| :--- | :--- |
|[`create`](app-store-version-submissions/create.md)|Submit an App Store Version to App Review|
|[`delete`](app-store-version-submissions/delete.md)|Remove a version submission from App Store review|
80 changes: 80 additions & 0 deletions docs/app-store-connect/app-store-version-submissions/create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@

create
======


**Submit an App Store Version to App Review**
### Usage
```bash
app-store-connect app-store-version-submissions create [-h] [--log-stream STREAM] [--no-color] [--version] [-s] [-v]
[--log-api-calls]
[--json]
[--issuer-id ISSUER_ID]
[--key-id KEY_IDENTIFIER]
[--private-key PRIVATE_KEY]
[--certificates-dir CERTIFICATES_DIRECTORY]
[--profiles-dir PROFILES_DIRECTORY]
APP_STORE_VERSION_ID
```
### Required arguments for action `create`

##### `APP_STORE_VERSION_ID`


UUID value of the App Store Version
### Optional arguments for command `app-store-connect`

##### `--log-api-calls`


Turn on logging for App Store Connect API HTTP requests
##### `--json`


Whether to show the resource in JSON format
##### `--issuer-id=ISSUER_ID`


App Store Connect API Key Issuer ID. Identifies the issuer who created the authentication token. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_ISSUER_ID`. Alternatively to entering` ISSUER_ID `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--key-id=KEY_IDENTIFIER`


App Store Connect API Key ID. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_KEY_IDENTIFIER`. Alternatively to entering` KEY_IDENTIFIER `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--private-key=PRIVATE_KEY`


App Store Connect API private key. Learn more at https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api. If not given, the value will be checked from environment variable `APP_STORE_CONNECT_PRIVATE_KEY`. Alternatively to entering` PRIVATE_KEY `in plaintext, it may also be specified using a `@env:` prefix followed by a environment variable name, or `@file:` prefix followed by a path to the file containing the value. Example: `@env:<variable>` uses the value in the environment variable named `<variable>`, and `@file:<file_path>` uses the value from file at `<file_path>`.
##### `--certificates-dir=CERTIFICATES_DIRECTORY`


Directory where the code signing certificates will be saved. Default:&nbsp;`$HOME/Library/MobileDevice/Certificates`
##### `--profiles-dir=PROFILES_DIRECTORY`


Directory where the provisioning profiles will be saved. Default:&nbsp;`$HOME/Library/MobileDevice/Provisioning Profiles`
### Common options

##### `-h, --help`


show this help message and exit
##### `--log-stream=stderr | stdout`


Log output stream. Default `stderr`
##### `--no-color`


Do not use ANSI colors to format terminal output
##### `--version`


Show tool version and exit
##### `-s, --silent`


Disable log output for commands
##### `-v, --verbose`


Enable verbose logging for commands
Loading

0 comments on commit e5e8f00

Please sign in to comment.