Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
86b165f
Improve CLI parity and harden file handling
TheusHen Jul 4, 2026
3b94435
Address Dart analyzer feedback
TheusHen Jul 4, 2026
fa300f6
Address PR review feedback
TheusHen Jul 6, 2026
2e4ab90
fix: skip symlinks in glob zipper
TheusHen Jul 7, 2026
b5ac380
Address follow-up review feedback
TheusHen Jul 7, 2026
d45149e
Address latest PR review feedback
TheusHen Jul 7, 2026
6fc8812
Add validation for non-empty apt options
Gorniaky Jul 7, 2026
ea65bf4
Validate 'apt' options in uninstall command
Gorniaky Jul 7, 2026
418a9cb
Refactor spinner success call in info.dart
Gorniaky Jul 7, 2026
818456b
Require app option and simplify directory logic
Gorniaky Jul 7, 2026
284b816
Refactor spinner parameter handling in backup.dart
Gorniaky Jul 7, 2026
7141841
Refactor glob assignment logic in commit.dart
Gorniaky Jul 7, 2026
e2bc5c7
Fix null safety for multiOptionOrRest method
Gorniaky Jul 7, 2026
00b1efc
Update app option handling in AppInfoCommand
Gorniaky Jul 7, 2026
da02f67
Refactor log handling functions and parameters
Gorniaky Jul 7, 2026
145a7ac
Refactor _handleMulti to remove appId parameter
Gorniaky Jul 7, 2026
2a7b2e0
Update glob option to include valueHelp
Gorniaky Jul 7, 2026
a7824b7
Update valueHelp for domain option in info command
Gorniaky Jul 7, 2026
b4d655f
Update valueHelp for subdomain id option
Gorniaky Jul 7, 2026
9a161fb
Simplify _handleSingle and _handleMulti methods
Gorniaky Jul 7, 2026
a4bf40f
Refactor option handling for app ID and glob
Gorniaky Jul 7, 2026
681659d
Refactor log handling methods for clarity and consistency
Gorniaky Jul 7, 2026
f8867c3
Update glob option to include valueHelp
Gorniaky Jul 7, 2026
4959990
Refactor download function for better error handling
Gorniaky Jul 7, 2026
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
9 changes: 6 additions & 3 deletions lib/commands/app/apt/install.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import "package:discloud/utils/messages.dart";
final class AppAptInstallCommand extends Command<void> {
AppAptInstallCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("app")
..addMultiOption("apt", help: appApts.join(","));
}

@override
final name = "install";

@override
final aliases = const ["i"];

@override
final description = "Install APT on your app";

