@celilo/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1566 -0
- package/bin/celilo +16 -0
- package/drizzle/0000_complex_puma.sql +179 -0
- package/drizzle/0001_dizzy_wolfpack.sql +2 -0
- package/drizzle/0002_web_routes.sql +16 -0
- package/drizzle/0003_backup_storage.sql +32 -0
- package/drizzle/meta/0000_snapshot.json +1151 -0
- package/drizzle/meta/0001_snapshot.json +1167 -0
- package/drizzle/meta/0002_snapshot.json +1257 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +64 -0
- package/schemas/system_config.json +106 -0
- package/src/__integration__/container-services-cli.integration.test.ts +246 -0
- package/src/ansible/dependencies.test.ts +309 -0
- package/src/ansible/dependencies.ts +896 -0
- package/src/ansible/inventory.test.ts +463 -0
- package/src/ansible/inventory.ts +445 -0
- package/src/ansible/secrets.ts +222 -0
- package/src/ansible/validation.test.ts +92 -0
- package/src/ansible/validation.ts +272 -0
- package/src/api-clients/digitalocean.ts +94 -0
- package/src/api-clients/proxmox.ts +655 -0
- package/src/capabilities/logging-wrapper.test.ts +217 -0
- package/src/capabilities/lookup.test.ts +149 -0
- package/src/capabilities/lookup.ts +89 -0
- package/src/capabilities/public-web-helpers.test.ts +198 -0
- package/src/capabilities/public-web-publish.test.ts +458 -0
- package/src/capabilities/registration.test.ts +395 -0
- package/src/capabilities/registration.ts +200 -0
- package/src/capabilities/route-validation.test.ts +121 -0
- package/src/capabilities/route-validation.ts +96 -0
- package/src/capabilities/secret-ref.test.ts +313 -0
- package/src/capabilities/secret-validation.ts +157 -0
- package/src/capabilities/secrets.test.ts +750 -0
- package/src/capabilities/secrets.ts +244 -0
- package/src/capabilities/validation.test.ts +613 -0
- package/src/capabilities/validation.ts +160 -0
- package/src/capabilities/well-known.test.ts +238 -0
- package/src/capabilities/well-known.ts +222 -0
- package/src/cli/cli.test.ts +654 -0
- package/src/cli/command-registry.ts +742 -0
- package/src/cli/command-tree-parser.test.ts +180 -0
- package/src/cli/command-tree-parser.ts +193 -0
- package/src/cli/commands/backup-create.ts +137 -0
- package/src/cli/commands/backup-delete.ts +74 -0
- package/src/cli/commands/backup-import.ts +97 -0
- package/src/cli/commands/backup-list.ts +132 -0
- package/src/cli/commands/backup-name.ts +73 -0
- package/src/cli/commands/backup-prune.ts +98 -0
- package/src/cli/commands/backup-restore.ts +122 -0
- package/src/cli/commands/capability-info.ts +121 -0
- package/src/cli/commands/capability-list.ts +47 -0
- package/src/cli/commands/completion.ts +87 -0
- package/src/cli/commands/hook-run.ts +176 -0
- package/src/cli/commands/ipam.ts +607 -0
- package/src/cli/commands/machine-add.ts +235 -0
- package/src/cli/commands/machine-earmark.ts +82 -0
- package/src/cli/commands/machine-list.ts +77 -0
- package/src/cli/commands/machine-remove.ts +90 -0
- package/src/cli/commands/machine-status.ts +131 -0
- package/src/cli/commands/module-audit.ts +51 -0
- package/src/cli/commands/module-build.ts +60 -0
- package/src/cli/commands/module-config.ts +170 -0
- package/src/cli/commands/module-deploy.ts +71 -0
- package/src/cli/commands/module-generate.ts +236 -0
- package/src/cli/commands/module-health.ts +108 -0
- package/src/cli/commands/module-import.ts +80 -0
- package/src/cli/commands/module-list.ts +43 -0
- package/src/cli/commands/module-logs.ts +73 -0
- package/src/cli/commands/module-remove.ts +162 -0
- package/src/cli/commands/module-show.ts +208 -0
- package/src/cli/commands/module-status.ts +131 -0
- package/src/cli/commands/module-types.ts +189 -0
- package/src/cli/commands/module-upgrade.ts +192 -0
- package/src/cli/commands/package.ts +68 -0
- package/src/cli/commands/secret-list.ts +99 -0
- package/src/cli/commands/secret-set.ts +134 -0
- package/src/cli/commands/service-add-digitalocean.ts +133 -0
- package/src/cli/commands/service-add-proxmox.ts +342 -0
- package/src/cli/commands/service-config-get.ts +83 -0
- package/src/cli/commands/service-config-set.ts +145 -0
- package/src/cli/commands/service-list.ts +74 -0
- package/src/cli/commands/service-reconfigure.ts +230 -0
- package/src/cli/commands/service-remove.ts +103 -0
- package/src/cli/commands/service-verify.ts +240 -0
- package/src/cli/commands/status.ts +216 -0
- package/src/cli/commands/storage-add-local.ts +106 -0
- package/src/cli/commands/storage-add-s3.ts +114 -0
- package/src/cli/commands/storage-list.ts +72 -0
- package/src/cli/commands/storage-remove.ts +54 -0
- package/src/cli/commands/storage-set-default.ts +44 -0
- package/src/cli/commands/storage-verify.ts +54 -0
- package/src/cli/commands/system-config.ts +168 -0
- package/src/cli/commands/system-init.ts +314 -0
- package/src/cli/commands/system-secret-get.ts +98 -0
- package/src/cli/commands/system-secret-set.ts +76 -0
- package/src/cli/commands/system-vault-password.ts +34 -0
- package/src/cli/completion.test.ts +37 -0
- package/src/cli/completion.ts +482 -0
- package/src/cli/fuel-gauge.test.ts +208 -0
- package/src/cli/fuel-gauge.ts +405 -0
- package/src/cli/generate-zsh-completion.test.ts +95 -0
- package/src/cli/generate-zsh-completion.ts +497 -0
- package/src/cli/index.ts +1583 -0
- package/src/cli/interactive-config.test.ts +201 -0
- package/src/cli/interactive-config.ts +62 -0
- package/src/cli/parser.test.ts +227 -0
- package/src/cli/parser.ts +244 -0
- package/src/cli/prompts.test.ts +33 -0
- package/src/cli/prompts.ts +121 -0
- package/src/cli/types.ts +38 -0
- package/src/cli/validators.test.ts +235 -0
- package/src/cli/validators.ts +188 -0
- package/src/config/env.ts +41 -0
- package/src/config/paths.test.ts +172 -0
- package/src/config/paths.ts +108 -0
- package/src/db/client.ts +190 -0
- package/src/db/migrate.ts +30 -0
- package/src/db/schema.test.ts +221 -0
- package/src/db/schema.ts +434 -0
- package/src/hooks/capability-loader-firewall.test.ts +246 -0
- package/src/hooks/capability-loader.test.ts +100 -0
- package/src/hooks/capability-loader.ts +520 -0
- package/src/hooks/define-hook.test.ts +488 -0
- package/src/hooks/executor.test.ts +462 -0
- package/src/hooks/executor.ts +469 -0
- package/src/hooks/logger.test.ts +54 -0
- package/src/hooks/logger.ts +95 -0
- package/src/hooks/test-fixtures/failing-hook.ts +13 -0
- package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
- package/src/hooks/test-fixtures/success-hook.ts +20 -0
- package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
- package/src/hooks/test-fixtures/void-hook.ts +13 -0
- package/src/hooks/types.ts +89 -0
- package/src/infrastructure/property-extractor.test.ts +194 -0
- package/src/infrastructure/property-extractor.ts +151 -0
- package/src/ipam/allocator.test.ts +442 -0
- package/src/ipam/allocator.ts +369 -0
- package/src/ipam/auto-allocator.test.ts +247 -0
- package/src/ipam/auto-allocator.ts +270 -0
- package/src/ipam/subnet-parser.test.ts +107 -0
- package/src/ipam/subnet-parser.ts +136 -0
- package/src/manifest/contracts/index.ts +61 -0
- package/src/manifest/contracts/v1.ts +118 -0
- package/src/manifest/json-schema-roundtrip.test.ts +99 -0
- package/src/manifest/schema.ts +367 -0
- package/src/manifest/template-validator.test.ts +231 -0
- package/src/manifest/template-validator.ts +322 -0
- package/src/manifest/validate.test.ts +1180 -0
- package/src/manifest/validate.ts +415 -0
- package/src/module/import.test.ts +355 -0
- package/src/module/import.ts +676 -0
- package/src/module/packaging/audit.ts +169 -0
- package/src/module/packaging/build.ts +228 -0
- package/src/module/packaging/checksum.ts +41 -0
- package/src/module/packaging/extract.ts +234 -0
- package/src/module/packaging/signature.ts +47 -0
- package/src/secrets/encryption.test.ts +284 -0
- package/src/secrets/encryption.ts +162 -0
- package/src/secrets/generators.test.ts +112 -0
- package/src/secrets/generators.ts +127 -0
- package/src/secrets/master-key.test.ts +159 -0
- package/src/secrets/master-key.ts +114 -0
- package/src/secrets/storage.test.ts +115 -0
- package/src/secrets/storage.ts +106 -0
- package/src/secrets/vault.test.ts +35 -0
- package/src/secrets/vault.ts +42 -0
- package/src/services/backup-create.ts +532 -0
- package/src/services/backup-metadata.ts +198 -0
- package/src/services/backup-restore.ts +229 -0
- package/src/services/backup-retention.ts +84 -0
- package/src/services/backup-storage.ts +281 -0
- package/src/services/build-stream.test.ts +122 -0
- package/src/services/build-stream.ts +201 -0
- package/src/services/config-interview.ts +694 -0
- package/src/services/container-service.test.ts +298 -0
- package/src/services/container-service.ts +401 -0
- package/src/services/cross-module-data-manager.test.ts +405 -0
- package/src/services/cross-module-data-manager.ts +412 -0
- package/src/services/deploy-ansible.ts +88 -0
- package/src/services/deploy-planner.ts +153 -0
- package/src/services/deploy-preflight.ts +274 -0
- package/src/services/deploy-ssh.ts +131 -0
- package/src/services/deploy-terraform.test.ts +55 -0
- package/src/services/deploy-terraform.ts +445 -0
- package/src/services/deploy-validation.ts +311 -0
- package/src/services/dns-auto-register.ts +211 -0
- package/src/services/health-runner.ts +184 -0
- package/src/services/infrastructure-selector.test.ts +485 -0
- package/src/services/infrastructure-selector.ts +245 -0
- package/src/services/infrastructure-variable-resolver.test.ts +751 -0
- package/src/services/infrastructure-variable-resolver.ts +234 -0
- package/src/services/machine-detector.ts +328 -0
- package/src/services/machine-pool.test.ts +405 -0
- package/src/services/machine-pool.ts +316 -0
- package/src/services/manifest-validation.ts +120 -0
- package/src/services/module-build.test.ts +290 -0
- package/src/services/module-build.ts +431 -0
- package/src/services/module-config.test.ts +237 -0
- package/src/services/module-config.ts +298 -0
- package/src/services/module-deploy.ts +862 -0
- package/src/services/module-types-drift.test.ts +73 -0
- package/src/services/module-types-generator.test.ts +288 -0
- package/src/services/module-types-generator.ts +189 -0
- package/src/services/proxmox-state-recovery.ts +140 -0
- package/src/services/schema-validation.ts +155 -0
- package/src/services/secret-schema-loader.test.ts +311 -0
- package/src/services/secret-schema-loader.ts +239 -0
- package/src/services/ssh-key-manager.test.ts +283 -0
- package/src/services/ssh-key-manager.ts +193 -0
- package/src/services/storage-providers/local.ts +105 -0
- package/src/services/storage-providers/s3.ts +182 -0
- package/src/services/storage-providers/types.ts +24 -0
- package/src/services/system-config-schema-types.ts +25 -0
- package/src/services/system-config-validator.test.ts +160 -0
- package/src/services/system-config-validator.ts +74 -0
- package/src/services/system-init.test.ts +153 -0
- package/src/services/system-init.ts +253 -0
- package/src/services/terraform-safety.ts +174 -0
- package/src/services/zone-detector.test.ts +110 -0
- package/src/services/zone-detector.ts +102 -0
- package/src/services/zone-policy.test.ts +97 -0
- package/src/services/zone-policy.ts +126 -0
- package/src/templates/generator.test.ts +645 -0
- package/src/templates/generator.ts +1119 -0
- package/src/templates/types.ts +62 -0
- package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
- package/src/test-utils/cli-context-interactive.test.ts +152 -0
- package/src/test-utils/cli-context-server.test.ts +66 -0
- package/src/test-utils/cli-context.test.ts +273 -0
- package/src/test-utils/cli-context.ts +677 -0
- package/src/test-utils/cli-result.test.ts +282 -0
- package/src/test-utils/cli-result.ts +241 -0
- package/src/test-utils/cli.ts +55 -0
- package/src/test-utils/completion-harness.test.ts +126 -0
- package/src/test-utils/completion-harness.ts +82 -0
- package/src/test-utils/database.test.ts +182 -0
- package/src/test-utils/database.ts +126 -0
- package/src/test-utils/filesystem.test.ts +208 -0
- package/src/test-utils/filesystem.ts +142 -0
- package/src/test-utils/fixtures.test.ts +123 -0
- package/src/test-utils/fixtures.ts +160 -0
- package/src/test-utils/golden-diff.ts +197 -0
- package/src/test-utils/index.ts +77 -0
- package/src/test-utils/integration.ts +81 -0
- package/src/test-utils/module-fixtures.ts +468 -0
- package/src/test-utils/modules.test.ts +144 -0
- package/src/test-utils/modules.ts +183 -0
- package/src/test-utils/setup-test-db.ts +90 -0
- package/src/test-utils/value-extractor.test.ts +231 -0
- package/src/test-utils/value-extractor.ts +228 -0
- package/src/types/infrastructure.ts +157 -0
- package/src/utils/shell.test.ts +365 -0
- package/src/utils/shell.ts +159 -0
- package/src/validation/schemas.ts +166 -0
- package/src/variables/ansible-resolver.test.ts +142 -0
- package/src/variables/ansible-resolver.ts +69 -0
- package/src/variables/capability-self-ref.test.ts +220 -0
- package/src/variables/context.test.ts +1265 -0
- package/src/variables/context.ts +624 -0
- package/src/variables/declarative-derivation.test.ts +743 -0
- package/src/variables/declarative-derivation.ts +200 -0
- package/src/variables/parser.test.ts +231 -0
- package/src/variables/parser.ts +76 -0
- package/src/variables/resolver.test.ts +458 -0
- package/src/variables/resolver.ts +282 -0
- package/src/variables/types.ts +59 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Test Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for creating and managing temporary directories and files
|
|
5
|
+
* for test isolation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { cp, mkdtemp, rm } from 'node:fs/promises';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create temporary directory for test
|
|
14
|
+
*
|
|
15
|
+
* Directory is created in system temp dir with unique name.
|
|
16
|
+
* Caller is responsible for cleanup via removeTempDirectory().
|
|
17
|
+
*
|
|
18
|
+
* @returns Path to temporary directory
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const tempDir = await createTempDirectory();
|
|
23
|
+
* // Use tempDir...
|
|
24
|
+
* await removeTempDirectory(tempDir);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function createTempDirectory(): Promise<string> {
|
|
28
|
+
return await mkdtemp(join(tmpdir(), 'celilo-test-'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Remove temporary directory (with retry for locked files)
|
|
33
|
+
*
|
|
34
|
+
* Handles file locking issues by retrying once after a short delay.
|
|
35
|
+
* Useful on Windows where files may be locked briefly.
|
|
36
|
+
*
|
|
37
|
+
* @param dir - Directory to remove
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const tempDir = await createTempDirectory();
|
|
42
|
+
* // Use tempDir...
|
|
43
|
+
* await removeTempDirectory(tempDir);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export async function removeTempDirectory(dir: string): Promise<void> {
|
|
47
|
+
try {
|
|
48
|
+
await rm(dir, { recursive: true, force: true });
|
|
49
|
+
} catch (_error) {
|
|
50
|
+
// Retry once after 100ms (helps with file locking issues)
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
52
|
+
await rm(dir, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Copy test fixture to temporary directory
|
|
58
|
+
*
|
|
59
|
+
* Recursively copies source to destination.
|
|
60
|
+
* Useful for setting up test data from fixtures.
|
|
61
|
+
*
|
|
62
|
+
* @param fixturePath - Source path (relative to cwd)
|
|
63
|
+
* @param destPath - Destination path
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* const tempDir = await createTempDirectory();
|
|
68
|
+
* await copyFixture('./test-fixtures/modules/artifact-test', tempDir);
|
|
69
|
+
* // tempDir now contains copy of artifact-test module
|
|
70
|
+
* await removeTempDirectory(tempDir);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export async function copyFixture(fixturePath: string, destPath: string): Promise<void> {
|
|
74
|
+
await cp(fixturePath, destPath, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create multiple temporary directories
|
|
79
|
+
*
|
|
80
|
+
* Useful for tests that need multiple isolated directories.
|
|
81
|
+
*
|
|
82
|
+
* @param count - Number of directories to create
|
|
83
|
+
* @returns Array of directory paths
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* const [dir1, dir2] = await createMultipleTempDirectories(2);
|
|
88
|
+
* // Use dir1 and dir2...
|
|
89
|
+
* await removeMultipleTempDirectories([dir1, dir2]);
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export async function createMultipleTempDirectories(count: number): Promise<string[]> {
|
|
93
|
+
const directories: string[] = [];
|
|
94
|
+
for (let i = 0; i < count; i++) {
|
|
95
|
+
directories.push(await createTempDirectory());
|
|
96
|
+
}
|
|
97
|
+
return directories;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Remove multiple temporary directories
|
|
102
|
+
*
|
|
103
|
+
* @param directories - Array of directory paths to remove
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```typescript
|
|
107
|
+
* const dirs = await createMultipleTempDirectories(3);
|
|
108
|
+
* // Use dirs...
|
|
109
|
+
* await removeMultipleTempDirectories(dirs);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export async function removeMultipleTempDirectories(directories: string[]): Promise<void> {
|
|
113
|
+
for (const dir of directories) {
|
|
114
|
+
await removeTempDirectory(dir);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Create temporary directory with cleanup function
|
|
120
|
+
*
|
|
121
|
+
* Returns both directory path and cleanup function.
|
|
122
|
+
* Convenient pattern for use in test setup/teardown.
|
|
123
|
+
*
|
|
124
|
+
* @returns Object with directory path and cleanup function
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const { path: tempDir, cleanup } = await createTempDirectoryWithCleanup();
|
|
129
|
+
* // Use tempDir...
|
|
130
|
+
* await cleanup();
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export async function createTempDirectoryWithCleanup(): Promise<{
|
|
134
|
+
path: string;
|
|
135
|
+
cleanup: () => Promise<void>;
|
|
136
|
+
}> {
|
|
137
|
+
const path = await createTempDirectory();
|
|
138
|
+
const cleanup = async () => {
|
|
139
|
+
await removeTempDirectory(path);
|
|
140
|
+
};
|
|
141
|
+
return { path, cleanup };
|
|
142
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
getFixturePath,
|
|
5
|
+
getModuleTestConfig,
|
|
6
|
+
getModuleTestSecrets,
|
|
7
|
+
getSystemTestConfig,
|
|
8
|
+
getSystemTestSecrets,
|
|
9
|
+
loadTestValues,
|
|
10
|
+
} from './fixtures';
|
|
11
|
+
|
|
12
|
+
describe('Fixture Test Utilities', () => {
|
|
13
|
+
describe('getFixturePath', () => {
|
|
14
|
+
test('returns absolute path to fixture', () => {
|
|
15
|
+
const path = getFixturePath('modules/artifact-test');
|
|
16
|
+
expect(path).toContain('test-fixtures/modules/artifact-test');
|
|
17
|
+
expect(path).toMatch(/^[/\\]/); // Starts with / or \ (absolute path)
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('handles nested paths', () => {
|
|
21
|
+
const path = getFixturePath('modules/artifact-test/manifest.yml');
|
|
22
|
+
expect(path).toContain('test-fixtures/modules/artifact-test/manifest.yml');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('returns path that exists', () => {
|
|
26
|
+
const path = getFixturePath('modules/artifact-test');
|
|
27
|
+
expect(existsSync(path)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('loadTestValues', () => {
|
|
32
|
+
test('loads test-values.yml', async () => {
|
|
33
|
+
const testValues = await loadTestValues();
|
|
34
|
+
expect(testValues).toBeDefined();
|
|
35
|
+
expect(testValues).toHaveProperty('system');
|
|
36
|
+
expect(testValues).toHaveProperty('modules');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('contains expected system configuration', async () => {
|
|
40
|
+
const testValues = await loadTestValues();
|
|
41
|
+
expect(testValues.system).toBeDefined();
|
|
42
|
+
expect(testValues.system?.dns).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('contains expected module data', async () => {
|
|
46
|
+
const testValues = await loadTestValues();
|
|
47
|
+
expect(testValues.modules).toBeDefined();
|
|
48
|
+
expect(testValues.modules?.homebridge).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('getModuleTestConfig', () => {
|
|
53
|
+
test('returns module configuration', async () => {
|
|
54
|
+
const config = await getModuleTestConfig('homebridge');
|
|
55
|
+
expect(config).toBeDefined();
|
|
56
|
+
expect(config.vmid).toBe(2110);
|
|
57
|
+
expect(config.hostname).toBe('iot');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('excludes secrets from config', async () => {
|
|
61
|
+
const config = await getModuleTestConfig('dns-external');
|
|
62
|
+
expect(config).toBeDefined();
|
|
63
|
+
expect(config.vps_ip).toBe('188.166.157.2');
|
|
64
|
+
expect(config.secrets).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('returns empty object for non-existent module', async () => {
|
|
68
|
+
const config = await getModuleTestConfig('non-existent-module');
|
|
69
|
+
expect(config).toEqual({});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('handles module with no secrets', async () => {
|
|
73
|
+
// Homebridge module has config but no secrets in test-values.yml
|
|
74
|
+
const config = await getModuleTestConfig('homebridge');
|
|
75
|
+
expect(config).toBeDefined();
|
|
76
|
+
expect(config.hostname).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getModuleTestSecrets', () => {
|
|
81
|
+
test('returns module secrets', async () => {
|
|
82
|
+
const secrets = await getModuleTestSecrets('dns-external');
|
|
83
|
+
expect(secrets).toBeDefined();
|
|
84
|
+
expect(secrets.knot_ddns_tsig_secret).toBeTruthy();
|
|
85
|
+
expect(typeof secrets.knot_ddns_tsig_secret).toBe('string');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('returns empty object for module with no secrets', async () => {
|
|
89
|
+
const secrets = await getModuleTestSecrets('test-module');
|
|
90
|
+
expect(secrets).toEqual({});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('returns empty object for non-existent module', async () => {
|
|
94
|
+
const secrets = await getModuleTestSecrets('non-existent-module');
|
|
95
|
+
expect(secrets).toEqual({});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('getSystemTestConfig', () => {
|
|
100
|
+
test('returns system configuration', async () => {
|
|
101
|
+
const config = await getSystemTestConfig();
|
|
102
|
+
expect(config).toBeDefined();
|
|
103
|
+
expect(config.primary_domain).toBe('example.com');
|
|
104
|
+
expect(config.dns).toBeDefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('contains nested configuration', async () => {
|
|
108
|
+
const config = await getSystemTestConfig();
|
|
109
|
+
expect(config.dns).toBeDefined();
|
|
110
|
+
const dns = config.dns as Record<string, unknown>;
|
|
111
|
+
expect(dns.primary).toBe('192.168.0.1');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('getSystemTestSecrets', () => {
|
|
116
|
+
test('returns system secrets (currently empty after container-services migration)', async () => {
|
|
117
|
+
const secrets = await getSystemTestSecrets();
|
|
118
|
+
expect(secrets).toBeDefined();
|
|
119
|
+
expect(typeof secrets).toBe('object');
|
|
120
|
+
// Note: Proxmox credentials are now per-service, not system-wide secrets
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture Test Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for loading test fixtures and test data.
|
|
5
|
+
* Centralizes fixture path resolution and test data loading.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { parse as parseYaml } from 'yaml';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get fixture path
|
|
14
|
+
*
|
|
15
|
+
* Resolves path to fixture directory or file.
|
|
16
|
+
* All fixtures are in ./test-fixtures/ directory.
|
|
17
|
+
*
|
|
18
|
+
* @param fixtureName - Fixture name (e.g., 'modules/artifact-test')
|
|
19
|
+
* @returns Absolute path to fixture
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const modulePath = getFixturePath('modules/artifact-test');
|
|
24
|
+
* // Returns: /absolute/path/to/celilo/backend/test-fixtures/modules/artifact-test
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function getFixturePath(fixtureName: string): string {
|
|
28
|
+
return join(process.cwd(), 'test-fixtures', fixtureName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Test values structure
|
|
33
|
+
*
|
|
34
|
+
* Mirrors test-values.yml structure.
|
|
35
|
+
* Top-level keys: system, system_secrets, modules
|
|
36
|
+
* Module values can include config and secrets
|
|
37
|
+
*/
|
|
38
|
+
export interface TestValues {
|
|
39
|
+
system?: Record<string, unknown>;
|
|
40
|
+
system_secrets?: Record<string, unknown>;
|
|
41
|
+
modules?: Record<string, Record<string, unknown>>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Load test values from test-values.yml
|
|
46
|
+
*
|
|
47
|
+
* Loads test configuration and secrets from centralized test data file.
|
|
48
|
+
* Use for integration tests that need realistic test data.
|
|
49
|
+
*
|
|
50
|
+
* @returns Test values object
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* const testValues = await loadTestValues();
|
|
55
|
+
* const caddy = testValues.modules?.caddy;
|
|
56
|
+
* expect(caddy.config?.hostname).toBe('www');
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export async function loadTestValues(): Promise<TestValues> {
|
|
60
|
+
const testValuesPath = getFixturePath('test-values.yml');
|
|
61
|
+
const content = await readFile(testValuesPath, 'utf-8');
|
|
62
|
+
return parseYaml(content) as TestValues;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get module test configuration
|
|
67
|
+
*
|
|
68
|
+
* Retrieves configuration for specific module from test-values.yml.
|
|
69
|
+
* Returns empty object if module not found.
|
|
70
|
+
* Excludes 'secrets' key from returned config.
|
|
71
|
+
*
|
|
72
|
+
* @param moduleId - Module ID
|
|
73
|
+
* @returns Module configuration
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const config = await getModuleTestConfig('homebridge');
|
|
78
|
+
* expect(config.vmid).toBe(2110);
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export async function getModuleTestConfig(moduleId: string): Promise<Record<string, unknown>> {
|
|
82
|
+
const testValues = await loadTestValues();
|
|
83
|
+
const moduleData = testValues.modules?.[moduleId];
|
|
84
|
+
if (!moduleData) {
|
|
85
|
+
return {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Return all data except 'secrets'
|
|
89
|
+
const { secrets, ...config } = moduleData as Record<string, unknown> & {
|
|
90
|
+
secrets?: unknown;
|
|
91
|
+
};
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get module test secrets
|
|
97
|
+
*
|
|
98
|
+
* Retrieves secrets for specific module from test-values.yml.
|
|
99
|
+
* Returns empty object if module not found or has no secrets.
|
|
100
|
+
*
|
|
101
|
+
* @param moduleId - Module ID
|
|
102
|
+
* @returns Module secrets
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* const secrets = await getModuleTestSecrets('dns-external');
|
|
107
|
+
* expect(secrets.knot_ddns_tsig_secret).toBeTruthy();
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export async function getModuleTestSecrets(moduleId: string): Promise<Record<string, unknown>> {
|
|
111
|
+
const testValues = await loadTestValues();
|
|
112
|
+
const moduleData = testValues.modules?.[moduleId];
|
|
113
|
+
if (!moduleData) {
|
|
114
|
+
return {};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const secrets = (moduleData as Record<string, unknown>).secrets;
|
|
118
|
+
if (!secrets || typeof secrets !== 'object') {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return secrets as Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get system test configuration
|
|
127
|
+
*
|
|
128
|
+
* Retrieves system-wide configuration from test-values.yml.
|
|
129
|
+
*
|
|
130
|
+
* @returns System configuration
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const config = await getSystemTestConfig();
|
|
135
|
+
* expect(config.dns).toBeDefined();
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export async function getSystemTestConfig(): Promise<Record<string, unknown>> {
|
|
139
|
+
const testValues = await loadTestValues();
|
|
140
|
+
return testValues.system ?? {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get system test secrets
|
|
145
|
+
*
|
|
146
|
+
* Retrieves system-wide secrets from test-values.yml.
|
|
147
|
+
*
|
|
148
|
+
* @returns System secrets
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* const secrets = await getSystemTestSecrets();
|
|
153
|
+
* // Note: Provider credentials are now per-service, not system-wide
|
|
154
|
+
* expect(secrets).toBeDefined();
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
export async function getSystemTestSecrets(): Promise<Record<string, unknown>> {
|
|
158
|
+
const testValues = await loadTestValues();
|
|
159
|
+
return testValues.system_secrets ?? {};
|
|
160
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden File Comparison Utilities
|
|
3
|
+
*
|
|
4
|
+
* Compares generated output against golden reference files for regression testing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
9
|
+
import { join, relative } from 'node:path';
|
|
10
|
+
|
|
11
|
+
export interface FileDifference {
|
|
12
|
+
path: string;
|
|
13
|
+
type: 'missing-in-golden' | 'missing-in-generated' | 'content-mismatch';
|
|
14
|
+
goldenContent?: string;
|
|
15
|
+
generatedContent?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ComparisonResult {
|
|
19
|
+
pass: boolean;
|
|
20
|
+
differences: FileDifference[];
|
|
21
|
+
filesCompared: number;
|
|
22
|
+
filesSkipped: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ComparisonOptions {
|
|
26
|
+
/**
|
|
27
|
+
* Files to exclude from comparison (glob patterns relative to base dir)
|
|
28
|
+
* Example: ['**\/*.json', 'secrets/**']
|
|
29
|
+
*/
|
|
30
|
+
excludePatterns?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Compare two directories recursively
|
|
35
|
+
*
|
|
36
|
+
* @param goldenDir - Path to golden reference directory
|
|
37
|
+
* @param generatedDir - Path to generated output directory
|
|
38
|
+
* @param options - Comparison options
|
|
39
|
+
* @returns Comparison result with any differences found
|
|
40
|
+
*/
|
|
41
|
+
export async function compareDirectories(
|
|
42
|
+
goldenDir: string,
|
|
43
|
+
generatedDir: string,
|
|
44
|
+
options: ComparisonOptions = {},
|
|
45
|
+
): Promise<ComparisonResult> {
|
|
46
|
+
const differences: FileDifference[] = [];
|
|
47
|
+
let filesCompared = 0;
|
|
48
|
+
let filesSkipped = 0;
|
|
49
|
+
|
|
50
|
+
// Files to skip (contain encrypted secrets that change each run)
|
|
51
|
+
const skipPatterns = options.excludePatterns || [];
|
|
52
|
+
const shouldSkip = (path: string): boolean => {
|
|
53
|
+
return skipPatterns.some((pattern) => {
|
|
54
|
+
// Simple glob matching - just check if path includes pattern
|
|
55
|
+
return path.includes(pattern.replace('**/', '').replace('*', ''));
|
|
56
|
+
});
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Get all files from both directories
|
|
60
|
+
const goldenFiles = await getAllFiles(goldenDir);
|
|
61
|
+
const generatedFiles = await getAllFiles(generatedDir);
|
|
62
|
+
|
|
63
|
+
// Convert to relative paths for comparison
|
|
64
|
+
const goldenRelativePaths = new Set(goldenFiles.map((f) => relative(goldenDir, f)));
|
|
65
|
+
const generatedRelativePaths = new Set(generatedFiles.map((f) => relative(generatedDir, f)));
|
|
66
|
+
|
|
67
|
+
// Check for files in golden but not in generated
|
|
68
|
+
for (const relativePath of goldenRelativePaths) {
|
|
69
|
+
if (!generatedRelativePaths.has(relativePath)) {
|
|
70
|
+
// Skip if matches exclude pattern
|
|
71
|
+
if (shouldSkip(relativePath)) {
|
|
72
|
+
filesSkipped++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
differences.push({
|
|
76
|
+
path: relativePath,
|
|
77
|
+
type: 'missing-in-generated',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check for files in generated but not in golden
|
|
83
|
+
for (const relativePath of generatedRelativePaths) {
|
|
84
|
+
if (!goldenRelativePaths.has(relativePath)) {
|
|
85
|
+
// Skip if matches exclude pattern
|
|
86
|
+
if (shouldSkip(relativePath)) {
|
|
87
|
+
filesSkipped++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
differences.push({
|
|
91
|
+
path: relativePath,
|
|
92
|
+
type: 'missing-in-golden',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Compare content of files that exist in both
|
|
98
|
+
for (const relativePath of goldenRelativePaths) {
|
|
99
|
+
if (generatedRelativePaths.has(relativePath)) {
|
|
100
|
+
// Skip files that match exclude patterns
|
|
101
|
+
if (shouldSkip(relativePath)) {
|
|
102
|
+
filesSkipped++;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const goldenPath = join(goldenDir, relativePath);
|
|
107
|
+
const generatedPath = join(generatedDir, relativePath);
|
|
108
|
+
|
|
109
|
+
const goldenContent = await readFile(goldenPath, 'utf-8');
|
|
110
|
+
const generatedContent = await readFile(generatedPath, 'utf-8');
|
|
111
|
+
|
|
112
|
+
if (goldenContent !== generatedContent) {
|
|
113
|
+
differences.push({
|
|
114
|
+
path: relativePath,
|
|
115
|
+
type: 'content-mismatch',
|
|
116
|
+
goldenContent,
|
|
117
|
+
generatedContent,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
filesCompared++;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
pass: differences.length === 0,
|
|
127
|
+
differences,
|
|
128
|
+
filesCompared,
|
|
129
|
+
filesSkipped,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get all files in a directory recursively
|
|
135
|
+
*
|
|
136
|
+
* @param dir - Directory path
|
|
137
|
+
* @returns Array of absolute file paths
|
|
138
|
+
*/
|
|
139
|
+
async function getAllFiles(dir: string): Promise<string[]> {
|
|
140
|
+
const files: string[] = [];
|
|
141
|
+
|
|
142
|
+
if (!existsSync(dir)) {
|
|
143
|
+
return files;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
147
|
+
|
|
148
|
+
for (const entry of entries) {
|
|
149
|
+
const fullPath = join(dir, entry.name);
|
|
150
|
+
|
|
151
|
+
if (entry.isDirectory()) {
|
|
152
|
+
const subFiles = await getAllFiles(fullPath);
|
|
153
|
+
files.push(...subFiles);
|
|
154
|
+
} else {
|
|
155
|
+
files.push(fullPath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return files;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Format differences for display
|
|
164
|
+
*
|
|
165
|
+
* @param differences - Array of differences
|
|
166
|
+
* @returns Formatted string
|
|
167
|
+
*/
|
|
168
|
+
export function formatDifferences(differences: FileDifference[]): string {
|
|
169
|
+
const lines: string[] = [];
|
|
170
|
+
|
|
171
|
+
for (const diff of differences) {
|
|
172
|
+
switch (diff.type) {
|
|
173
|
+
case 'missing-in-golden':
|
|
174
|
+
lines.push(`❌ File exists in generated but not in golden: ${diff.path}`);
|
|
175
|
+
lines.push(' (Run with --update-golden to add this file)');
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case 'missing-in-generated':
|
|
179
|
+
lines.push(`❌ File exists in golden but not in generated: ${diff.path}`);
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case 'content-mismatch':
|
|
183
|
+
lines.push(`❌ Content mismatch: ${diff.path}`);
|
|
184
|
+
if (diff.goldenContent && diff.generatedContent) {
|
|
185
|
+
lines.push(' Golden (first 200 chars):');
|
|
186
|
+
lines.push(` ${diff.goldenContent.substring(0, 200).replace(/\n/g, '\\n')}`);
|
|
187
|
+
lines.push(' Generated (first 200 chars):');
|
|
188
|
+
lines.push(` ${diff.generatedContent.substring(0, 200).replace(/\n/g, '\\n')}`);
|
|
189
|
+
}
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
lines.push('');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return lines.join('\n');
|
|
197
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Utilities - Public API
|
|
3
|
+
*
|
|
4
|
+
* Exports all test utilities for use in integration and unit tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Persistent CLI testing (new)
|
|
8
|
+
export { CLIContext, ResponseBuilder, type RunOptions } from './cli-context';
|
|
9
|
+
export { type CLIResult, CLIResultImpl } from './cli-result';
|
|
10
|
+
export {
|
|
11
|
+
fixtures,
|
|
12
|
+
homebridgeModule,
|
|
13
|
+
caddyModule,
|
|
14
|
+
dnsExternalModule,
|
|
15
|
+
multiModule,
|
|
16
|
+
} from './module-fixtures';
|
|
17
|
+
|
|
18
|
+
// Legacy CLI testing (deprecated - use CLIContext instead)
|
|
19
|
+
export { runCli, runCliExpectingFailure } from './cli';
|
|
20
|
+
|
|
21
|
+
// Integration test setup
|
|
22
|
+
export { setupIntegrationTest, type IntegrationTestContext } from './integration';
|
|
23
|
+
|
|
24
|
+
// Database utilities
|
|
25
|
+
export {
|
|
26
|
+
setupTestDatabase,
|
|
27
|
+
setupTestDatabaseFile,
|
|
28
|
+
cleanupTestDatabase,
|
|
29
|
+
} from './database';
|
|
30
|
+
|
|
31
|
+
// Filesystem utilities
|
|
32
|
+
export {
|
|
33
|
+
createTempDirectory,
|
|
34
|
+
removeTempDirectory,
|
|
35
|
+
copyFixture,
|
|
36
|
+
createTempDirectoryWithCleanup,
|
|
37
|
+
} from './filesystem';
|
|
38
|
+
|
|
39
|
+
// Module utilities
|
|
40
|
+
export {
|
|
41
|
+
importTestModule,
|
|
42
|
+
cleanupTestModule,
|
|
43
|
+
importMultipleTestModules,
|
|
44
|
+
cleanupMultipleTestModules,
|
|
45
|
+
getModuleState,
|
|
46
|
+
type ImportTestModuleOptions,
|
|
47
|
+
} from './modules';
|
|
48
|
+
|
|
49
|
+
// Fixture utilities
|
|
50
|
+
export {
|
|
51
|
+
getFixturePath,
|
|
52
|
+
loadTestValues,
|
|
53
|
+
getModuleTestConfig,
|
|
54
|
+
getModuleTestSecrets,
|
|
55
|
+
getSystemTestConfig,
|
|
56
|
+
getSystemTestSecrets,
|
|
57
|
+
type TestValues,
|
|
58
|
+
} from './fixtures';
|
|
59
|
+
|
|
60
|
+
// Golden file testing
|
|
61
|
+
export {
|
|
62
|
+
compareDirectories,
|
|
63
|
+
formatDifferences,
|
|
64
|
+
type FileDifference,
|
|
65
|
+
type ComparisonResult,
|
|
66
|
+
type ComparisonOptions,
|
|
67
|
+
} from './golden-diff';
|
|
68
|
+
|
|
69
|
+
// Value extraction
|
|
70
|
+
export {
|
|
71
|
+
extractTerraformValues,
|
|
72
|
+
extractAnsibleValues,
|
|
73
|
+
compareExtractedValues,
|
|
74
|
+
formatValueMismatches,
|
|
75
|
+
type ExtractedValues,
|
|
76
|
+
type ValueExtractionResult,
|
|
77
|
+
} from './value-extractor';
|