@vibesdotdev/secrets 0.0.1
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 +59 -0
- package/SPEC.md +47 -0
- package/dist/cli/check/schemas/check-result.d.ts +9 -0
- package/dist/cli/check/schemas/check-result.d.ts.map +1 -0
- package/dist/cli/check/schemas/check-result.js +2 -0
- package/dist/cli/check/schemas/check-result.js.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.js +19 -0
- package/dist/cli/check/secrets.check.cli-command.descriptor.js.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.impl.d.ts +5 -0
- package/dist/cli/check/secrets.check.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/check/secrets.check.cli-command.impl.js +135 -0
- package/dist/cli/check/secrets.check.cli-command.impl.js.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.js +20 -0
- package/dist/cli/export/secrets.export.cli-command.descriptor.js.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.impl.d.ts +5 -0
- package/dist/cli/export/secrets.export.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/export/secrets.export.cli-command.impl.js +104 -0
- package/dist/cli/export/secrets.export.cli-command.impl.js.map +1 -0
- package/dist/cli/hooks/pre-commit-secrets.d.ts +2 -0
- package/dist/cli/hooks/pre-commit-secrets.d.ts.map +1 -0
- package/dist/cli/hooks/pre-commit-secrets.js +68 -0
- package/dist/cli/hooks/pre-commit-secrets.js.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.js +19 -0
- package/dist/cli/import/secrets.import.cli-command.descriptor.js.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.impl.d.ts +5 -0
- package/dist/cli/import/secrets.import.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/import/secrets.import.cli-command.impl.js +155 -0
- package/dist/cli/import/secrets.import.cli-command.impl.js.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.js +18 -0
- package/dist/cli/list/secrets.list.cli-command.descriptor.js.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.impl.d.ts +5 -0
- package/dist/cli/list/secrets.list.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/list/secrets.list.cli-command.impl.js +61 -0
- package/dist/cli/list/secrets.list.cli-command.impl.js.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js +16 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.js.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts +5 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js +10 -0
- package/dist/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.js.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.js +20 -0
- package/dist/cli/pull/secrets.pull.cli-command.descriptor.js.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts +5 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.js +76 -0
- package/dist/cli/pull/secrets.pull.cli-command.impl.js.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.js +22 -0
- package/dist/cli/push/secrets.push.cli-command.descriptor.js.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.impl.d.ts +5 -0
- package/dist/cli/push/secrets.push.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/push/secrets.push.cli-command.impl.js +109 -0
- package/dist/cli/push/secrets.push.cli-command.impl.js.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js +19 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.descriptor.js.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts +5 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.js +85 -0
- package/dist/cli/reveal/secrets.reveal.cli-command.impl.js.map +1 -0
- package/dist/cli/secrets.cli-group.descriptor.d.ts +4 -0
- package/dist/cli/secrets.cli-group.descriptor.d.ts.map +1 -0
- package/dist/cli/secrets.cli-group.descriptor.js +11 -0
- package/dist/cli/secrets.cli-group.descriptor.js.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.js +21 -0
- package/dist/cli/set/secrets.set.cli-command.descriptor.js.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.impl.d.ts +5 -0
- package/dist/cli/set/secrets.set.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/set/secrets.set.cli-command.impl.js +59 -0
- package/dist/cli/set/secrets.set.cli-command.impl.js.map +1 -0
- package/dist/cli/shared/resolve-environment.d.ts +14 -0
- package/dist/cli/shared/resolve-environment.d.ts.map +1 -0
- package/dist/cli/shared/resolve-environment.js +45 -0
- package/dist/cli/shared/resolve-environment.js.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts +4 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.d.ts.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.js +20 -0
- package/dist/cli/unset/secrets.unset.cli-command.descriptor.js.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts +5 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.d.ts.map +1 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.js +31 -0
- package/dist/cli/unset/secrets.unset.cli-command.impl.js.map +1 -0
- package/dist/docs/backends.docs.descriptor.d.ts +4 -0
- package/dist/docs/backends.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/backends.docs.descriptor.js +149 -0
- package/dist/docs/backends.docs.descriptor.js.map +1 -0
- package/dist/docs/encryption.docs.descriptor.d.ts +4 -0
- package/dist/docs/encryption.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/encryption.docs.descriptor.js +163 -0
- package/dist/docs/encryption.docs.descriptor.js.map +1 -0
- package/dist/docs/env-file.docs.descriptor.d.ts +4 -0
- package/dist/docs/env-file.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/env-file.docs.descriptor.js +207 -0
- package/dist/docs/env-file.docs.descriptor.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/kinds/index.d.ts +4 -0
- package/dist/kinds/index.d.ts.map +1 -0
- package/dist/kinds/index.js +3 -0
- package/dist/kinds/index.js.map +1 -0
- package/dist/kinds/schemas/store.schema.d.ts +49 -0
- package/dist/kinds/schemas/store.schema.d.ts.map +1 -0
- package/dist/kinds/schemas/store.schema.js +34 -0
- package/dist/kinds/schemas/store.schema.js.map +1 -0
- package/dist/kinds/schemas/store.types.d.ts +28 -0
- package/dist/kinds/schemas/store.types.d.ts.map +1 -0
- package/dist/kinds/schemas/store.types.js +2 -0
- package/dist/kinds/schemas/store.types.js.map +1 -0
- package/dist/kinds/store.interface.d.ts +2 -0
- package/dist/kinds/store.interface.d.ts.map +1 -0
- package/dist/kinds/store.interface.js +2 -0
- package/dist/kinds/store.interface.js.map +1 -0
- package/dist/kinds/store.kind.d.ts +10 -0
- package/dist/kinds/store.kind.d.ts.map +1 -0
- package/dist/kinds/store.kind.js +36 -0
- package/dist/kinds/store.kind.js.map +1 -0
- package/dist/kinds/store.schema.d.ts +2 -0
- package/dist/kinds/store.schema.d.ts.map +1 -0
- package/dist/kinds/store.schema.js +2 -0
- package/dist/kinds/store.schema.js.map +1 -0
- package/dist/manifest/canonical.d.ts +30 -0
- package/dist/manifest/canonical.d.ts.map +1 -0
- package/dist/manifest/canonical.js +313 -0
- package/dist/manifest/canonical.js.map +1 -0
- package/dist/manifest/import-manifest.schema.d.ts +77 -0
- package/dist/manifest/import-manifest.schema.d.ts.map +1 -0
- package/dist/manifest/import-manifest.schema.js +55 -0
- package/dist/manifest/import-manifest.schema.js.map +1 -0
- package/dist/manifest/index.d.ts +3 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +3 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/requirements/index.d.ts +2 -0
- package/dist/requirements/index.d.ts.map +1 -0
- package/dist/requirements/index.js +2 -0
- package/dist/requirements/index.js.map +1 -0
- package/dist/requirements/resolver.d.ts +52 -0
- package/dist/requirements/resolver.d.ts.map +1 -0
- package/dist/requirements/resolver.js +196 -0
- package/dist/requirements/resolver.js.map +1 -0
- package/dist/requirements/schemas/requirements.d.ts +27 -0
- package/dist/requirements/schemas/requirements.d.ts.map +1 -0
- package/dist/requirements/schemas/requirements.js +2 -0
- package/dist/requirements/schemas/requirements.js.map +1 -0
- package/dist/secrets.plugin.d.ts +8 -0
- package/dist/secrets.plugin.d.ts.map +1 -0
- package/dist/secrets.plugin.js +59 -0
- package/dist/secrets.plugin.js.map +1 -0
- package/package.json +108 -0
- package/src/cli/check/schemas/check-result.ts +8 -0
- package/src/cli/check/secrets.check.cli-command.descriptor.ts +21 -0
- package/src/cli/check/secrets.check.cli-command.impl.ts +163 -0
- package/src/cli/export/secrets.export.cli-command.descriptor.ts +22 -0
- package/src/cli/export/secrets.export.cli-command.impl.ts +139 -0
- package/src/cli/hooks/pre-commit-secrets.ts +73 -0
- package/src/cli/import/secrets.import.cli-command.descriptor.ts +21 -0
- package/src/cli/import/secrets.import.cli-command.impl.ts +178 -0
- package/src/cli/list/secrets.list.cli-command.descriptor.ts +21 -0
- package/src/cli/list/secrets.list.cli-command.impl.ts +79 -0
- package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.descriptor.ts +18 -0
- package/src/cli/pre-commit/secrets.pre-commit-check.cli-command.impl.ts +11 -0
- package/src/cli/pull/secrets.pull.cli-command.descriptor.ts +22 -0
- package/src/cli/pull/secrets.pull.cli-command.impl.ts +103 -0
- package/src/cli/push/secrets.push.cli-command.descriptor.ts +24 -0
- package/src/cli/push/secrets.push.cli-command.impl.ts +149 -0
- package/src/cli/reveal/secrets.reveal.cli-command.descriptor.ts +21 -0
- package/src/cli/reveal/secrets.reveal.cli-command.impl.ts +108 -0
- package/src/cli/secrets.cli-group.descriptor.ts +13 -0
- package/src/cli/set/secrets.set.cli-command.descriptor.ts +23 -0
- package/src/cli/set/secrets.set.cli-command.impl.ts +77 -0
- package/src/cli/shared/resolve-environment.ts +57 -0
- package/src/cli/unset/secrets.unset.cli-command.descriptor.ts +22 -0
- package/src/cli/unset/secrets.unset.cli-command.impl.ts +41 -0
- package/src/docs/backends.docs.descriptor.ts +151 -0
- package/src/docs/encryption.docs.descriptor.ts +165 -0
- package/src/docs/env-file.docs.descriptor.ts +209 -0
- package/src/index.ts +35 -0
- package/src/kinds/index.ts +12 -0
- package/src/kinds/schemas/store.schema.ts +47 -0
- package/src/kinds/schemas/store.types.ts +35 -0
- package/src/kinds/store.interface.ts +1 -0
- package/src/kinds/store.kind.ts +52 -0
- package/src/kinds/store.schema.ts +8 -0
- package/src/manifest/canonical.ts +324 -0
- package/src/manifest/import-manifest.schema.ts +63 -0
- package/src/manifest/index.ts +12 -0
- package/src/requirements/index.ts +6 -0
- package/src/requirements/resolver.ts +216 -0
- package/src/requirements/schemas/requirements.ts +29 -0
- package/src/secrets.plugin.ts +65 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
3
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
4
|
+
import type { SecretsStoreDescriptor } from '../../kinds/store.schema';
|
|
5
|
+
import type { CheckResult } from './schemas/check-result';
|
|
6
|
+
import { resolveSecretRequirements, groupRequirementsByApp } from '../../requirements/resolver';
|
|
7
|
+
import { canonicalImportManifest } from '../../manifest/canonical';
|
|
8
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
9
|
+
|
|
10
|
+
interface ManifestCheck {
|
|
11
|
+
key: string;
|
|
12
|
+
required: boolean;
|
|
13
|
+
source: string | undefined;
|
|
14
|
+
status: 'ok' | 'missing' | 'warn';
|
|
15
|
+
storedIn: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
20
|
+
const runtime = getVibesRuntime();
|
|
21
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
22
|
+
const options = opts as {
|
|
23
|
+
environment?: string;
|
|
24
|
+
app?: string;
|
|
25
|
+
manifest?: boolean;
|
|
26
|
+
json?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
30
|
+
ui,
|
|
31
|
+
options.environment
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let requirements = await resolveSecretRequirements(runtime);
|
|
35
|
+
if (options.app) {
|
|
36
|
+
requirements = requirements.filter((r) => r.appId === options.app);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (requirements.length === 0 && !options.manifest) {
|
|
40
|
+
ui.info(options.app
|
|
41
|
+
? `No secret requirements found for app: ${options.app}`
|
|
42
|
+
: 'No secret requirements found in infra descriptors');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
47
|
+
const tierStores = descriptors.filter((d) => d.tiers.includes(envTier));
|
|
48
|
+
|
|
49
|
+
const availableKeys = new Map<string, string>();
|
|
50
|
+
for (const desc of tierStores) {
|
|
51
|
+
const impl = (await runtime
|
|
52
|
+
.query('secrets/store')
|
|
53
|
+
.withId(desc.id)
|
|
54
|
+
.resolve()) as SecretsStoreImplementation;
|
|
55
|
+
const entries = await impl.list(envName);
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
if (entry.hasValue && !availableKeys.has(entry.key)) {
|
|
58
|
+
availableKeys.set(entry.key, entry.source);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const results: CheckResult[] = requirements.map((req) => {
|
|
64
|
+
const source = availableKeys.get(req.key);
|
|
65
|
+
let status: 'ok' | 'missing' | 'warn';
|
|
66
|
+
if (source) {
|
|
67
|
+
status = 'ok';
|
|
68
|
+
} else if (req.required) {
|
|
69
|
+
status = 'missing';
|
|
70
|
+
} else {
|
|
71
|
+
status = 'warn';
|
|
72
|
+
}
|
|
73
|
+
return { ...req, status, source };
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const manifestChecks: ManifestCheck[] = options.manifest
|
|
77
|
+
? canonicalImportManifest.secrets.map((entry) => {
|
|
78
|
+
const storedIn = availableKeys.get(entry.key);
|
|
79
|
+
let status: 'ok' | 'missing' | 'warn';
|
|
80
|
+
if (storedIn) status = 'ok';
|
|
81
|
+
else if (entry.required) status = 'missing';
|
|
82
|
+
else status = 'warn';
|
|
83
|
+
return { key: entry.key, required: entry.required, source: entry.source, status, storedIn };
|
|
84
|
+
})
|
|
85
|
+
: [];
|
|
86
|
+
|
|
87
|
+
if (options.json) {
|
|
88
|
+
const summary: Record<string, unknown> = {
|
|
89
|
+
environment: envName,
|
|
90
|
+
tier: envTier,
|
|
91
|
+
total: results.length,
|
|
92
|
+
ok: results.filter((r) => r.status === 'ok').length,
|
|
93
|
+
missing: results.filter((r) => r.status === 'missing').length,
|
|
94
|
+
warn: results.filter((r) => r.status === 'warn').length,
|
|
95
|
+
results
|
|
96
|
+
};
|
|
97
|
+
if (options.manifest) {
|
|
98
|
+
summary.manifest = {
|
|
99
|
+
total: manifestChecks.length,
|
|
100
|
+
ok: manifestChecks.filter((m) => m.status === 'ok').length,
|
|
101
|
+
missing: manifestChecks.filter((m) => m.status === 'missing').length,
|
|
102
|
+
warn: manifestChecks.filter((m) => m.status === 'warn').length,
|
|
103
|
+
results: manifestChecks
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
ui.render(summary, { format: 'json' });
|
|
107
|
+
const manifestMissing = manifestChecks.filter((m) => m.status === 'missing').length;
|
|
108
|
+
if (results.filter((r) => r.status === 'missing').length > 0 || manifestMissing > 0) {
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const grouped = groupRequirementsByApp(requirements);
|
|
115
|
+
const okCount = results.filter((r) => r.status === 'ok').length;
|
|
116
|
+
const missingCount = results.filter((r) => r.status === 'missing').length;
|
|
117
|
+
const warnCount = results.filter((r) => r.status === 'warn').length;
|
|
118
|
+
|
|
119
|
+
ui.log(`\nSecrets check for environment: ${envName} (${envTier})\n`);
|
|
120
|
+
|
|
121
|
+
for (const [appId, reqs] of grouped) {
|
|
122
|
+
ui.log(` ${appId}`);
|
|
123
|
+
for (const req of reqs) {
|
|
124
|
+
const result = results.find((r) => r.key === req.key && r.appId === req.appId);
|
|
125
|
+
if (!result) continue;
|
|
126
|
+
const icon = result.status === 'ok' ? '+' : result.status === 'missing' ? 'x' : '?';
|
|
127
|
+
const label = result.required ? 'required' : 'optional';
|
|
128
|
+
const src = result.source ? ` (${result.source})` : '';
|
|
129
|
+
ui.log(` [${icon}] ${result.key.padEnd(40)} ${label.padEnd(10)} ${result.status}${src}`);
|
|
130
|
+
}
|
|
131
|
+
ui.log('');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ui.log(` Summary: ${okCount} ok, ${missingCount} missing, ${warnCount} optional not set`);
|
|
135
|
+
if (missingCount > 0) {
|
|
136
|
+
ui.log(` Run 'vibes secrets set <KEY>' to add missing secrets`);
|
|
137
|
+
}
|
|
138
|
+
ui.log('');
|
|
139
|
+
|
|
140
|
+
let manifestMissing = 0;
|
|
141
|
+
if (options.manifest && manifestChecks.length > 0) {
|
|
142
|
+
ui.log(` Canonical manifest cross-check (${manifestChecks.length} entries)`);
|
|
143
|
+
for (const m of manifestChecks) {
|
|
144
|
+
const icon = m.status === 'ok' ? '+' : m.status === 'missing' ? 'x' : '?';
|
|
145
|
+
const label = m.required ? 'required' : 'optional';
|
|
146
|
+
const where = m.storedIn ? ` (${m.storedIn})` : ` <- ${m.source}`;
|
|
147
|
+
ui.log(` [${icon}] ${m.key.padEnd(40)} ${label.padEnd(10)} ${m.status}${where}`);
|
|
148
|
+
}
|
|
149
|
+
const mOk = manifestChecks.filter((m) => m.status === 'ok').length;
|
|
150
|
+
manifestMissing = manifestChecks.filter((m) => m.status === 'missing').length;
|
|
151
|
+
const mWarn = manifestChecks.filter((m) => m.status === 'warn').length;
|
|
152
|
+
ui.log(` Manifest summary: ${mOk} ok, ${manifestMissing} missing, ${mWarn} optional not set`);
|
|
153
|
+
if (manifestMissing > 0) {
|
|
154
|
+
ui.log(` Run 'vibes secrets import' to populate from local sources`);
|
|
155
|
+
}
|
|
156
|
+
ui.log('');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (missingCount > 0 || manifestMissing > 0) {
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.export',
|
|
6
|
+
name: 'export',
|
|
7
|
+
description: 'Export secrets from a local backend to an env-formatted file (audit-logged)',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--environment <name>', description: 'Source environment (default: current)' },
|
|
11
|
+
{ flags: '--from <backend>', description: 'Source backend (default: highest-priority local)' },
|
|
12
|
+
{ flags: '--out <path>', description: 'Output file path (required; created mode 0600)' },
|
|
13
|
+
{ flags: '--manifest-only', description: 'Limit to keys declared in the canonical manifest' },
|
|
14
|
+
{ flags: '--force', description: 'Skip the interactive bulk-reveal confirmation' }
|
|
15
|
+
],
|
|
16
|
+
surfaces: ['cli'],
|
|
17
|
+
hardware: ['consumer'],
|
|
18
|
+
enabled: true,
|
|
19
|
+
order: 35
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default descriptor;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { resolve, dirname } from 'node:path';
|
|
2
|
+
import { mkdir, writeFile, chmod, rename } from 'node:fs/promises';
|
|
3
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
4
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
5
|
+
import type { AuditLogInput } from '@vibesdotdev/logging';
|
|
6
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
7
|
+
import type { SecretsStoreDescriptor, EnvironmentTier } from '../../kinds/store.schema';
|
|
8
|
+
import { canonicalImportManifest } from '../../manifest/canonical';
|
|
9
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
10
|
+
|
|
11
|
+
const LOCAL_BACKENDS = new Set(['env-file', 'encrypted-local']);
|
|
12
|
+
|
|
13
|
+
function findSourceBackend(
|
|
14
|
+
descriptors: SecretsStoreDescriptor[],
|
|
15
|
+
tier: EnvironmentTier,
|
|
16
|
+
backendId?: string
|
|
17
|
+
): SecretsStoreDescriptor | undefined {
|
|
18
|
+
if (backendId) return descriptors.find((d) => d.id === backendId);
|
|
19
|
+
return descriptors
|
|
20
|
+
.filter((d) => d.tiers.includes(tier) && LOCAL_BACKENDS.has(d.backend))
|
|
21
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function serializeEnv(secrets: Record<string, string>): string {
|
|
25
|
+
const lines = Object.keys(secrets)
|
|
26
|
+
.sort()
|
|
27
|
+
.map((key) => {
|
|
28
|
+
const value = secrets[key] ?? '';
|
|
29
|
+
const needsQuote = value.includes(' ') || value.includes('#') || value.includes('"');
|
|
30
|
+
const escaped = needsQuote ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
31
|
+
return `${key}=${escaped}`;
|
|
32
|
+
});
|
|
33
|
+
return `# Generated by 'vibes secrets export' — DO NOT COMMIT\n${lines.join('\n')}\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function writeEnvFile(path: string, content: string): Promise<void> {
|
|
37
|
+
const absolute = resolve(process.cwd(), path);
|
|
38
|
+
await mkdir(dirname(absolute), { recursive: true });
|
|
39
|
+
const tmp = `${absolute}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
40
|
+
await writeFile(tmp, content, { encoding: 'utf-8', mode: 0o600 });
|
|
41
|
+
await rename(tmp, absolute);
|
|
42
|
+
await chmod(absolute, 0o600);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function promptConfirm(prompt: string): Promise<boolean> {
|
|
46
|
+
process.stderr.write(prompt);
|
|
47
|
+
for await (const chunk of process.stdin) {
|
|
48
|
+
const answer = (chunk as Buffer).toString('utf-8').trim().toLowerCase();
|
|
49
|
+
return answer === 'y' || answer === 'yes';
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function emitAudit(input: AuditLogInput): void {
|
|
55
|
+
process.stderr.write(`[AUDIT] ${JSON.stringify({
|
|
56
|
+
id: crypto.randomUUID(),
|
|
57
|
+
timestamp: new Date().toISOString(),
|
|
58
|
+
...input
|
|
59
|
+
})}\n`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default {
|
|
63
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
64
|
+
const runtime = getVibesRuntime();
|
|
65
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
66
|
+
const options = opts as {
|
|
67
|
+
environment?: string;
|
|
68
|
+
from?: string;
|
|
69
|
+
out?: string;
|
|
70
|
+
manifestOnly?: boolean;
|
|
71
|
+
force?: boolean;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (!options.out) {
|
|
75
|
+
ui.error('--out <path> is required. Export refuses to write secrets to stdout.');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
80
|
+
ui,
|
|
81
|
+
options.environment
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
85
|
+
const source = findSourceBackend(descriptors, envTier, options.from);
|
|
86
|
+
if (!source) {
|
|
87
|
+
ui.error('No local secrets store found to export from.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const impl = (await runtime
|
|
92
|
+
.query('secrets/store')
|
|
93
|
+
.withId(source.id)
|
|
94
|
+
.resolve()) as SecretsStoreImplementation;
|
|
95
|
+
let secrets = await impl.getAll(envName);
|
|
96
|
+
|
|
97
|
+
if (options.manifestOnly) {
|
|
98
|
+
const manifestKeys = new Set(canonicalImportManifest.secrets.map((s) => s.key));
|
|
99
|
+
secrets = Object.fromEntries(
|
|
100
|
+
Object.entries(secrets).filter(([k]) => manifestKeys.has(k))
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const count = Object.keys(secrets).length;
|
|
105
|
+
if (count === 0) {
|
|
106
|
+
ui.info(`No secrets to export from [${source.id}] for ${envName}.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!options.force) {
|
|
111
|
+
const confirmed = await promptConfirm(
|
|
112
|
+
`\n[EXPORT] About to write ${count} secret value(s) from [${source.id}] (${envName}) to ${options.out}.\nThis bulk reveal is logged to the audit trail.\nContinue? [y/N] `
|
|
113
|
+
);
|
|
114
|
+
if (!confirmed) {
|
|
115
|
+
ui.info('Export cancelled.');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
emitAudit({
|
|
121
|
+
category: 'data_access',
|
|
122
|
+
severity: 'high',
|
|
123
|
+
action: 'secrets.export',
|
|
124
|
+
actor: { id: 'cli', type: 'user', name: 'CLI user' },
|
|
125
|
+
target: { id: source.id, type: 'secrets-store', name: source.id },
|
|
126
|
+
outcome: 'success',
|
|
127
|
+
context: {
|
|
128
|
+
environment: envName,
|
|
129
|
+
backend: source.id,
|
|
130
|
+
count,
|
|
131
|
+
manifestOnly: options.manifestOnly === true,
|
|
132
|
+
outPath: options.out
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await writeEnvFile(options.out, serializeEnv(secrets));
|
|
137
|
+
ui.success(`Exported ${count} secret(s) from [${source.id}] to ${options.out} (mode 0600)`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const SECRET_PATTERNS: Array<{ pattern: RegExp; name: string }> = [
|
|
2
|
+
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe live secret key (sk_live_)' },
|
|
3
|
+
{ pattern: /pk_live_[a-zA-Z0-9]{24,}/, name: 'Stripe live public key (pk_live_)' },
|
|
4
|
+
{ pattern: /do_[a-zA-Z0-9]{32,}/, name: 'DigitalOcean API key (do_)' },
|
|
5
|
+
{ pattern: /AKIA[a-zA-Z0-9]{16,}/, name: 'AWS access key ID (AKIA)' },
|
|
6
|
+
{ pattern: /ghp_[a-zA-Z0-9]{36}/, name: 'GitHub personal access token (ghp_)' },
|
|
7
|
+
{ pattern: /gho_[a-zA-Z0-9]{36}/, name: 'GitHub OAuth token (gho_)' }
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export async function runSecretsPreCommitCheck(workspacePath: string): Promise<number> {
|
|
11
|
+
const { execSync } = await import('node:child_process');
|
|
12
|
+
|
|
13
|
+
let stagedFiles: string[];
|
|
14
|
+
try {
|
|
15
|
+
const output = execSync('git diff --cached --name-only --diff-filter=ACM', {
|
|
16
|
+
cwd: workspacePath,
|
|
17
|
+
encoding: 'utf-8'
|
|
18
|
+
});
|
|
19
|
+
stagedFiles = output.trim().split('\n').filter(Boolean);
|
|
20
|
+
} catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!stagedFiles.length) return 0;
|
|
25
|
+
|
|
26
|
+
const violations: Array<{ file: string; name: string; line?: string }> = [];
|
|
27
|
+
const { readFileSync } = await import('node:fs');
|
|
28
|
+
|
|
29
|
+
for (const file of stagedFiles) {
|
|
30
|
+
const fullPath = `${workspacePath}/${file}`;
|
|
31
|
+
|
|
32
|
+
let content: string;
|
|
33
|
+
try {
|
|
34
|
+
content = readFileSync(fullPath, 'utf-8');
|
|
35
|
+
} catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
for (let i = 0; i < lines.length; i++) {
|
|
41
|
+
const line = lines[i];
|
|
42
|
+
for (const { pattern, name } of SECRET_PATTERNS) {
|
|
43
|
+
if (pattern.test(line)) {
|
|
44
|
+
violations.push({
|
|
45
|
+
file,
|
|
46
|
+
name,
|
|
47
|
+
line: line.trim().substring(0, 80)
|
|
48
|
+
});
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (violations.length > 0) {
|
|
56
|
+
process.stderr.write(`\n[SECRETS-001] ${violations.length} secret pattern(s) detected in staged files.\n\n`);
|
|
57
|
+
for (const v of violations) {
|
|
58
|
+
process.stderr.write(` BLOCKED ${v.file}\n`);
|
|
59
|
+
process.stderr.write(` Pattern: ${v.name}\n`);
|
|
60
|
+
if (v.line) {
|
|
61
|
+
process.stderr.write(` Line: ${v.line}\n`);
|
|
62
|
+
}
|
|
63
|
+
process.stderr.write('\n');
|
|
64
|
+
}
|
|
65
|
+
process.stderr.write('Commit blocked: production secrets must not be committed.\n');
|
|
66
|
+
process.stderr.write(' - Rotate any exposed keys immediately.\n');
|
|
67
|
+
process.stderr.write(' - Use `vibes secrets set` to store in a secret manager.\n');
|
|
68
|
+
process.stderr.write(' - Add to .gitignore if genuinely not a secret.\n\n');
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.import',
|
|
6
|
+
name: 'import',
|
|
7
|
+
description: 'Import secrets from local root env files into a backend, driven by the canonical manifest',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
11
|
+
{ flags: '--to <backend>', description: 'Target backend (default: encrypted-local)' },
|
|
12
|
+
{ flags: '--source <id>', description: 'Restrict to one source from the manifest' },
|
|
13
|
+
{ flags: '--dry-run', description: 'Show what would be imported without writing' }
|
|
14
|
+
],
|
|
15
|
+
surfaces: ['cli'],
|
|
16
|
+
hardware: ['consumer'],
|
|
17
|
+
enabled: true,
|
|
18
|
+
order: 30
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default descriptor;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
4
|
+
import type { UIContext } from '@vibesdotdev/cli/providers';
|
|
5
|
+
import type { SecretsStoreImplementation } from '../../kinds/store.interface';
|
|
6
|
+
import type { SecretsStoreDescriptor } from '../../kinds/store.schema';
|
|
7
|
+
import { canonicalImportManifest } from '../../manifest/canonical';
|
|
8
|
+
import { resolveSecretsEnvironment } from '../shared/resolve-environment';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tiny line-oriented .env parser. Returns a flat KEY → value map.
|
|
12
|
+
* Quoted values are unwrapped. Comments and blank lines are ignored.
|
|
13
|
+
* Missing files yield an empty map (not an error) — sources are bootstrap inputs.
|
|
14
|
+
*/
|
|
15
|
+
async function readEnvFile(path: string): Promise<Record<string, string>> {
|
|
16
|
+
let content: string;
|
|
17
|
+
try {
|
|
18
|
+
content = await readFile(path, 'utf-8');
|
|
19
|
+
} catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const result: Record<string, string> = {};
|
|
24
|
+
for (const raw of content.split('\n')) {
|
|
25
|
+
const line = raw.trim();
|
|
26
|
+
if (line === '' || line.startsWith('#')) continue;
|
|
27
|
+
const eqIdx = raw.indexOf('=');
|
|
28
|
+
if (eqIdx === -1) continue;
|
|
29
|
+
const key = raw.slice(0, eqIdx).trim();
|
|
30
|
+
let value = raw.slice(eqIdx + 1).trim();
|
|
31
|
+
if (
|
|
32
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
33
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
34
|
+
) {
|
|
35
|
+
value = value.slice(1, -1);
|
|
36
|
+
}
|
|
37
|
+
result[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pickValue(
|
|
43
|
+
envMap: Record<string, string>,
|
|
44
|
+
key: string,
|
|
45
|
+
aliases: string[]
|
|
46
|
+
): string | undefined {
|
|
47
|
+
if (envMap[key] !== undefined && envMap[key] !== '') return envMap[key];
|
|
48
|
+
for (const alias of aliases) {
|
|
49
|
+
if (envMap[alias] !== undefined && envMap[alias] !== '') return envMap[alias];
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export default {
|
|
55
|
+
async execute(_args: Record<string, unknown>, opts: Record<string, unknown>): Promise<void> {
|
|
56
|
+
const runtime = getVibesRuntime();
|
|
57
|
+
const ui = (await runtime.context('cli/ui')) as UIContext;
|
|
58
|
+
const options = opts as {
|
|
59
|
+
environment?: string;
|
|
60
|
+
to?: string;
|
|
61
|
+
source?: string;
|
|
62
|
+
dryRun?: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const { name: envName, tier: envTier } = await resolveSecretsEnvironment(
|
|
66
|
+
ui,
|
|
67
|
+
options.environment
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const sourceFilter = options.source;
|
|
71
|
+
if (sourceFilter && !canonicalImportManifest.sources[sourceFilter]) {
|
|
72
|
+
ui.error(`Unknown source: ${sourceFilter}. Known: ${Object.keys(canonicalImportManifest.sources).join(', ')}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const repoRoot = process.cwd();
|
|
77
|
+
const sourceContents = new Map<string, Record<string, string>>();
|
|
78
|
+
const manualSources = new Set<string>();
|
|
79
|
+
for (const [id, src] of Object.entries(canonicalImportManifest.sources)) {
|
|
80
|
+
if (sourceFilter && id !== sourceFilter) continue;
|
|
81
|
+
if (!src.path) {
|
|
82
|
+
manualSources.add(id);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
sourceContents.set(id, await readEnvFile(resolve(repoRoot, src.path)));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const found: Record<string, string> = {};
|
|
89
|
+
const missingRequired: string[] = [];
|
|
90
|
+
const missingOptional: string[] = [];
|
|
91
|
+
const manualEntries: { key: string; required: boolean }[] = [];
|
|
92
|
+
|
|
93
|
+
for (const entry of canonicalImportManifest.secrets) {
|
|
94
|
+
if (sourceFilter && entry.source !== sourceFilter) continue;
|
|
95
|
+
if (manualSources.has(entry.source)) {
|
|
96
|
+
manualEntries.push({ key: entry.key, required: entry.required });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const sourceMap = sourceContents.get(entry.source);
|
|
100
|
+
if (!sourceMap) {
|
|
101
|
+
if (entry.required) missingRequired.push(entry.key);
|
|
102
|
+
else missingOptional.push(entry.key);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const value = pickValue(sourceMap, entry.key, entry.aliases);
|
|
106
|
+
if (value === undefined) {
|
|
107
|
+
if (entry.required) missingRequired.push(entry.key);
|
|
108
|
+
else missingOptional.push(entry.key);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
found[entry.key] = value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const foundCount = Object.keys(found).length;
|
|
115
|
+
|
|
116
|
+
if (options.dryRun) {
|
|
117
|
+
ui.log(`\nDry run: would import ${foundCount} secret(s) for ${envName}\n`);
|
|
118
|
+
for (const key of Object.keys(found).sort()) ui.log(` + ${key}`);
|
|
119
|
+
if (missingRequired.length) {
|
|
120
|
+
ui.log(`\n ${missingRequired.length} required secret(s) not found:`);
|
|
121
|
+
for (const key of missingRequired) ui.log(` ! ${key}`);
|
|
122
|
+
}
|
|
123
|
+
if (missingOptional.length) {
|
|
124
|
+
ui.log(`\n ${missingOptional.length} optional secret(s) not found:`);
|
|
125
|
+
for (const key of missingOptional) ui.log(` ? ${key}`);
|
|
126
|
+
}
|
|
127
|
+
if (manualEntries.length) {
|
|
128
|
+
ui.log(`\n ${manualEntries.length} manual entry/entries (set via 'vibes secrets set'):`);
|
|
129
|
+
for (const { key, required } of manualEntries) {
|
|
130
|
+
ui.log(` m ${key} (${required ? 'required' : 'optional'})`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
ui.log('');
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (foundCount === 0) {
|
|
138
|
+
ui.warn('No secrets found in any source file. Nothing to import.');
|
|
139
|
+
if (missingRequired.length > 0) process.exit(1);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const descriptors = runtime.assets('secrets/store').descriptors() as SecretsStoreDescriptor[];
|
|
144
|
+
const targetId = options.to ?? 'encrypted-local';
|
|
145
|
+
const target = descriptors.find((d) => d.id === targetId)
|
|
146
|
+
?? descriptors.filter((d) => d.tiers.includes(envTier) && d.backend === 'encrypted-local')[0];
|
|
147
|
+
|
|
148
|
+
if (!target) {
|
|
149
|
+
ui.error(`No backend available with id [${targetId}] for tier ${envTier}`);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const impl = (await runtime
|
|
154
|
+
.query('secrets/store')
|
|
155
|
+
.withId(target.id)
|
|
156
|
+
.resolve()) as SecretsStoreImplementation;
|
|
157
|
+
await impl.setAll(envName, found);
|
|
158
|
+
|
|
159
|
+
ui.success(`Imported ${foundCount} secret(s) into [${target.id}] for ${envName}`);
|
|
160
|
+
|
|
161
|
+
const missingManualRequired = manualEntries.filter((e) => e.required).map((e) => e.key);
|
|
162
|
+
if (manualEntries.length > 0) {
|
|
163
|
+
ui.info(
|
|
164
|
+
`${manualEntries.length} manual entry/entries — set via 'vibes secrets set <KEY>' (${
|
|
165
|
+
missingManualRequired.length
|
|
166
|
+
} required)`
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (missingRequired.length > 0) {
|
|
171
|
+
ui.warn(`${missingRequired.length} required secret(s) missing from sources: ${missingRequired.join(', ')}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
if (missingOptional.length > 0) {
|
|
175
|
+
ui.info(`${missingOptional.length} optional secret(s) skipped: ${missingOptional.join(', ')}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CLICommandAssetDescriptor } from '@vibesdotdev/cli/schemas/types';
|
|
2
|
+
|
|
3
|
+
const descriptor: CLICommandAssetDescriptor = {
|
|
4
|
+
kind: 'cli/command',
|
|
5
|
+
id: 'secrets.list',
|
|
6
|
+
name: 'list',
|
|
7
|
+
description: 'List secrets for the current environment',
|
|
8
|
+
group: 'secrets',
|
|
9
|
+
options: [
|
|
10
|
+
{ flags: '--environment <name>', description: 'Target environment (default: current)' },
|
|
11
|
+
{ flags: '--backend <id>', description: 'Filter to a specific backend' },
|
|
12
|
+
|
|
13
|
+
{ flags: '--json', description: 'Output as JSON' }
|
|
14
|
+
],
|
|
15
|
+
surfaces: ['cli'],
|
|
16
|
+
hardware: ['consumer'],
|
|
17
|
+
enabled: true,
|
|
18
|
+
order: 10
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default descriptor;
|