1885990081
CI / build (push) Waiting to run
CI / docker (push) Blocked by required conditions
CI / helm (push) Waiting to run
CI / push-validation (push) Waiting to run
CI / status-check (push) Blocked by required conditions
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
CI / security (push) Waiting to run
CI / quality (push) Waiting to run
CI / unit_tests (push) Waiting to run
CI / integration_tests (push) Waiting to run
CI / e2e_tests (push) Waiting to run
CI / coverage (push) Blocked by required conditions
CI / benchmark-publish (push) Waiting to run
134 lines
4.5 KiB
TypeScript
134 lines
4.5 KiB
TypeScript
#!/usr/bin/env -S npx --yes tsx
|
|
// =============================================================================
|
|
// set.ts — Store a value in the vault
|
|
//
|
|
// Part of: .opencode/skills/templating-vault/scripts/
|
|
//
|
|
// Stores a value at a dot-separated key. Enforces:
|
|
// - Literal keys can only be set via extract.ts (rejected here)
|
|
// - Write-once keys cannot be overwritten
|
|
// - Type and regex validation
|
|
// - Owner-based access control
|
|
//
|
|
// USAGE:
|
|
// set.ts --key <key> --value <value> [--owner <id>] [--write-once]
|
|
// [--literal] [--force-literal] [--owner-overwrite]
|
|
// [--type <type>] [--description <desc>]
|
|
// [--config <path>] [--store-dir <path>]
|
|
//
|
|
// OUTPUT (stdout): JSON result
|
|
// =============================================================================
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
parseCommonFlags, getFlag, hasFlag, output, log,
|
|
loadConfig, resolveStore, loadKeyMeta, saveKeyMeta,
|
|
readValue, writeValue, hasValue, validateValue,
|
|
type KeyMeta,
|
|
} from './api.ts';
|
|
|
|
async function main(): Promise<void> {
|
|
const { config: cfgPath, storeDirs, rest } = parseCommonFlags(process.argv);
|
|
const config = loadConfig(cfgPath, storeDirs.length > 0 ? storeDirs : undefined);
|
|
|
|
const key = getFlag(rest, '--key');
|
|
const value = getFlag(rest, '--value');
|
|
const owner = getFlag(rest, '--owner');
|
|
const writeOnce = hasFlag(rest, '--write-once');
|
|
const forceLiteral = hasFlag(rest, '--force-literal'); // internal: used by extract.ts
|
|
const setLiteralFlag = hasFlag(rest, '--literal'); // set literal in metadata
|
|
const ownerOverwrite = hasFlag(rest, '--owner-overwrite');
|
|
const typeFlag = getFlag(rest, '--type');
|
|
const description = getFlag(rest, '--description');
|
|
|
|
if (!key) {
|
|
output({ stored: false, error: '--key is required' });
|
|
process.exit(1);
|
|
}
|
|
if (value === undefined) {
|
|
output({ stored: false, error: '--value is required' });
|
|
process.exit(1);
|
|
}
|
|
|
|
// Check access level
|
|
const resolved = resolveStore(config, key);
|
|
if (resolved.mapping.access === 'readonly') {
|
|
output({ stored: false, error: `Store for prefix "${resolved.mapping.prefix}" is readonly` });
|
|
process.exit(1);
|
|
}
|
|
|
|
// Load existing metadata
|
|
const meta = loadKeyMeta(config, key);
|
|
|
|
// Literal enforcement: reject direct set on literal keys unless:
|
|
// - --force-literal is set (internal flag used by extract.ts)
|
|
// - Store access is 'admin' (bootstrap/setup mode)
|
|
if (meta.literal && !forceLiteral && resolved.mapping.access !== 'admin') {
|
|
output({
|
|
stored: false,
|
|
error: `Cannot directly set literal variable "${key}". Use extract.ts with a regex instead, or use admin access to bootstrap.`,
|
|
key,
|
|
literal: true,
|
|
});
|
|
process.exit(1);
|
|
}
|
|
|
|
// Write-once enforcement
|
|
if (meta.write_once && hasValue(config, key)) {
|
|
if (ownerOverwrite && meta.owner && meta.owner === owner) {
|
|
// Owner can overwrite write-once keys
|
|
log(`Owner-overwrite: updating write-once key "${key}"`);
|
|
} else {
|
|
output({
|
|
stored: false,
|
|
error: `Key "${key}" is write-once and already has a value`,
|
|
key,
|
|
write_once: true,
|
|
});
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Readonly enforcement
|
|
if (meta.readonly && hasValue(config, key) && resolved.mapping.access !== 'admin') {
|
|
output({
|
|
stored: false,
|
|
error: `Key "${key}" is readonly`,
|
|
key,
|
|
readonly: true,
|
|
});
|
|
process.exit(1);
|
|
}
|
|
|
|
// Validation
|
|
const validationError = validateValue(value, meta);
|
|
if (validationError) {
|
|
output({ stored: false, error: `Validation failed for "${key}": ${validationError}`, key });
|
|
process.exit(1);
|
|
}
|
|
|
|
// Write value
|
|
writeValue(config, key, value);
|
|
|
|
// Update metadata if flags provided
|
|
const updatedMeta: KeyMeta = { ...meta };
|
|
if (writeOnce) updatedMeta.write_once = true;
|
|
if (setLiteralFlag) updatedMeta.literal = true;
|
|
if (owner) updatedMeta.owner = owner;
|
|
if (typeFlag) updatedMeta.type = typeFlag;
|
|
if (description) updatedMeta.description = description;
|
|
|
|
// Only save meta if something changed from defaults or explicitly provided
|
|
if (writeOnce || setLiteralFlag || owner || typeFlag || description) {
|
|
saveKeyMeta(config, key, updatedMeta);
|
|
}
|
|
|
|
output({ stored: true, key, write_once: updatedMeta.write_once ?? false });
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
main().catch((err: unknown) => {
|
|
output({ stored: false, error: String(err instanceof Error ? err.message : err) });
|
|
process.exit(1);
|
|
});
|
|
}
|