From 86ebaf468b7d889bdc51becd3f55b0dfba9e48d7 Mon Sep 17 00:00:00 2001 From: mydoxie <351276057+mydoxie@users.noreply.github.com> Date: Tue, 3 Nov 2026 12:29:11 +0700 Subject: [PATCH] Fix exit code of an unknown command and the model doc comment An unknown command in an app with package commands exited with 0. The runner now sets exit code 1. An old runner file is written again when its text is not the same as the new one. The make:model stub had [...] in a doc comment, which gave the analyzer info unintended_html_in_doc_comment. It is now in backticks. --- CHANGELOG.md | 2 ++ lib/src/tools/create_model.dart | 2 +- lib/src/tools/package_commands.dart | 13 +++++++++++- test/generators_test.dart | 31 +++++++++++++++++++++++++++++ test/package_commands_e2e_test.dart | 1 + test/package_commands_test.dart | 17 ++++++++++++++++ 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14d6a0b..57e85e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +- Fix: an unknown command in an app with package commands (like an app with dox_queue) now exits with code 1. Before, it printed "Could not find a command" and exited with 0. An app that already has `.dart_tool/dox/commands.dart` gets the new file on the next run. +- Fix: the model from `dox make:model` has no `unintended_html_in_doc_comment` info any more. The `[...]` example in its doc comment is now in backticks. - New: package commands. A package (or the app) adds `dox` commands with the `dox: commands:` key in its pubspec.yaml, like `dox: {commands: package:dox_queue/commands.dart}`. `dox ` finds them when the name is not a built-in command. It reads `.dart_tool/package_config.json`, writes `.dart_tool/dox/commands.dart`, and runs it with `dart run`. The exit code of the command is the exit code of `dox`. The file is written again only when package_config.json or a pubspec.yaml change. - A built-in command always wins. Two packages with the same command name print an error (exit code 1). A bad `dox: commands:` key prints a warning, and the other packages still work. - `dox help` lists the package commands, one group per package. `dox help ` shows one package command. Outside an app, or when no package has commands, it does not start Dart. diff --git a/lib/src/tools/create_model.dart b/lib/src/tools/create_model.dart index 6edf88b..e3fccc0 100644 --- a/lib/src/tools/create_model.dart +++ b/lib/src/tools/create_model.dart @@ -16,7 +16,7 @@ class $className extends ${className}Generator { List get hidden => []; /// Columns that create(), insert() and update() may take from a map, - /// like req.body. Add your columns here, eg. ['title', 'body']. + /// like req.body. Add your columns here, eg. `['title', 'body']`. /// While it is empty, create(req.body) throws MassAssignmentException. @override List get fillable => []; diff --git a/lib/src/tools/package_commands.dart b/lib/src/tools/package_commands.dart index 6df32cc..eac5d67 100644 --- a/lib/src/tools/package_commands.dart +++ b/lib/src/tools/package_commands.dart @@ -118,7 +118,17 @@ class PackageCommands { (runner.existsSync() || (cache['libraries'] as Map).isEmpty)) { _files = Map.from(cache['files'] as Map); _cacheKey = key; - return Map.from(cache['libraries'] as Map); + Map libs = Map.from( + cache['libraries'] as Map, + ); + + /// a new dox CLI can make a different runner, so write it again + /// when its text is old + if (libs.isNotEmpty) { + String source = runnerSource(libs); + if (runner.readAsStringSync() != source) _writeAtomic(runner, source); + } + return libs; } Map files = {}; Map libs = discover(files: files); @@ -339,6 +349,7 @@ Future main(List args) async { } if (matches == 0) { print($notFound.replaceAll('{name}', name)); + exitCode = 1; return; } if (matches > 1) { diff --git a/test/generators_test.dart b/test/generators_test.dart index 4f4d9ac..7bfa0be 100644 --- a/test/generators_test.dart +++ b/test/generators_test.dart @@ -431,6 +431,37 @@ void main() { }); }); + group('generators | doc comments', () { + /// `` in a doc comment, outside backticks, is the analyzer + /// info unintended_html_in_doc_comment in the app + test('no <...> outside backticks', () async { + File('pubspec.yaml').writeAsStringSync('name: my_app\n'); + createController('PostController', true); + createModel('Post'); + createMiddleware('AuthMiddleware'); + createDto('PostDto'); + createSerializer('PostSerializer'); + createFactory('PostFactory'); + createSeeder('PostSeeder'); + createJob('SendMailJob'); + await CommandRegistry.findCommand('make:migration')! + .function(['create_posts_table']); + RegExp doc = RegExp(r'^\s*///(.*)$'); + RegExp code = RegExp(r'`[^`]*`'); + List bad = []; + for (FileSystemEntity f in Directory.current.listSync(recursive: true)) { + if (f is! File || !f.path.endsWith('.dart')) continue; + for (String line in f.readAsLinesSync()) { + Match? m = doc.firstMatch(line); + if (m != null && m.group(1)!.replaceAll(code, '').contains('<')) { + bad.add('${f.path}: $line'); + } + } + } + expect(bad, isEmpty); + }); + }); + group('dox binary', () { test('make files through the real cli in a temp dir', () async { String bin = '${originalCwd.path}/bin/dox.dart'; diff --git a/test/package_commands_e2e_test.dart b/test/package_commands_e2e_test.dart index 8df8827..c664f07 100644 --- a/test/package_commands_e2e_test.dart +++ b/test/package_commands_e2e_test.dart @@ -309,6 +309,7 @@ final List commands = [Late()]; ProcessResult r = await dox(['nope']); expect(r.stdout, contains('Could not find a command named "nope"')); expect(r.stdout, contains('`dox: commands:`')); + expect(r.exitCode, 1); }); test('help lists the package commands by package', () async { diff --git a/test/package_commands_test.dart b/test/package_commands_test.dart index f66b504..a663749 100644 --- a/test/package_commands_test.dart +++ b/test/package_commands_test.dart @@ -197,6 +197,23 @@ void main() { expect(PackageCommands(root: app).libraries().keys, ['app']); }); + test('an old runner from an older CLI is written again', () { + package( + 'pkg', + withKey, + lib: {'commands.dart': 'var commands = [];'}, + ); + package('app', '', dir: app); + packageConfig(['pkg']); + PackageCommands commands = PackageCommands(root: app); + Map libs = commands.libraries(); + commands.runner.writeAsStringSync('// old runner'); + + /// the cache key is the same, but the runner text is old + expect(PackageCommands(root: app).libraries(), libs); + expect(commands.runner.readAsStringSync(), commands.runnerSource(libs)); + }); + test('a path dependency that changes its key is read again', () { package('pkg', ''); package('app', '', dir: app);