@override
Future<void> run() async {
final appId = argResults!.option("app");
final apts = argResults!.multiOption("apt");
final appId = argResults!.requiredOptionOrRest("app");
final apts = argResults!.requiredMultiOptionOrRest("apt", ["app"]);

if (apts.isEmpty) usageException("Apt option cannot be empty");

Expand Down
9 changes: 6 additions & 3 deletions lib/commands/app/apt/uninstall.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import "package:discloud/utils/messages.dart";
final class AppAptUninstallCommand extends Command<void> {
AppAptUninstallCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("app")
..addMultiOption("apt", help: appApts.join(","));
}

@override
final name = "uninstall";

@override
final aliases = const ["u"];

@override
final description = "Uninstall APT from your app";

@override
Future<void> run() async {
final appId = argResults!.option("app");
final apts = argResults!.multiOption("apt");
final appId = argResults!.requiredOptionOrRest("app");
final apts = argResults!.requiredMultiOptionOrRest("apt", ["app"]);

if (apts.isEmpty) usageException("Apt option cannot be empty");

Expand Down
15 changes: 7 additions & 8 deletions lib/commands/app/backup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@ import "package:discloud/utils/download.dart";
import "package:discloud/utils/messages.dart";
import "package:discloud/utils/progress.dart";
import "package:discloud/utils/speed_monitor.dart";

const _pSep = "/";
import "package:path/path.dart" hide context;

final class AppBackupCommand extends Command<void> with Disposable {
AppBackupCommand() {
argParser
..addOption("app", mandatory: true, valueHelp: "all")
..addOption("app", valueHelp: "all")
..addOption(
"dir",
abbr: "d",
aliases: const ["out"],
aliases: const ["out", "path"],
help:
"Specifies the destination path for downloading backups. The destination path will be considered a directory.",
);
Expand All @@ -41,7 +40,7 @@ final class AppBackupCommand extends Command<void> with Disposable {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final appId = argResults!.requiredOptionOrRest("app");

final spinner = context.printer.spin(text: "Fetching backup...");

Expand All @@ -61,7 +60,7 @@ final class AppBackupCommand extends Command<void> with Disposable {

Future<void> _handleSingle(Map<dynamic, dynamic> data, ISpin spinner) async {
if (data["url"] case final String url) {
if (argResults?.option("dir") case final dir?) {
if (argResults!.optionOrRest("dir", ["app"]) case final dir) {
final Uri uri = .parse(url);

return _download(dir: dir, spinner: spinner, uri: uri);
Expand All @@ -73,7 +72,7 @@ final class AppBackupCommand extends Command<void> with Disposable {

Future<void> _handleMulti(List list, ISpin spinner) async {
final client = _client = .new();
final dir = argResults?.option("dir") ?? ".";
final dir = argResults?.optionOrRest("dir") ?? ".";

for (final data in list) {
final String appId = data["id"];
Expand All @@ -99,7 +98,7 @@ final class AppBackupCommand extends Command<void> with Disposable {
HttpClient? client,
}) async {
final filename = uri.pathSegments.last;
final filepath = "$dir$_pSep$filename";
final filepath = joinAll([dir, filename]);
final file = _file = .new(filepath);

final monitor = _monitor = .new();
Expand Down
8 changes: 5 additions & 3 deletions lib/commands/app/commit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,15 @@ final class AppCommitCommand extends Command<void> with Disposable {
Future<void> run() async {
final directory = context.workspaceFolder;

final optionAppId = argResults!.option("app");
final appId =
argResults!.option("app") ?? await _getDiscloudConfigAppId(directory);
optionAppId ??
argResults?.rest.firstOrNull ??
await _getDiscloudConfigAppId(directory);

if (appId == null) throw Exception("Missing app id");

final glob = argResults!.multiOption("glob");
final glob = argResults?.multiOptionOrRest("glob", [if (optionAppId != null) "app"]) ?? const ["**"];

final spinner = context.printer.spin(text: "Zipping...");

Expand Down Expand Up @@ -82,7 +85,6 @@ final class AppCommitCommand extends Command<void> with Disposable {
);
},
onUploadDone: () {
_file = null;
spinner.start("Processing...");
},
);
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/app/console.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import "package:tint/tint.dart";
final class AppConsoleCommand extends Command<void> {
AppConsoleCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("app")
..addOption("command");
}

Expand All @@ -26,7 +26,7 @@ final class AppConsoleCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app")!;
final appId = argResults!.requiredOptionOrRest("app");
String? command = argResults!.option("command");

final spinner = context.printer.spin(start: false);
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/app/delete.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class AppDeleteCommand extends Command<void> {
help: "Skip confirmation prompt",
negatable: false,
)
..addOption("app", mandatory: true);
..addOption("app");
}

@override
Expand All @@ -25,7 +25,7 @@ final class AppDeleteCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final appId = argResults!.requiredOptionOrRest("app");

final result =
argResults!.flag("yes") ||
Expand Down
4 changes: 2 additions & 2 deletions lib/commands/app/info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const _keysIgnore = {

final class AppInfoCommand extends Command<void> {
AppInfoCommand() {
argParser.addOption("app", defaultsTo: "all");
argParser.addOption("app", valueHelp: "all");
}

@override
Expand All @@ -33,7 +33,7 @@ final class AppInfoCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final appId = argResults?.optionOrRest("app") ?? "all";

final spinner = context.printer.spin(text: "Fetching app info...");

Expand Down
37 changes: 21 additions & 16 deletions lib/commands/app/logs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ final class AppLogsCommand extends Command<void> {
argParser
..addOption(
"app",
mandatory: true,
valueHelp: "all",
help:
"When set to 'all', this command will automatically download logs and will not display URLs. If the 'out' option is not set, downloads will be made to the current folder.",
)
..addOption(
"out",
aliases: const ["path"],
help:
"Specifies the destination path for downloading logs. When the application option is set to 'all', the destination path will be considered a directory where all downloads will be stored.",
);
Expand All @@ -33,7 +33,8 @@ final class AppLogsCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final appId = argResults!.optionOrRest("app") ?? "all";
final out = argResults!.optionOrRest("out", ["app"]);

final spinner = context.printer.spin();

Expand All @@ -43,40 +44,44 @@ final class AppLogsCommand extends Command<void> {

switch (response["apps"]) {
case final Map data:
await _handleSingle(data, argResults?.option("out"), spinner);
await _handleSingle(data, spinner, appId, out);
break;
case final List list:
await _handleMulti(list, spinner);
await _handleMulti(list, spinner, out ?? ".");
break;
}
}

Future<void> _handleSingle(
Map<dynamic, dynamic> data,
String? out,
ISpin spinner,
String appId,
String? out,
) async {
if (data["terminal"]?["big"] case final String data) {
if (out case final out?) return _saveLog(data, out, spinner);
context.printer.writeln(data);
if (data["terminal"]?["big"] case final String contents) {
if (out case final out?) {
return _saveLog(contents, _resolvePath(out, appId), spinner);
}
context.printer.writeln(contents);
}
}

Future<void> _handleMulti(List list, ISpin spinner) async {
final out = argResults?.option("out") ?? ".";

Future<void> _handleMulti(List list, ISpin spinner, String out) async {
for (final data in list) {
final String appId = data["id"];
final filename = "$appId.log";
final filepath = joinAll([out, filename]);
await _handleSingle(data, filepath, spinner);
await _handleSingle(data, spinner, appId, out);
}
}

Future<void> _saveLog(String data, String out, ISpin spinner) async {
final file = File(out);
await file.create(recursive: true);
final File file = .new(out);
await file.parent.create(recursive: true);
await file.writeAsString(stripAnsi(data));
spinner.success(out);
}

String _resolvePath(String out, String appId) {
if (extension(out).isNotEmpty) return out;
return joinAll([out, "$appId.log"]);
}
}
8 changes: 4 additions & 4 deletions lib/commands/app/mod/add.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import "package:discloud/utils/messages.dart";
final class AppModAddCommand extends Command<void> {
AppModAddCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("mod", mandatory: true)
..addOption("app")
..addOption("mod")
..addMultiOption("perms", allowed: appModPerms);
}

Expand All @@ -21,8 +21,8 @@ final class AppModAddCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final modId = argResults!.option("mod");
final appId = argResults!.requiredOptionOrRest("app");
final modId = argResults!.requiredOptionOrRest("mod", ["app"]);
final perms = argResults!.multiOption("perms");

final spinner = context.printer.spin(text: "Adding app MOD...");
Expand Down
10 changes: 5 additions & 5 deletions lib/commands/app/mod/delete.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import "package:discloud/utils/messages.dart";
final class AppModDeleteCommand extends Command<void> {
AppModDeleteCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("mod", mandatory: true);
..addOption("app")
..addOption("mod");
}

@override
Expand All @@ -19,12 +19,12 @@ final class AppModDeleteCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final modId = argResults!.option("mod");
final appId = argResults!.requiredOptionOrRest("app");
final modId = argResults!.requiredOptionOrRest("mod", ["app"]);

final spinner = context.printer.spin(text: "Deleting app MOD...");

final response = await context.api.delete("/app/$appId/team$modId");
final response = await context.api.delete("/app/$appId/team/$modId");

spinner.success(resolveResponseMessage(response));
}
Expand Down
8 changes: 4 additions & 4 deletions lib/commands/app/mod/edit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import "package:discloud/utils/messages.dart";
final class AppModEditCommand extends Command<void> {
AppModEditCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("mod", mandatory: true)
..addOption("app")
..addOption("mod")
..addMultiOption("perms", allowed: appModPerms);
}

Expand All @@ -21,8 +21,8 @@ final class AppModEditCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final modId = argResults!.option("mod");
final appId = argResults!.requiredOptionOrRest("app");
final modId = argResults!.requiredOptionOrRest("mod", ["app"]);
final perms = argResults!.multiOption("perms");

final spinner = context.printer.spin(text: "Editing app MOD...");
Expand Down
10 changes: 4 additions & 6 deletions lib/commands/app/mod/info.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import "dart:async";
import "dart:io";

import "package:args/command_runner.dart";
import "package:discloud/extensions/command.dart";
import "package:discloud/utils/ascii_table.dart";
import "package:discloud/utils/messages.dart";

final class AppModInfoCommand extends Command<void> {
AppModInfoCommand() {
argParser.addOption("app", mandatory: true);
argParser.addOption("app");
}

@override
Expand All @@ -19,16 +17,16 @@ final class AppModInfoCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app");
final appId = argResults!.requiredOptionOrRest("app");

final spinner = context.printer.spin(text: "Fetching app MODs...");

final response = await context.api.get("/app/$appId/team");

spinner.success(resolveResponseMessage(response));

if (response["app"] case final Map data) {
stdout.writeln(mapToVerticalAsciiTable(data));
if (response["team"] case final List list when list.isNotEmpty) {
context.printer.writeln(listToAsciiTable(list));
}
}
}
4 changes: 2 additions & 2 deletions lib/commands/app/profile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "package:discloud/utils/messages.dart";
final class AppProfileCommand extends Command<void> {
AppProfileCommand() {
argParser
..addOption("app", mandatory: true)
..addOption("app")
..addOption("name")
..addOption("avatar");
}
Expand All @@ -21,7 +21,7 @@ final class AppProfileCommand extends Command<void> {

@override
Future<void> run() async {
final appId = argResults!.option("app")!;
final appId = argResults!.requiredOptionOrRest("app");
final name = argResults?.option("name");
final avatar = argResults?.option("avatar");

Expand Down
Loading
Loading