Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

* New command `alex pubspec version <patch|minor|major>` (alias `alex pub version`) — increments the version in `pubspec.yaml`. By default only the version is changed and a build number is kept as is (usually it's already prepared for the next build); pass `--build` (`-b`) to increment the build number too (#12).
* [Release] `start`: new `--increment` (`-i`) option (`patch`, `minor` or `major`) to increment the version **of this release** before it's started, keeping the build number: `1.2.3+4` is released as `1.3.0+4` with `-i minor`. The release branch, `CHANGELOG.md`, the tag and build artifacts use the new version. Without the option nothing changes — the current version is released as is. After the release a patch and a build number are incremented in `develop` as always, and the new version is printed in the end (#12).
* [Release] `start`: fixed a failure of a local iOS build (`--local`), when alex reported that the artifact was placed in an unexpected directory, though the `.ipa` was built correctly. `flutter build ipa` prints two `✓ Built` lines — for the Xcode archive and for the IPA itself — and alex checked the first one. Now the line with a path in the build output directory is used.
* [Release] `start`: if the release failed, then all the changes made in the repository are rolled back automatically: uncommitted and untracked changes are discarded, release branch and tag are removed (only if they didn't exist before the release), and `develop` and `master` are restored to the state before the release. Every step is printed, and if the rollback itself fails, then instructions for a manual cleanup are shown. Nothing is rolled back as soon as the push is started — a push can update the remote even if it failed as a whole, and a published release should not be rewritten — in that case alex only reports that the release should be finished or reverted manually.
* [Release] `start`: fix the length limit of the release notes in the form. The limit is enforced by alex now, instead of the `maxlength` attribute: a browser counts a newline as CRLF (2 characters), so a note with N newlines was cut N characters earlier than the store allows, and it was impossible to type anything while the counter still showed a value below the limit. The counter shows the number of characters as the store counts them, and it's highlighted if the value is longer than the limit.
Expand Down
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ _Note: You can change GIT branches, localization parameters, CI/CD and other set

- `--check_locale=<LOCALE>` (`-l`) - Locale to check before release if translations exist for all strings. If not specified, "en" locale will be checked.
- `--skip_l10n` (`-s`) - Skip translations check during release.
- `--increment=<PART>` (`-i`) - Increment a part of the version **of this release** before it's started: `patch`, `minor` or `major`. Build number is kept as is. If not specified, the current version is released as is.
- `--local` (`-b`) - Run local release build for Android and iOS platforms.
- `--entry-point=<path>` (`-e`) - Entry point of the app (e.g., lib/main_test.dart). Only for local release builds.
- `--platforms=<PLATFORMS>` (`-p`) - Target build platforms: ios, android. You can pass multiple platforms separated by commas. Defaults to "android,ios". Only for local release builds.
Expand Down Expand Up @@ -135,6 +136,33 @@ otherwise the artifacts would be added in the release commit by `git add -A`.
This is checked before the release is started, along with the write access to the directory,
so the release will not be published if an artifact can't be copied.

**Version increment:**

By default the version from `pubspec.yaml` is released as is,
and after the release is finished a patch and a build number are incremented
and committed in `develop` for the next release: `1.2.3+4` is released,
and `develop` continues with `1.2.4+5`.

If it turns out at the release time that the release should be a minor
or a major one, pass `--increment` (`-i`).
The version is incremented **before** the release is started,
so the release branch, `CHANGELOG.md`, the tag and build artifacts
all use the new version. A build number is kept as is —
it's already prepared for this build:

| `-i` | Released version | Version in `develop` after the release |
|-----------|------------------|----------------------------------------|
| _not set_ | `1.2.3+4` | `1.2.4+5` |
| `patch` | `1.2.4+4` | `1.2.5+5` |
| `minor` | `1.3.0+4` | `1.3.1+5` |
| `major` | `2.0.0+4` | `2.0.1+5` |

A pre-release suffix is kept as is.

If the version was already raised during the work on the task
(see [`alex pubspec version`](#version)), then nothing should be passed
at the release time — the current version is released.

**Pre-release scripts:**

You can define pre-release scripts in your project's [configuration](#configuration):
Expand Down Expand Up @@ -173,6 +201,11 @@ Skip translations check:
$ alex release start --skip_l10n
```

Release as a minor version (`1.2.3+4` is released as `1.3.0+4`):
```bash
$ alex release start --increment=minor
```

### Feature

Work with feature branches and issues.
Expand Down Expand Up @@ -393,6 +426,36 @@ or
$ alex pub get
```

#### Version

Increment the version in `pubspec.yaml`.

```bash
$ alex pubspec version <part>
```

where `<part>` is `patch`, `minor` or `major`.

**Only the version is changed, a build number is kept as is**, because usually
it's already prepared for the next build. Pass `--build` (`-b`) to increment
the build number (after `+`) too. A pre-release suffix is always kept as is.

| Command | `1.2.3+4` becomes |
|----------------------------------------|-------------------|
| `alex pubspec version patch` | `1.2.4+4` |
| `alex pubspec version minor` | `1.3.0+4` |
| `alex pubspec version major` | `2.0.0+4` |
| `alex pubspec version minor --build` | `1.3.0+5` |

Use it when it becomes clear during the work on a task that the next release
should be a minor or a major one — raise the version right away, and then
release it as usual with `alex release start`.
If you realize it only at the release time, use the `--increment` option
of the [`release start`](#start-release) command instead.

The command changes `pubspec.yaml` in the current directory
and doesn't commit anything.

### Update

Manage updates for `alex`.
Expand Down
7 changes: 4 additions & 3 deletions lib/commands/code/generate_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ class GenerateCommand extends CodeCommandBase {

if (pubspec.isWorkspaceRoot()) {
printVerbose('Workspace root found');
final info =
workspaces.putIfAbsent(file.parent.path, () => _WorkspaceInfo(file));
final info = workspaces.putIfAbsent(
file.parent.path, () => _WorkspaceInfo(file));
if (hasBuildRunner) info.rootHasBuildRunner = true;
continue;
}
Expand Down Expand Up @@ -248,7 +248,8 @@ class _GenTarget {
/// workspace generation, so the build step can skip its own pub get.
final bool depsResolved;

_GenTarget(this.pubspec, {this.useWorkspace = false, this.depsResolved = false});
_GenTarget(this.pubspec,
{this.useWorkspace = false, this.depsResolved = false});
}

/// Aggregated info about a single pub workspace discovered during the scan.
Expand Down
66 changes: 46 additions & 20 deletions lib/commands/custom/add_custom_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class AddCustomCommand extends AlexCommand {
// Get actions
final actions = <CustomCommandAction>[];
while (true) {
final addAction = _prompt('Add action? (y/n)', defaultValue: actions.isEmpty ? 'y' : 'n');
final addAction = _prompt('Add action? (y/n)',
defaultValue: actions.isEmpty ? 'y' : 'n');
if (addAction.toLowerCase() != 'y') {
if (actions.isEmpty) {
printError('At least one action is required');
Expand Down Expand Up @@ -111,7 +112,8 @@ class AddCustomCommand extends AlexCommand {
if (name.isEmpty) return null;

printInfo(' Type:');
final typeStr = _promptChoice('Select type', ['option', 'flag'], defaultValue: 'option');
final typeStr = _promptChoice('Select type', ['option', 'flag'],
defaultValue: 'option');
final type = typeStr.toLowerCase() == 'flag'
? CustomCommandArgumentType.flag
: CustomCommandArgumentType.option;
Expand Down Expand Up @@ -160,11 +162,13 @@ class AddCustomCommand extends AlexCommand {
// Prompt with validation loop
CustomCommandActionType? actionType;
while (actionType == null) {
final input = _prompt(' Enter action type number (1-${allTypes.length})', defaultValue: '1');
final input = _prompt(' Enter action type number (1-${allTypes.length})',
defaultValue: '1');
final number = int.tryParse(input);

if (number == null || number < 1 || number > allTypes.length) {
printError('Invalid number. Please enter a number between 1 and ${allTypes.length}.');
printError(
'Invalid number. Please enter a number between 1 and ${allTypes.length}.');
continue;
}

Expand Down Expand Up @@ -235,7 +239,11 @@ class AddCustomCommand extends AlexCommand {
final workingDir = _prompt(' Working directory (optional)');

final args = argsStr.isNotEmpty
? argsStr.split(' ').map((e) => e.trim()).where((e) => e.isNotEmpty).toList()
? argsStr
.split(' ')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList()
: <String>[];

return ExecAction(
Expand Down Expand Up @@ -271,7 +279,8 @@ class AddCustomCommand extends AlexCommand {

CheckGitBranchAction _promptCheckGitBranchAction() {
final branch = _prompt(' Branch name');
final autoSwitchStr = _prompt(' Auto switch to branch if not on it? (y/n)', defaultValue: 'y');
final autoSwitchStr = _prompt(' Auto switch to branch if not on it? (y/n)',
defaultValue: 'y');
final autoSwitch = autoSwitchStr.toLowerCase() == 'y';

return CheckGitBranchAction(
Expand All @@ -292,9 +301,12 @@ class AddCustomCommand extends AlexCommand {

DeleteFileAction _promptDeleteFileAction() {
final path = _prompt(' File path to delete');
final recursiveStr = _prompt(' Delete recursively (for directories)? (y/n)', defaultValue: 'n');
final recursiveStr = _prompt(
' Delete recursively (for directories)? (y/n)',
defaultValue: 'n');
final recursive = recursiveStr.toLowerCase() == 'y';
final ignoreNotFoundStr = _prompt(' Ignore if not found? (y/n)', defaultValue: 'y');
final ignoreNotFoundStr =
_prompt(' Ignore if not found? (y/n)', defaultValue: 'y');
final ignoreNotFound = ignoreNotFoundStr.toLowerCase() == 'y';

return DeleteFileAction(
Expand All @@ -306,7 +318,9 @@ class AddCustomCommand extends AlexCommand {

CheckFileExistsAction _promptCheckFileExistsAction() {
final path = _prompt(' File/directory path to check');
final shouldExistStr = _prompt(' Should exist? (y=must exist, n=must not exist)', defaultValue: 'y');
final shouldExistStr = _prompt(
' Should exist? (y=must exist, n=must not exist)',
defaultValue: 'y');
final shouldExist = shouldExistStr.toLowerCase() == 'y';

return CheckFileExistsAction(
Expand All @@ -318,7 +332,8 @@ class AddCustomCommand extends AlexCommand {
CopyFileAction _promptCopyFileAction() {
final source = _prompt(' Source file path');
final destination = _prompt(' Destination file path');
final overwriteStr = _prompt(' Overwrite if exists? (y/n)', defaultValue: 'n');
final overwriteStr =
_prompt(' Overwrite if exists? (y/n)', defaultValue: 'n');
final overwrite = overwriteStr.toLowerCase() == 'y';

return CopyFileAction(
Expand Down Expand Up @@ -351,7 +366,8 @@ class AddCustomCommand extends AlexCommand {
CreateFileAction _promptCreateFileAction() {
final path = _prompt(' File path to create');
final content = _prompt(' File content (optional, supports variables)');
final overwriteStr = _prompt(' Overwrite if exists? (y/n)', defaultValue: 'n');
final overwriteStr =
_prompt(' Overwrite if exists? (y/n)', defaultValue: 'n');
final overwrite = overwriteStr.toLowerCase() == 'y';

return CreateFileAction(
Expand All @@ -363,7 +379,8 @@ class AddCustomCommand extends AlexCommand {

CreateDirAction _promptCreateDirAction() {
final path = _prompt(' Directory path to create');
final recursiveStr = _prompt(' Create parent directories? (y/n)', defaultValue: 'y');
final recursiveStr =
_prompt(' Create parent directories? (y/n)', defaultValue: 'y');
final recursive = recursiveStr.toLowerCase() == 'y';

return CreateDirAction(
Expand All @@ -374,9 +391,11 @@ class AddCustomCommand extends AlexCommand {

DeleteDirAction _promptDeleteDirAction() {
final path = _prompt(' Directory path to delete');
final recursiveStr = _prompt(' Delete recursively? (y/n)', defaultValue: 'y');
final recursiveStr =
_prompt(' Delete recursively? (y/n)', defaultValue: 'y');
final recursive = recursiveStr.toLowerCase() == 'y';
final ignoreNotFoundStr = _prompt(' Ignore if not found? (y/n)', defaultValue: 'y');
final ignoreNotFoundStr =
_prompt(' Ignore if not found? (y/n)', defaultValue: 'y');
final ignoreNotFound = ignoreNotFoundStr.toLowerCase() == 'y';

return DeleteDirAction(
Expand Down Expand Up @@ -414,7 +433,8 @@ class AddCustomCommand extends AlexCommand {
AppendToFileAction _promptAppendToFileAction() {
final path = _prompt(' File path');
final content = _prompt(' Content to append');
final createIfMissingStr = _prompt(' Create file if missing? (y/n)', defaultValue: 'y');
final createIfMissingStr =
_prompt(' Create file if missing? (y/n)', defaultValue: 'y');
final createIfMissing = createIfMissingStr.toLowerCase() == 'y';

return AppendToFileAction(
Expand All @@ -427,7 +447,8 @@ class AddCustomCommand extends AlexCommand {
PrependToFileAction _promptPrependToFileAction() {
final path = _prompt(' File path');
final content = _prompt(' Content to prepend');
final createIfMissingStr = _prompt(' Create file if missing? (y/n)', defaultValue: 'y');
final createIfMissingStr =
_prompt(' Create file if missing? (y/n)', defaultValue: 'y');
final createIfMissing = createIfMissingStr.toLowerCase() == 'y';

return PrependToFileAction(
Expand All @@ -441,7 +462,8 @@ class AddCustomCommand extends AlexCommand {
final message = _prompt(' Message to print');

printInfo(' Level:');
final level = _promptChoice('Select level', ['info', 'warning', 'error'], defaultValue: 'info');
final level = _promptChoice('Select level', ['info', 'warning', 'error'],
defaultValue: 'info');

return PrintAction(
message: message,
Expand All @@ -462,7 +484,9 @@ class AddCustomCommand extends AlexCommand {

CheckPlatformAction _promptCheckPlatformAction() {
printInfo(' Expected platform:');
final platform = _promptChoice('Select platform', ['macos', 'linux', 'windows'], defaultValue: 'macos');
final platform = _promptChoice(
'Select platform', ['macos', 'linux', 'windows'],
defaultValue: 'macos');

return CheckPlatformAction(platform: platform);
}
Expand All @@ -472,7 +496,8 @@ class AddCustomCommand extends AlexCommand {
final destination = _prompt(' Destination archive path');

printInfo(' Archive format:');
final format = _promptChoice('Select format', ['zip', 'tar.gz'], defaultValue: 'zip');
final format =
_promptChoice('Select format', ['zip', 'tar.gz'], defaultValue: 'zip');

return CreateArchiveAction(
source: source,
Expand Down Expand Up @@ -540,7 +565,8 @@ class AddCustomCommand extends AlexCommand {
}
}

printError('Invalid choice. Please enter a number (1-${choices.length}) or one of: ${choices.join(", ")}');
printError(
'Invalid choice. Please enter a number (1-${choices.length}) or one of: ${choices.join(", ")}');
}
}

Expand Down
10 changes: 7 additions & 3 deletions lib/commands/custom/check_custom_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ class CheckCustomCommandsCommand extends AlexCommand {
printInfo('Custom commands config file not found.');
printInfo('');
printInfo('Expected location: alex_custom_commands.yaml');
printInfo('Searched in current directory and parent directories (up to 10 levels)');
printInfo(
'Searched in current directory and parent directories (up to 10 levels)');
printInfo('');
printInfo('To create a config file, use:');
printInfo(' alex custom add');
Expand Down Expand Up @@ -54,7 +55,8 @@ class CheckCustomCommandsCommand extends AlexCommand {
}

if (yamlData is! YamlMap) {
printError('Invalid config file format: expected YAML map, got ${yamlData.runtimeType}');
printError(
'Invalid config file format: expected YAML map, got ${yamlData.runtimeType}');
return 1;
}

Expand Down Expand Up @@ -95,7 +97,9 @@ class CheckCustomCommandsCommand extends AlexCommand {
printInfo(' Arguments: ${cmd.arguments.length}');
for (final arg in cmd.arguments) {
final required = arg.required ? ' (required)' : '';
final defaultVal = arg.defaultValue != null ? ' [default: ${arg.defaultValue}]' : '';
final defaultVal = arg.defaultValue != null
? ' [default: ${arg.defaultValue}]'
: '';
printInfo(' --${arg.name}$required$defaultVal');
}
}
Expand Down
3 changes: 2 additions & 1 deletion lib/commands/custom/list_custom_commands.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class ListCustomCommandsCommand extends AlexCommand {
printInfo('');
}

printInfo('Config file: ${config.configPath ?? config.getOrCreateConfigPath()}');
printInfo(
'Config file: ${config.configPath ?? config.getOrCreateConfigPath()}');

return 0;
}
Expand Down
3 changes: 1 addition & 2 deletions lib/commands/custom/show_custom_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import 'package:alex/src/custom_commands/custom_command_config.dart';

/// Show details of a custom command.
class ShowCustomCommand extends AlexCommand {
ShowCustomCommand()
: super('show', 'Show details of a custom command.') {
ShowCustomCommand() : super('show', 'Show details of a custom command.') {
argParser.addOption(
'name',
abbr: 'n',
Expand Down
5 changes: 2 additions & 3 deletions lib/commands/custom/user_custom_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ class UserCustomCommand extends AlexCommand {

try {
// Create executor with verbose logger if needed
final executor = isVerbose
? CustomCommandExecutor(logger: out)
: _executor;
final executor =
isVerbose ? CustomCommandExecutor(logger: out) : _executor;

final exitCode = await executor.execute(_definition, arguments);
return exitCode;
Expand Down
2 changes: 2 additions & 0 deletions lib/commands/pubspec/pubspec_command.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:alex/commands/pubspec/update_command.dart';
import 'package:alex/commands/pubspec/version_command.dart';
import 'package:alex/runner/alex_command.dart';

import 'get_command.dart';
Expand All @@ -8,6 +9,7 @@ class PubspecCommand extends AlexCommand {
: super('pubspec', 'Work with pubspec.yaml.', const ['pub']) {
addSubcommand(UpdateCommand());
addSubcommand(GetCommand());
addSubcommand(VersionCommand());
}

@override
Expand Down
Loading
Loading