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
188 lines
6.3 KiB
TypeScript
188 lines
6.3 KiB
TypeScript
#!/usr/bin/env -S npx --yes tsx
|
|
// =============================================================================
|
|
// init.ts — Initialize a data store from a YAML definition
|
|
//
|
|
// Part of: .opencode/skills/templating-vault/scripts/
|
|
//
|
|
// Creates or validates a data directory from a YAML definition file.
|
|
// Handles three modes for existing stores: ignore, validate, fix.
|
|
//
|
|
// USAGE:
|
|
// init.ts --source <definition.yaml> --target <data_dir>
|
|
// [--on-exists ignore|validate|fix]
|
|
//
|
|
// DEFINITION FORMAT:
|
|
// defaults:
|
|
// literal: true
|
|
// type: string
|
|
// keys:
|
|
// credentials.forgejo_pat:
|
|
// literal: true
|
|
// write_once: true
|
|
// type: hex
|
|
// description: "Primary bot Forgejo PAT"
|
|
// value: "preset_value" # optional preset value
|
|
// config.max_workers:
|
|
// literal: false
|
|
// type: positive_integer
|
|
// on_absent: "4"
|
|
//
|
|
// OUTPUT (stdout): JSON result
|
|
// =============================================================================
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import yaml from 'js-yaml';
|
|
import {
|
|
parseCommonFlags, getFlag, output, log, atomicWrite,
|
|
} from './api.ts';
|
|
|
|
interface KeyDefinition {
|
|
literal?: boolean;
|
|
readonly?: boolean;
|
|
write_once?: boolean;
|
|
type?: string;
|
|
validate?: { regex?: string; message?: string };
|
|
template?: boolean;
|
|
on_absent?: string | number;
|
|
description?: string;
|
|
value?: string; // optional preset value
|
|
owner?: string;
|
|
}
|
|
|
|
interface StoreDefinition {
|
|
defaults?: Record<string, unknown>;
|
|
keys?: Record<string, KeyDefinition>;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const { rest } = parseCommonFlags(process.argv);
|
|
|
|
const source = getFlag(rest, '--source');
|
|
const target = getFlag(rest, '--target');
|
|
const onExists = (getFlag(rest, '--on-exists') ?? 'ignore') as 'ignore' | 'validate' | 'fix';
|
|
|
|
if (!source) {
|
|
output({ initialized: false, error: '--source is required' });
|
|
process.exit(1);
|
|
}
|
|
if (!target) {
|
|
output({ initialized: false, error: '--target is required' });
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!fs.existsSync(source)) {
|
|
output({ initialized: false, error: `Source file "${source}" not found` });
|
|
process.exit(1);
|
|
}
|
|
|
|
const definition = yaml.load(fs.readFileSync(source, 'utf-8')) as StoreDefinition;
|
|
if (!definition) {
|
|
output({ initialized: false, error: 'Empty or invalid YAML definition' });
|
|
process.exit(1);
|
|
}
|
|
|
|
const targetExists = fs.existsSync(target);
|
|
|
|
if (targetExists && onExists === 'ignore') {
|
|
output({ initialized: true, action: 'ignored', message: 'Store already exists' });
|
|
return;
|
|
}
|
|
|
|
// Create target directory
|
|
fs.mkdirSync(target, { recursive: true });
|
|
|
|
const actions: string[] = [];
|
|
const errors: string[] = [];
|
|
|
|
// Write .store.yaml with defaults
|
|
if (definition.defaults) {
|
|
const storeYamlPath = path.join(target, '.store.yaml');
|
|
if (!targetExists || onExists === 'fix' || !fs.existsSync(storeYamlPath)) {
|
|
atomicWrite(storeYamlPath, yaml.dump({ defaults: definition.defaults }, { lineWidth: -1 }));
|
|
actions.push('wrote .store.yaml');
|
|
} else if (onExists === 'validate') {
|
|
// Check if existing .store.yaml matches
|
|
const existing = fs.existsSync(storeYamlPath)
|
|
? yaml.load(fs.readFileSync(storeYamlPath, 'utf-8'))
|
|
: null;
|
|
if (JSON.stringify(existing) !== JSON.stringify({ defaults: definition.defaults })) {
|
|
errors.push('.store.yaml diverges from definition');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process keys
|
|
if (definition.keys) {
|
|
for (const [keyPath, keyDef] of Object.entries(definition.keys)) {
|
|
const segments = keyPath.split('.');
|
|
const keyDir = path.join(target, ...segments);
|
|
|
|
// Create directory
|
|
fs.mkdirSync(keyDir, { recursive: true });
|
|
|
|
// Write meta.yaml
|
|
const metaPath = path.join(keyDir, 'meta.yaml');
|
|
const metaContent: Record<string, unknown> = {};
|
|
if (keyDef.literal !== undefined) metaContent.literal = keyDef.literal;
|
|
if (keyDef.readonly !== undefined) metaContent.readonly = keyDef.readonly;
|
|
if (keyDef.write_once !== undefined) metaContent.write_once = keyDef.write_once;
|
|
if (keyDef.type !== undefined) metaContent.type = keyDef.type;
|
|
if (keyDef.validate !== undefined) metaContent.validate = keyDef.validate;
|
|
if (keyDef.template !== undefined) metaContent.template = keyDef.template;
|
|
if (keyDef.on_absent !== undefined) metaContent.on_absent = keyDef.on_absent;
|
|
if (keyDef.description !== undefined) metaContent.description = keyDef.description;
|
|
if (keyDef.owner !== undefined) metaContent.owner = keyDef.owner;
|
|
|
|
if (Object.keys(metaContent).length > 0) {
|
|
if (!targetExists || onExists === 'fix' || !fs.existsSync(metaPath)) {
|
|
atomicWrite(metaPath, yaml.dump(metaContent, { lineWidth: -1 }));
|
|
actions.push(`wrote meta for ${keyPath}`);
|
|
} else if (onExists === 'validate') {
|
|
const existing = fs.existsSync(metaPath)
|
|
? yaml.load(fs.readFileSync(metaPath, 'utf-8'))
|
|
: null;
|
|
if (JSON.stringify(existing) !== JSON.stringify(metaContent)) {
|
|
errors.push(`${keyPath}: meta.yaml diverges from definition`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write preset value if defined
|
|
if (keyDef.value !== undefined) {
|
|
const valuePath = path.join(keyDir, 'value');
|
|
if (!targetExists || onExists === 'fix' || !fs.existsSync(valuePath)) {
|
|
atomicWrite(valuePath, String(keyDef.value));
|
|
actions.push(`wrote value for ${keyPath}`);
|
|
} else if (onExists === 'validate') {
|
|
const existing = fs.existsSync(valuePath)
|
|
? fs.readFileSync(valuePath, 'utf-8')
|
|
: null;
|
|
if (existing !== String(keyDef.value)) {
|
|
errors.push(`${keyPath}: value diverges from definition`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (onExists === 'validate' && errors.length > 0) {
|
|
output({ initialized: false, action: 'validate', errors });
|
|
process.exit(1);
|
|
}
|
|
|
|
output({
|
|
initialized: true,
|
|
action: targetExists ? onExists : 'created',
|
|
target,
|
|
actions,
|
|
});
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
main().catch((err: unknown) => {
|
|
output({ initialized: false, error: String(err instanceof Error ? err.message : err) });
|
|
process.exit(1);
|
|
});
|
|
}
|