134 lines
3.5 KiB
Dart
134 lines
3.5 KiB
Dart
import 'package:args/command_runner.dart';
|
|
import 'package:file/file.dart';
|
|
import 'package:file/local.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:yaml/yaml.dart';
|
|
|
|
import 'config.dart';
|
|
|
|
/// Options passed to every [DewInitHook] during `dew init`.
|
|
class DewInitOptions {
|
|
/// Whether to create `.gitkeep` files in newly-created empty directories.
|
|
final bool gitkeep;
|
|
|
|
const DewInitOptions({this.gitkeep = true});
|
|
}
|
|
|
|
/// Creates the initial `.project/` scaffold for `dew init`.
|
|
///
|
|
/// Each module (kanban, mcp, etc.) registers a hook so it can create its own
|
|
/// subdirectories and config alongside the core scaffold.
|
|
abstract interface class DewInitHook {
|
|
/// Called after `dew.yaml` is written. [projectRoot] is the resolved absolute
|
|
/// path of the project being initialised; [config] is the loaded config;
|
|
/// [options] carries flags like [DewInitOptions.gitkeep].
|
|
Future<void> onInit(
|
|
String projectRoot,
|
|
DewConfig config,
|
|
DewInitOptions options,
|
|
);
|
|
}
|
|
|
|
const _defaultDewYaml = '''
|
|
dew:
|
|
mcp:
|
|
host: "localhost"
|
|
port: 8080
|
|
|
|
kanban:
|
|
prefix: "PROJ"
|
|
ticket_types:
|
|
- id: "epic"
|
|
name: "Epic"
|
|
- id: "story"
|
|
name: "Story"
|
|
- id: "task"
|
|
name: "Task"
|
|
- id: "bug"
|
|
name: "Bug"
|
|
- id: "spike"
|
|
name: "Spike"
|
|
columns:
|
|
- id: "backlog"
|
|
name: "Backlog"
|
|
color: "blue"
|
|
- id: "doing"
|
|
name: "Doing"
|
|
color: "yellow"
|
|
- id: "done"
|
|
name: "Done"
|
|
color: "green"
|
|
''';
|
|
|
|
const _projectGitignore = '''
|
|
/secrets/
|
|
/toolchain/
|
|
/cache/
|
|
''';
|
|
|
|
class InitCommand extends Command<void> {
|
|
final List<DewInitHook> _hooks;
|
|
final FileSystem _fs;
|
|
|
|
InitCommand(this._hooks, {FileSystem fs = const LocalFileSystem()})
|
|
: _fs = fs {
|
|
argParser
|
|
..addOption(
|
|
'path',
|
|
abbr: 'p',
|
|
help: 'Path to the project root to initialise.',
|
|
defaultsTo: '.',
|
|
)
|
|
..addFlag(
|
|
'gitkeep',
|
|
help: 'Add .gitkeep files to newly-created empty directories.',
|
|
defaultsTo: true,
|
|
);
|
|
}
|
|
|
|
@override
|
|
final String name = 'init';
|
|
|
|
@override
|
|
final String description =
|
|
'Initialise a Dew project at the given path, creating '
|
|
'.project/dew.yaml and scaffolding module directories.';
|
|
|
|
@override
|
|
Future<void> run() async {
|
|
final rawPath = argResults!['path'] as String;
|
|
final gitkeep = argResults!['gitkeep'] as bool;
|
|
final projectRoot = p.canonicalize(rawPath);
|
|
final options = DewInitOptions(gitkeep: gitkeep);
|
|
|
|
final projectDir = _fs.directory(p.join(projectRoot, '.project'));
|
|
final configFile = _fs.file(p.join(projectDir.path, 'dew.yaml'));
|
|
final gitignoreFile = _fs.file(p.join(projectDir.path, '.gitignore'));
|
|
|
|
await projectDir.create(recursive: true);
|
|
|
|
if (await configFile.exists()) {
|
|
print(' found .project/dew.yaml (already exists, skipping)');
|
|
} else {
|
|
await configFile.writeAsString(_defaultDewYaml.trimLeft());
|
|
print(' created .project/dew.yaml');
|
|
}
|
|
|
|
if (await gitignoreFile.exists()) {
|
|
print(' found .project/.gitignore (already exists, skipping)');
|
|
} else {
|
|
await gitignoreFile.writeAsString(_projectGitignore);
|
|
print(' created .project/.gitignore');
|
|
}
|
|
|
|
final config = DewConfig.fromYaml(
|
|
loadYaml(await configFile.readAsString()) as YamlMap,
|
|
);
|
|
|
|
for (final hook in _hooks) {
|
|
await hook.onInit(projectRoot, config, options);
|
|
}
|
|
|
|
print('\nProject initialised at $projectRoot');
|
|
}
|
|
}
|