Raise SDK floor and resolve vault paths from config
This commit is contained in:
parent
0cd08e78d3
commit
f7346b1afe
22 changed files with 184 additions and 95 deletions
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
dart 3.12.0
|
||||||
|
|
@ -4,7 +4,7 @@ Thank you for your interest in contributing! This guide covers everything you ne
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **Dart SDK ^3.11.4** — verify with `dart --version`
|
- **Dart SDK ^3.12.0** — verify with `dart --version`
|
||||||
- **Melos** (optional, for workspace scripts) — `dart pub global activate melos`
|
- **Melos** (optional, for workspace scripts) — `dart pub global activate melos`
|
||||||
|
|
||||||
## Clone & setup
|
## Clone & setup
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ The TUI auto-refreshes when ticket files change on disk, so it stays in sync whe
|
||||||
dart pub global activate dew
|
dart pub global activate dew
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires Dart SDK ^3.11.4.
|
Requires Dart SDK ^3.12.0.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,9 @@ your-project/
|
||||||
└── dew.yaml
|
└── dew.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Path-like values in `dew.yaml` are resolved relative to `.project/dew.yaml`
|
||||||
|
unless they are absolute (for example, paths under `dew.vault`).
|
||||||
|
|
||||||
## Full Schema
|
## Full Schema
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ issue_tracker: https://github.com/artificerchris/dew/issues
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.7.0
|
args: ^2.7.0
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
@ -34,16 +36,42 @@ class ProjectContext {
|
||||||
final String root;
|
final String root;
|
||||||
final DewConfig config;
|
final DewConfig config;
|
||||||
final FileSystem fs;
|
final FileSystem fs;
|
||||||
|
final String configFilePath;
|
||||||
|
|
||||||
const ProjectContext({
|
const ProjectContext({
|
||||||
required this.root,
|
required this.root,
|
||||||
required this.config,
|
required this.config,
|
||||||
required this.fs,
|
required this.fs,
|
||||||
|
required this.configFilePath,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Typed path helpers for this project's well-known directories.
|
/// Typed path helpers for this project's well-known directories.
|
||||||
ProjectDirs get dirs => ProjectDirs(root);
|
ProjectDirs get dirs => ProjectDirs(root);
|
||||||
|
|
||||||
|
/// Path to `.project/dew.yaml` used to bootstrap this context.
|
||||||
|
String get configPath => configFilePath;
|
||||||
|
|
||||||
|
/// Resolves configuration values that are file paths relative to this project's
|
||||||
|
/// `.project/dew.yaml` location.
|
||||||
|
String resolveConfigPath(String value) {
|
||||||
|
final expanded = _expandTilde(value);
|
||||||
|
if (p.isAbsolute(expanded)) return p.normalize(expanded);
|
||||||
|
final segments = p.split(p.normalize(expanded));
|
||||||
|
if (segments.isNotEmpty && segments.first == '.project') {
|
||||||
|
return p.normalize(
|
||||||
|
p.joinAll(
|
||||||
|
[
|
||||||
|
p.dirname(configPath),
|
||||||
|
'..',
|
||||||
|
'.project',
|
||||||
|
...segments.skip(1),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return p.normalize(p.join(p.dirname(configPath), expanded));
|
||||||
|
}
|
||||||
|
|
||||||
/// Walks up from [from] (defaults to [fs.currentDirectory]) until a
|
/// Walks up from [from] (defaults to [fs.currentDirectory]) until a
|
||||||
/// `.project/dew.yaml` is found.
|
/// `.project/dew.yaml` is found.
|
||||||
static Future<ProjectContext> find({
|
static Future<ProjectContext> find({
|
||||||
|
|
@ -54,11 +82,13 @@ class ProjectContext {
|
||||||
while (true) {
|
while (true) {
|
||||||
final configFile = fs.file(p.join(dir.path, '.project', 'dew.yaml'));
|
final configFile = fs.file(p.join(dir.path, '.project', 'dew.yaml'));
|
||||||
if (await configFile.exists()) {
|
if (await configFile.exists()) {
|
||||||
|
final path = configFile.path;
|
||||||
final yaml = loadYaml(await configFile.readAsString()) as YamlMap;
|
final yaml = loadYaml(await configFile.readAsString()) as YamlMap;
|
||||||
return ProjectContext(
|
return ProjectContext(
|
||||||
root: dir.path,
|
root: dir.path,
|
||||||
config: DewConfig.fromYaml(yaml),
|
config: DewConfig.fromYaml(yaml),
|
||||||
fs: fs,
|
fs: fs,
|
||||||
|
configFilePath: path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final parent = dir.parent;
|
final parent = dir.parent;
|
||||||
|
|
@ -72,3 +102,15 @@ class ProjectContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _expandTilde(String input) {
|
||||||
|
if (!input.startsWith('~')) return input;
|
||||||
|
final home = Platform.environment['HOME'] ??
|
||||||
|
Platform.environment['USERPROFILE'] ??
|
||||||
|
'';
|
||||||
|
if (home.isEmpty) return input;
|
||||||
|
if (input.length == 1) return home;
|
||||||
|
final rest = input.substring(1);
|
||||||
|
if (rest.startsWith('/')) return p.join(home, rest.substring(1));
|
||||||
|
return p.join(home, rest);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ issue_tracker: https://github.com/artificerchris/dew/issues
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -72,5 +72,25 @@ dew:
|
||||||
final ctx = await ProjectContext.find(fs: fs, from: fs.directory('/sub'));
|
final ctx = await ProjectContext.find(fs: fs, from: fs.directory('/sub'));
|
||||||
expect(ctx.root, '/');
|
expect(ctx.root, '/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolveConfigPath resolves paths relative to .project/dew.yaml', () async {
|
||||||
|
final fs = MemoryFileSystem();
|
||||||
|
fs.directory('/foo/.project').createSync(recursive: true);
|
||||||
|
fs.file('/foo/.project/dew.yaml').writeAsStringSync(configYaml);
|
||||||
|
|
||||||
|
final ctx = await ProjectContext.find(fs: fs, from: fs.directory('/foo/.project/child'));
|
||||||
|
expect(
|
||||||
|
ctx.resolveConfigPath('vault'),
|
||||||
|
'/foo/.project/vault',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
ctx.resolveConfigPath('.project/vault'),
|
||||||
|
'/foo/.project/vault',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
ctx.resolveConfigPath('/tmp/abs'),
|
||||||
|
'/tmp/abs',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ issue_tracker: https://github.com/artificerchris/dew/issues
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ issue_tracker: https://github.com/artificerchris/dew/issues
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
|
|
||||||
# Add regular dependencies here.
|
# Add regular dependencies here.
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ class DeleteCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
await store.delete(secretName);
|
await store.delete(secretName);
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ class GetCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class VaultInitCommand extends DewCommand with DewToolCommand {
|
||||||
|
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, storageDir),
|
storageDir: context.resolveConfigPath(storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, passwordFile),
|
passwordFilePath: context.resolveConfigPath(passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
await store.initialize();
|
await store.initialize();
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ class ListCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
final names = await store.listSecretNames();
|
final names = await store.listSecretNames();
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class RenameCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
await store.rename(from, to);
|
await store.rename(from, to);
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@ class RotateCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,8 +46,8 @@ class SetCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ class UpdateCommand extends DewCommand with DewToolCommand {
|
||||||
final context = await ProjectContext.find(fs: _fs);
|
final context = await ProjectContext.find(fs: _fs);
|
||||||
final config = context.config.vault;
|
final config = context.config.vault;
|
||||||
final store = VaultStore(
|
final store = VaultStore(
|
||||||
storageDir: resolveProjectPath(context.root, config.storageDir),
|
storageDir: context.resolveConfigPath(config.storageDir),
|
||||||
passwordFilePath: resolveProjectPath(context.root, config.passwordFile),
|
passwordFilePath: context.resolveConfigPath(config.passwordFile),
|
||||||
fs: context.fs,
|
fs: context.fs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ issue_tracker: https://github.com/artificerchris/dew/issues
|
||||||
resolution: workspace
|
resolution: workspace
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
dew_core: ^0.1.0
|
dew_core: ^0.1.0
|
||||||
|
|
|
||||||
|
|
@ -48,19 +48,17 @@ void main() {
|
||||||
final tools = registry.mcpTools.map((t) => t.name).toSet();
|
final tools = registry.mcpTools.map((t) => t.name).toSet();
|
||||||
expect(
|
expect(
|
||||||
tools,
|
tools,
|
||||||
containsAll(
|
containsAll({
|
||||||
{
|
'vault_init',
|
||||||
'vault_init',
|
'vault_set_secret',
|
||||||
'vault_set_secret',
|
'vault_get_secret',
|
||||||
'vault_get_secret',
|
'vault_update_secret',
|
||||||
'vault_update_secret',
|
'vault_rename_secret',
|
||||||
'vault_rename_secret',
|
'vault_rotate_secret',
|
||||||
'vault_rotate_secret',
|
'vault_generate_secret',
|
||||||
'vault_generate_secret',
|
'vault_list_secrets',
|
||||||
'vault_list_secrets',
|
'vault_delete_secret',
|
||||||
'vault_delete_secret',
|
}),
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -151,11 +149,17 @@ void main() {
|
||||||
);
|
);
|
||||||
expect(initResult['message'], 'Vault initialised.');
|
expect(initResult['message'], 'Vault initialised.');
|
||||||
expect(initResult['initialized'], isTrue);
|
expect(initResult['initialized'], isTrue);
|
||||||
expect(initResult['password_file'], '/.project/secrets/dew.vault.password');
|
expect(
|
||||||
|
initResult['password_file'],
|
||||||
|
'/.project/secrets/dew.vault.password',
|
||||||
|
);
|
||||||
expect(initResult['storage_dir'], '/.project/vault');
|
expect(initResult['storage_dir'], '/.project/vault');
|
||||||
|
|
||||||
expect(await fs.directory('/.project/vault').exists(), isTrue);
|
expect(await fs.directory('/.project/vault').exists(), isTrue);
|
||||||
expect(await fs.file('/.project/secrets/dew.vault.password').exists(), isTrue);
|
expect(
|
||||||
|
await fs.file('/.project/secrets/dew.vault.password').exists(),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('init command accepts custom password and storage paths', () async {
|
test('init command accepts custom password and storage paths', () async {
|
||||||
|
|
@ -168,20 +172,27 @@ void main() {
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
customInit['password_file'],
|
customInit['password_file'],
|
||||||
'/.custom/secrets/custom.vault.password',
|
'/.project/.custom/secrets/custom.vault.password',
|
||||||
|
);
|
||||||
|
expect(customInit['storage_dir'], '/.project/.custom/vault-store');
|
||||||
|
expect(
|
||||||
|
await fs.directory('/.project/.custom/vault-store').exists(),
|
||||||
|
isTrue,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
customInit['storage_dir'],
|
await fs
|
||||||
'/.custom/vault-store',
|
.file('/.project/.custom/secrets/custom.vault.password')
|
||||||
|
.exists(),
|
||||||
|
isTrue,
|
||||||
);
|
);
|
||||||
expect(await fs.directory('/.custom/vault-store').exists(), isTrue);
|
|
||||||
expect(await fs.file('/.custom/secrets/custom.vault.password').exists(), isTrue);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('set command accepts metadata from file', () async {
|
test('set command accepts metadata from file', () async {
|
||||||
fs.file('/meta.json').writeAsStringSync(
|
fs
|
||||||
'{"rotation":{"generator":"postgres_password","length":16},"notes":"from-file"}',
|
.file('/meta.json')
|
||||||
);
|
.writeAsStringSync(
|
||||||
|
'{"rotation":{"generator":"postgres_password","length":16},"notes":"from-file"}',
|
||||||
|
);
|
||||||
await tools['vault_set_secret']!.handler({
|
await tools['vault_set_secret']!.handler({
|
||||||
'name': 'META_FILE_SECRET',
|
'name': 'META_FILE_SECRET',
|
||||||
'file': '/seed.txt',
|
'file': '/seed.txt',
|
||||||
|
|
@ -242,9 +253,11 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('update command accepts metadata file', () async {
|
test('update command accepts metadata file', () async {
|
||||||
fs.file('/meta-update.json').writeAsStringSync(
|
fs
|
||||||
'{"rotation":{"generator":"postgres_password","length":24},"notes":"file-metadata"}',
|
.file('/meta-update.json')
|
||||||
);
|
.writeAsStringSync(
|
||||||
|
'{"rotation":{"generator":"postgres_password","length":24},"notes":"file-metadata"}',
|
||||||
|
);
|
||||||
await tools['vault_set_secret']!.handler({
|
await tools['vault_set_secret']!.handler({
|
||||||
'name': 'META_UPDATE_SECRET',
|
'name': 'META_UPDATE_SECRET',
|
||||||
'file': '/seed.txt',
|
'file': '/seed.txt',
|
||||||
|
|
@ -289,7 +302,10 @@ void main() {
|
||||||
expect(listResult['secrets'], isNot(contains('LEGACY_KEY')));
|
expect(listResult['secrets'], isNot(contains('LEGACY_KEY')));
|
||||||
|
|
||||||
await expectLater(
|
await expectLater(
|
||||||
tools['vault_get_secret']!.handler({'name': 'LEGACY_KEY', 'format': 'json'}),
|
tools['vault_get_secret']!.handler({
|
||||||
|
'name': 'LEGACY_KEY',
|
||||||
|
'format': 'json',
|
||||||
|
}),
|
||||||
throwsA(isA<ArgumentError>()),
|
throwsA(isA<ArgumentError>()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -320,51 +336,54 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('rotate command uses rotation metadata and can rotate vault password', () async {
|
test(
|
||||||
await tools['vault_set_secret']!.handler({
|
'rotate command uses rotation metadata and can rotate vault password',
|
||||||
'name': 'service_db_password',
|
() async {
|
||||||
'file': '/seed.txt',
|
await tools['vault_set_secret']!.handler({
|
||||||
'metadata':
|
|
||||||
'{"rotation":{"generator":"postgres_password","length":12}}',
|
|
||||||
});
|
|
||||||
await tools['vault_set_secret']!.handler({
|
|
||||||
'name': 'service_api_key',
|
|
||||||
'file': '/new-seed.txt',
|
|
||||||
'metadata':
|
|
||||||
'{"rotation":{"generator":"postgres_password","length":12}}',
|
|
||||||
});
|
|
||||||
|
|
||||||
final before = _decodeToolJson(
|
|
||||||
await tools['vault_get_secret']!.handler({
|
|
||||||
'name': 'service_db_password',
|
'name': 'service_db_password',
|
||||||
'format': 'json',
|
'file': '/seed.txt',
|
||||||
}),
|
'metadata':
|
||||||
);
|
'{"rotation":{"generator":"postgres_password","length":12}}',
|
||||||
expect(before['value'], 'super-secret');
|
});
|
||||||
|
await tools['vault_set_secret']!.handler({
|
||||||
|
'name': 'service_api_key',
|
||||||
|
'file': '/new-seed.txt',
|
||||||
|
'metadata':
|
||||||
|
'{"rotation":{"generator":"postgres_password","length":12}}',
|
||||||
|
});
|
||||||
|
|
||||||
final secretRotated = _decodeToolJson(
|
final before = _decodeToolJson(
|
||||||
await tools['vault_rotate_secret']!.handler({
|
await tools['vault_get_secret']!.handler({
|
||||||
'name': 'service_db_password',
|
'name': 'service_db_password',
|
||||||
'format': 'json',
|
'format': 'json',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(secretRotated['scope'], 'secret');
|
expect(before['value'], 'super-secret');
|
||||||
expect(secretRotated['name'], 'service_db_password');
|
|
||||||
|
|
||||||
final after = _decodeToolJson(
|
final secretRotated = _decodeToolJson(
|
||||||
await tools['vault_get_secret']!.handler({
|
await tools['vault_rotate_secret']!.handler({
|
||||||
'name': 'service_db_password',
|
'name': 'service_db_password',
|
||||||
'format': 'json',
|
'format': 'json',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(after['value'], isNot('super-secret'));
|
expect(secretRotated['scope'], 'secret');
|
||||||
|
expect(secretRotated['name'], 'service_db_password');
|
||||||
|
|
||||||
final allRotated = _decodeToolJson(
|
final after = _decodeToolJson(
|
||||||
await tools['vault_rotate_secret']!.handler({'format': 'json'}),
|
await tools['vault_get_secret']!.handler({
|
||||||
);
|
'name': 'service_db_password',
|
||||||
expect(allRotated['scope'], 'vault');
|
'format': 'json',
|
||||||
expect(allRotated['rotated_count'], 2);
|
}),
|
||||||
});
|
);
|
||||||
|
expect(after['value'], isNot('super-secret'));
|
||||||
|
|
||||||
|
final allRotated = _decodeToolJson(
|
||||||
|
await tools['vault_rotate_secret']!.handler({'format': 'json'}),
|
||||||
|
);
|
||||||
|
expect(allRotated['scope'], 'vault');
|
||||||
|
expect(allRotated['rotated_count'], 2);
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group('vault crypto helpers', () {
|
group('vault crypto helpers', () {
|
||||||
|
|
@ -373,10 +392,14 @@ void main() {
|
||||||
const value = 'very-sensitive';
|
const value = 'very-sensitive';
|
||||||
final encoded = VaultCrypto.encryptToEnvelope(value, password: password);
|
final encoded = VaultCrypto.encryptToEnvelope(value, password: password);
|
||||||
expect(encoded['version'], 1);
|
expect(encoded['version'], 1);
|
||||||
final decoded = VaultCrypto.decryptFromEnvelope(encoded, password: password);
|
final decoded = VaultCrypto.decryptFromEnvelope(
|
||||||
|
encoded,
|
||||||
|
password: password,
|
||||||
|
);
|
||||||
expect(decoded, value);
|
expect(decoded, value);
|
||||||
expect(
|
expect(
|
||||||
() => VaultCrypto.decryptFromEnvelope(encoded, password: 'bad-password'),
|
() =>
|
||||||
|
VaultCrypto.decryptFromEnvelope(encoded, password: 'bad-password'),
|
||||||
throwsA(isA<ArgumentError>()),
|
throwsA(isA<ArgumentError>()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -618,4 +618,4 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.2.4"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.11.4 <4.0.0"
|
dart: ">=3.12.0 <4.0.0"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ repository: https://github.com/artificerchris/dew
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.12.0
|
||||||
workspace:
|
workspace:
|
||||||
- packages/cli
|
- packages/cli
|
||||||
- packages/core
|
- packages/core
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue