diff --git a/docs/features/vault.md b/docs/features/vault.md new file mode 100644 index 0000000..eba52a6 --- /dev/null +++ b/docs/features/vault.md @@ -0,0 +1,169 @@ +# Dew Vault Secret Manager + +Dew Vault is a secret manager for your projects. It helps you keep secrets out of +version control by storing each secret as an encrypted file under `.project/vault`. +By default the vault password is stored in `.project/secrets/dew.vault.password`. + +## Config + +Vault settings live in `.project/dew.yaml` under `dew.vault`. + +```yaml +dew: + vault: + password_file: .project/secrets/dew.vault.password + storage_dir: .project/vault + generators: + postgres_password: + type: random_password + description: Generate random PostgreSQL passwords. + config: + length: 64 + include_symbols: true + jwt_secret: + type: random_token + description: Generate JWT signing secrets. + config: + encoding: base64 + bytes: 48 + service_uuid: + type: uuid_v4 + description: Generate stable-looking unique IDs. +``` + +`generators` maps a generator name (for example `postgres_password`) to a built-in +generator definition. Values under `config` are defaults and can be overridden per +run or stored in secret rotation metadata. + +Built-in generator types are resolved inside Dew, so secrets can be generated +without depending on host binaries. + +## Commands + +Most commands support `--format [default|json]` (default is `default`) for +machine-friendly automation. + +### Initialize Vault + +Initialize the vault storage and metadata. + +```bash +dew vault init + +dew vault init --password-file .project/secrets/dew.vault.password +``` + +### List all secrets + +List stored secrets. + +```bash +dew vault list +dew vault list --format json +``` + +### Set a secret + +`set` stores or replaces a secret and optional metadata. + +```bash +dew vault set # Prompts for secret value +dew vault set --env ENV_VAR_NAME # Uses value from environment variable +dew vault set --file /path/to/secret.txt # Uses value from file +echo "secret value" | dew vault set # Uses piped stdin + +# Include metadata for automated rotation requirements +dew vault set DB_PASSWORD --metadata '{"rotation":{"enabled":true,"generator":"postgres_password","length":64}}' +dew vault set DB_PASSWORD --metadata-file .project/vault/db_password.meta.json +``` + +### Get a secret + +`get` retrieves a secret by name. + +```bash +dew vault get +dew vault get --format json +``` + +### Update a secret + +`update` patches secret metadata and/or value. Omit value source flags to edit +metadata only. + +```bash +dew vault update --env ROLLED_PASSWORD +dew vault update --metadata '{"rotation":{"enabled":false}}' +dew vault update --metadata-file .project/vault/db_password.meta.json +``` + +### Rename a secret + +`rename` changes a secret identifier while preserving value and metadata. + +```bash +dew vault rename OLD_NAME NEW_NAME +dew vault rename OLD_NAME NEW_NAME --format json +``` + +### Generate a secret value + +`generate` runs a built-in generator without writing to the vault by default. + +```bash +dew vault generate postgres_password --length 64 --include_symbols +dew vault generate jwt_secret --bytes 64 --encoding base64 +dew vault generate postgres_password --service payments --username app_user --format json +``` + +Pipe generated output directly into `set` when needed: + +```bash +dew vault generate postgres_password --service payments | dew vault set DB_PASSWORD +``` + +### Rotate secrets + +`rotate` rewraps secrets with a new vault password when run without a name. +When run with a secret name, it rotates only that secret. For a secret with rotation +metadata (`rotation.enabled: true` and `rotation.generator`), Dew invokes that +configured built-in generator using the provided rotation values. + +```bash +dew vault rotate +dew vault rotate +dew vault rotate --format json +``` + +### Delete a secret + +`delete` removes a secret and metadata from the vault. + +```bash +dew vault delete +dew vault delete --format json +``` + +### Metadata format for rotation-aware secrets + +Attach arbitrary metadata and include rotation policy details. Example shape: + +```json +{ + "rotation": { + "enabled": true, + "generator": "postgres_password", + "service": "payments", + "username": "app_user", + "length": 64 + }, + "notes": "Rotate monthly and update app config via sidecar" +} +``` + +Rotation flow: + +1. Define a built-in generator in `dew.yaml` under `dew.vault.generators`. +2. Attach `rotation.generator` and generator args to the secret metadata. +3. Run `dew vault rotate ` to rotate one secret, or `dew vault rotate` + to rotate all configured secrets. diff --git a/packages/cli/bin/dew.dart b/packages/cli/bin/dew.dart index 367a57f..e4d1f40 100644 --- a/packages/cli/bin/dew.dart +++ b/packages/cli/bin/dew.dart @@ -2,11 +2,13 @@ import 'package:args/command_runner.dart'; import 'package:dew_core/dew_core.dart'; import 'package:dew_kanban/dew_kanban.dart' as kanban; import 'package:dew_mcp/dew_mcp.dart' as mcp; +import 'package:dew_vault/dew_vault.dart' as vault; Future main(List args) async { final commandRegistry = CommandRegistry(); kanban.registerCommands(commandRegistry); + vault.registerCommands(commandRegistry); mcp.registerCommands(commandRegistry); final runner = CommandRunner('dew', 'A project management tool.'); diff --git a/packages/cli/pubspec.yaml b/packages/cli/pubspec.yaml index e825305..1849056 100644 --- a/packages/cli/pubspec.yaml +++ b/packages/cli/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: args: ^2.7.0 dew_core: ^0.1.0 dew_kanban: ^0.1.0 + dew_vault: ^0.1.0 dew_mcp: ^0.1.0 dev_dependencies: diff --git a/packages/cli/test/cli_test.dart b/packages/cli/test/cli_test.dart index 6156a2f..b0417b4 100644 --- a/packages/cli/test/cli_test.dart +++ b/packages/cli/test/cli_test.dart @@ -2,12 +2,14 @@ import 'package:args/command_runner.dart'; import 'package:dew_core/dew_core.dart'; import 'package:dew_kanban/dew_kanban.dart' as kanban; import 'package:dew_mcp/dew_mcp.dart' as mcp; +import 'package:dew_vault/dew_vault.dart' as vault; import 'package:test/test.dart'; /// Builds the same CommandRunner as bin/dew.dart without actually running it. CommandRunner buildRunner() { final commandRegistry = CommandRegistry(); kanban.registerCommands(commandRegistry); + vault.registerCommands(commandRegistry); mcp.registerCommands(commandRegistry); final runner = CommandRunner('dew', 'A project management tool.'); @@ -24,9 +26,9 @@ void main() { expect(buildRunner, returnsNormally); }); - test('has kanban, init, and mcp commands registered', () { + test('has kanban, vault, init, and mcp commands registered', () { final runner = buildRunner(); - expect(runner.commands.keys, containsAll(['kanban', 'init', 'mcp'])); + expect(runner.commands.keys, containsAll(['kanban', 'vault', 'init', 'mcp'])); }); test('--help flag does not throw', () async { diff --git a/packages/vault/CHANGELOG.md b/packages/vault/CHANGELOG.md new file mode 100644 index 0000000..f26f4c3 --- /dev/null +++ b/packages/vault/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## 0.2.0 — 2026-05-03 + +Initial feature stub package for vault support. + +- Added `dew vault` command registration and MCP tool wiring. +- Added vault subcommands for init/list/get/set/update/rename/rotate/generate/delete. +- Added `VaultInitHook` scaffold for `.project/vault` and `.project/secrets`. diff --git a/packages/vault/LICENSE b/packages/vault/LICENSE new file mode 100644 index 0000000..4ec485e --- /dev/null +++ b/packages/vault/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 artificery-dev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/vault/README.md b/packages/vault/README.md new file mode 100644 index 0000000..d06625b --- /dev/null +++ b/packages/vault/README.md @@ -0,0 +1,16 @@ +# dew_vault + +Vault feature package for the [Dew](https://github.com/artificerchris/dew) project +management tool. + +This package provides the `dew vault` command surface and registers Vault commands +as MCP tools through `DewToolCommand`. + +## Status + +This package is currently a scaffold/stub to establish the command and MCP +wiring. It does not yet implement full encrypted secret storage or rotation logic. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/packages/vault/lib/dew_vault.dart b/packages/vault/lib/dew_vault.dart new file mode 100644 index 0000000..19d49af --- /dev/null +++ b/packages/vault/lib/dew_vault.dart @@ -0,0 +1,19 @@ +library; + +export 'src/dew_vault_base.dart'; + +import 'package:dew_core/dew_core.dart'; +import 'package:file/file.dart'; +import 'package:file/local.dart'; + +import 'package:dew_vault/src/dew_vault_base.dart'; +import 'package:dew_vault/src/vault_init_hook.dart'; + +/// Registers all Vault commands and init hooks into [registry]. +void registerCommands( + CommandRegistry registry, { + FileSystem fs = const LocalFileSystem(), +}) { + registry.register(VaultCommand()); + registry.registerInitHook(VaultInitHook(fs: fs)); +} diff --git a/packages/vault/lib/src/command_output.dart b/packages/vault/lib/src/command_output.dart new file mode 100644 index 0000000..6290cd2 --- /dev/null +++ b/packages/vault/lib/src/command_output.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; + +String renderVaultOutput({ + String format = 'default', + required String message, + Map? json, +}) { + if (format == 'json') { + final payload = { + 'message': message, + if (json != null) ...json, + }; + return const JsonEncoder.withIndent(' ').convert(payload); + } + return message; +} + +String requireStringArg(Map args, String key) { + final value = args[key]; + if (value == null || value.toString().trim().isEmpty) { + throw ArgumentError('Missing required argument: --$key'); + } + return value.toString(); +} + +String formatFromArgs(Map args) { + final value = args['format']; + if (value == null || value.toString().trim().isEmpty) return 'default'; + return value.toString(); +} diff --git a/packages/vault/lib/src/commands/delete_command.dart b/packages/vault/lib/src/commands/delete_command.dart new file mode 100644 index 0000000..4112770 --- /dev/null +++ b/packages/vault/lib/src/commands/delete_command.dart @@ -0,0 +1,42 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class DeleteCommand extends DewCommand with DewToolCommand { + DeleteCommand() { + argParser + ..addOption( + 'name', + abbr: 'n', + mandatory: true, + help: 'Secret name.', + ) + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'delete'; + + @override + final String description = 'Delete a secret.'; + + @override + final String toolName = 'vault_delete_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final secretName = requireStringArg(args, 'name'); + return renderVaultOutput( + format: format, + message: 'Delete stub executed.', + json: {'secret': secretName}, + ); + } + +} diff --git a/packages/vault/lib/src/commands/generate_command.dart b/packages/vault/lib/src/commands/generate_command.dart new file mode 100644 index 0000000..9203195 --- /dev/null +++ b/packages/vault/lib/src/commands/generate_command.dart @@ -0,0 +1,51 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class GenerateCommand extends DewCommand with DewToolCommand { + GenerateCommand() { + argParser + ..addOption( + 'generator', + abbr: 'g', + mandatory: true, + help: 'Generator ID.', + ) + ..addMultiOption( + 'arg', + help: 'Generator option as key=value. Repeat as needed.', + ) + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'generate'; + + @override + final String description = 'Generate a new secret value using a built-in generator.'; + + @override + final String toolName = 'vault_generate_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final generator = requireStringArg(args, 'generator'); + final overrides = args['arg'] as List?; + return renderVaultOutput( + format: format, + message: 'Generate stub output.', + json: { + 'generator': generator, + 'options': overrides == null ? const [] : overrides, + 'value': '', + }, + ); + } + +} diff --git a/packages/vault/lib/src/commands/get_command.dart b/packages/vault/lib/src/commands/get_command.dart new file mode 100644 index 0000000..282f53a --- /dev/null +++ b/packages/vault/lib/src/commands/get_command.dart @@ -0,0 +1,37 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class GetCommand extends DewCommand with DewToolCommand { + GetCommand() { + argParser + ..addOption('name', abbr: 'n', mandatory: true, help: 'Secret name.') + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'get'; + + @override + final String description = 'Get a secret value.'; + + @override + final String toolName = 'vault_get_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final secretName = requireStringArg(args, 'name'); + return renderVaultOutput( + format: format, + message: 'Get stub value: [redacted].', + json: {'secret': secretName}, + ); + } + +} diff --git a/packages/vault/lib/src/commands/init_command.dart b/packages/vault/lib/src/commands/init_command.dart new file mode 100644 index 0000000..b8b2679 --- /dev/null +++ b/packages/vault/lib/src/commands/init_command.dart @@ -0,0 +1,51 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class VaultInitCommand extends DewCommand with DewToolCommand { + VaultInitCommand() { + argParser + ..addOption( + 'password-file', + abbr: 'p', + defaultsTo: '.project/secrets/dew.vault.password', + help: 'Path to the vault password file to record in config.', + ) + ..addOption( + 'storage-dir', + defaultsTo: '.project/vault', + help: 'Directory where encrypted secret files are stored.', + ) + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'init'; + + @override + final String description = 'Initialise vault directories and defaults.'; + + @override + final String toolName = 'vault_init'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final passwordFile = requireStringArg(args, 'password-file'); + final storageDir = requireStringArg(args, 'storage-dir'); + return renderVaultOutput( + format: format, + message: 'Vault init stub completed.', + json: { + 'password_file': passwordFile, + 'storage_dir': storageDir, + 'initialized': true, + }, + ); + } +} diff --git a/packages/vault/lib/src/commands/list_command.dart b/packages/vault/lib/src/commands/list_command.dart new file mode 100644 index 0000000..ac35a71 --- /dev/null +++ b/packages/vault/lib/src/commands/list_command.dart @@ -0,0 +1,34 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class ListCommand extends DewCommand with DewToolCommand { + ListCommand() { + argParser.addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'list'; + + @override + final String description = 'List vault secrets.'; + + @override + final String toolName = 'vault_list_secrets'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + return renderVaultOutput( + format: format, + message: 'No secrets found (stubbed vault).', + json: {'secrets': [], 'count': 0}, + ); + } + +} diff --git a/packages/vault/lib/src/commands/rename_command.dart b/packages/vault/lib/src/commands/rename_command.dart new file mode 100644 index 0000000..58c2f90 --- /dev/null +++ b/packages/vault/lib/src/commands/rename_command.dart @@ -0,0 +1,47 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class RenameCommand extends DewCommand with DewToolCommand { + RenameCommand() { + argParser + ..addOption( + 'from', + mandatory: true, + help: 'Current secret name.', + ) + ..addOption( + 'to', + mandatory: true, + help: 'New secret name.', + ) + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'rename'; + + @override + final String description = 'Rename a secret while preserving value and metadata.'; + + @override + final String toolName = 'vault_rename_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final from = requireStringArg(args, 'from'); + final to = requireStringArg(args, 'to'); + return renderVaultOutput( + format: format, + message: 'Rename stub executed.', + json: {'from': from, 'to': to}, + ); + } + +} diff --git a/packages/vault/lib/src/commands/rotate_command.dart b/packages/vault/lib/src/commands/rotate_command.dart new file mode 100644 index 0000000..5cf1577 --- /dev/null +++ b/packages/vault/lib/src/commands/rotate_command.dart @@ -0,0 +1,38 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class RotateCommand extends DewCommand with DewToolCommand { + RotateCommand() { + argParser + ..addOption('name', help: 'Secret name to rotate; omit to rotate vault password.') + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'rotate'; + + @override + final String description = + 'Rotate vault password or a single secret value (stub).'; + + @override + final String toolName = 'vault_rotate_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final target = args['name']?.toString(); + return renderVaultOutput( + format: format, + message: 'Rotate stub executed.', + json: {'target': target ?? '', 'scope': target == null ? 'vault' : 'secret'}, + ); + } + +} diff --git a/packages/vault/lib/src/commands/set_command.dart b/packages/vault/lib/src/commands/set_command.dart new file mode 100644 index 0000000..5b47286 --- /dev/null +++ b/packages/vault/lib/src/commands/set_command.dart @@ -0,0 +1,52 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class SetCommand extends DewCommand with DewToolCommand { + SetCommand() { + argParser + ..addOption( + 'name', + abbr: 'n', + mandatory: true, + help: 'Secret name.', + ) + ..addOption('env', help: 'Use value from an environment variable.') + ..addOption('file', help: 'Use value from file path.') + ..addOption('metadata', help: 'JSON object to save as metadata.') + ..addOption('metadata-file', help: 'Path to JSON metadata file.') + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'set'; + + @override + final String description = 'Set a secret value and optional metadata.'; + + @override + final String toolName = 'vault_set_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final secretName = requireStringArg(args, 'name'); + final source = + args['env'] ?? args['file'] ?? args['metadata'] ?? args['metadata-file']; + return renderVaultOutput( + format: format, + message: 'Set stub executed.', + json: { + 'secret': secretName, + 'source': source == null ? 'interactive' : source.toString(), + 'status': 'set', + }, + ); + } + +} diff --git a/packages/vault/lib/src/commands/update_command.dart b/packages/vault/lib/src/commands/update_command.dart new file mode 100644 index 0000000..aee2fd2 --- /dev/null +++ b/packages/vault/lib/src/commands/update_command.dart @@ -0,0 +1,50 @@ +import 'package:dew_core/dew_core.dart'; + +import '../command_output.dart'; + +class UpdateCommand extends DewCommand with DewToolCommand { + UpdateCommand() { + argParser + ..addOption( + 'name', + abbr: 'n', + mandatory: true, + help: 'Secret name.', + ) + ..addOption('env', help: 'Use value from an environment variable.') + ..addOption('file', help: 'Use value from file path.') + ..addOption('metadata', help: 'JSON object to save as metadata.') + ..addOption('metadata-file', help: 'Path to JSON metadata file.') + ..addOption( + 'format', + defaultsTo: 'default', + allowed: ['default', 'json'], + help: 'Output format for this command.', + ); + } + + @override + final String name = 'update'; + + @override + final String description = 'Update a secret value and/or metadata.'; + + @override + final String toolName = 'vault_update_secret'; + + @override + Future callAsTool(Map args) async { + final format = formatFromArgs(args); + final secretName = requireStringArg(args, 'name'); + final mode = args['env'] != null || args['file'] != null ? 'value-update' : 'metadata-only'; + return renderVaultOutput( + format: format, + message: 'Update stub executed.', + json: { + 'secret': secretName, + 'mode': mode, + }, + ); + } + +} diff --git a/packages/vault/lib/src/dew_vault_base.dart b/packages/vault/lib/src/dew_vault_base.dart new file mode 100644 index 0000000..99a30b9 --- /dev/null +++ b/packages/vault/lib/src/dew_vault_base.dart @@ -0,0 +1,35 @@ +import 'package:dew_core/dew_core.dart'; + +import 'commands/delete_command.dart'; +import 'commands/generate_command.dart'; +import 'commands/get_command.dart'; +import 'commands/init_command.dart'; +import 'commands/list_command.dart'; +import 'commands/rename_command.dart'; +import 'commands/rotate_command.dart'; +import 'commands/set_command.dart'; +import 'commands/update_command.dart'; + +/// Top-level CLI command for all Vault operations. +class VaultCommand extends DewCommand { + VaultCommand() { + addSubcommand(VaultInitCommand()); + addSubcommand(ListCommand()); + addSubcommand(SetCommand()); + addSubcommand(GetCommand()); + addSubcommand(UpdateCommand()); + addSubcommand(RenameCommand()); + addSubcommand(RotateCommand()); + addSubcommand(GenerateCommand()); + addSubcommand(DeleteCommand()); + } + + @override + final String name = 'vault'; + + @override + final String description = 'Manage project secrets with a local vault.'; + + @override + Future run() async => printUsage(); +} diff --git a/packages/vault/lib/src/vault_init_hook.dart b/packages/vault/lib/src/vault_init_hook.dart new file mode 100644 index 0000000..902803f --- /dev/null +++ b/packages/vault/lib/src/vault_init_hook.dart @@ -0,0 +1,39 @@ +import 'package:dew_core/dew_core.dart'; +import 'package:file/file.dart'; +import 'package:file/local.dart'; +import 'package:path/path.dart' as p; + +class VaultInitHook implements DewInitHook { + final FileSystem _fs; + + VaultInitHook({FileSystem fs = const LocalFileSystem()}) : _fs = fs; + + @override + Future onInit( + String projectRoot, + DewConfig config, + DewInitOptions options, + ) async { + final vaultDir = p.join(projectRoot, '.project', 'vault'); + final secretDir = p.join(projectRoot, '.project', 'secrets'); + + await _createDir(vaultDir, options.gitkeep); + await _createDir(secretDir, options.gitkeep); + } + + Future _createDir(String path, bool withGitkeep) async { + final dir = _fs.directory(path); + final existed = await dir.exists(); + await dir.create(recursive: true); + final relPath = p.join('.project', p.basename(path)); + if (existed) { + print(' found $relPath/'); + } else { + print(' created $relPath/'); + if (withGitkeep) { + await _fs.file(p.join(path, '.gitkeep')).writeAsString(''); + print(' created $relPath/.gitkeep'); + } + } + } +} diff --git a/packages/vault/pubspec.yaml b/packages/vault/pubspec.yaml new file mode 100644 index 0000000..df33369 --- /dev/null +++ b/packages/vault/pubspec.yaml @@ -0,0 +1,18 @@ +name: dew_vault +description: Vault feature package for the Dew project management tool. +version: 0.2.0 +repository: https://github.com/artificerchris/dew +issue_tracker: https://github.com/artificerchris/dew/issues +resolution: workspace + +environment: + sdk: ^3.11.4 + +dependencies: + dew_core: ^0.2.0 + file: ^7.0.1 + path: ^1.9.0 + +dev_dependencies: + lints: ^6.0.0 + test: ^1.25.6 diff --git a/pubspec.yaml b/pubspec.yaml index 74409a0..30260b0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,12 +6,12 @@ publish_to: none environment: sdk: ^3.11.4 - workspace: - packages/cli - packages/core - packages/kanban - packages/mcp + - packages/vault dev_dependencies: file: ^7.0.1