Merge pull request #395 from pikasTech/ymalops-r3
YAML-first R3:非 Sub2API helper 去重与 WeChat archive 最小薄化
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, isAbsolute, join } from "node:path";
|
||||
@@ -6,6 +6,7 @@ import type { UniDeskConfig } from "./config";
|
||||
import { repoRoot, rootPath } from "./config";
|
||||
import { startJob } from "./jobs";
|
||||
import { runSshCommandCapture, type SshCaptureResult } from "./ssh";
|
||||
import { compactText, fingerprintEnvValues as fingerprintValues, parseEnvFile, parseJsonOutput } from "./platform-infra-ops-library";
|
||||
|
||||
const defaultConfigPath = "config/platform-db/postgres-pk01.yaml";
|
||||
const managedHbaStart = "# BEGIN unidesk managed pk01-platform-postgres";
|
||||
@@ -1114,27 +1115,6 @@ function secretRoot(pg: PostgresHostConfig): string {
|
||||
return isAbsolute(pg.secrets.root) ? pg.secrets.root : rootPath(pg.secrets.root);
|
||||
}
|
||||
|
||||
function parseEnvFile(text: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/u)) {
|
||||
const line = rawLine.trim();
|
||||
if (line.length === 0 || line.startsWith("#")) continue;
|
||||
const eq = line.indexOf("=");
|
||||
if (eq <= 0) continue;
|
||||
const key = line.slice(0, eq).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue;
|
||||
result[key] = unquoteEnvValue(line.slice(eq + 1).trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function unquoteEnvValue(value: string): string {
|
||||
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function writeEnvFile(path: string, values: Record<string, string>): void {
|
||||
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
||||
const lines = Object.keys(values)
|
||||
@@ -1149,19 +1129,6 @@ function quoteEnv(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
function compactText(value: string): string {
|
||||
return value.replace(/\s+/gu, " ").trim().slice(0, 500);
|
||||
}
|
||||
|
||||
function fingerprintValues(values: Record<string, string>, keys: string[]): string {
|
||||
const material = keys
|
||||
.slice()
|
||||
.sort()
|
||||
.map((key) => `${key}=${values[key] ?? ""}`)
|
||||
.join("\n");
|
||||
return `sha256:${createHash("sha256").update(material).digest("hex")}`;
|
||||
}
|
||||
|
||||
function secretSummary(secrets: SecretInspection): Record<string, unknown> {
|
||||
return {
|
||||
ok: secrets.ok,
|
||||
@@ -2008,20 +1975,6 @@ function sqlStringList(values: string[]): string {
|
||||
return values.map((value) => `'${value.replaceAll("'", "''")}'`).join(", ");
|
||||
}
|
||||
|
||||
function parseJsonOutput(stdout: string): Record<string, unknown> | null {
|
||||
const trimmed = stdout.trim();
|
||||
if (trimmed.length === 0) return null;
|
||||
const start = trimmed.indexOf("{");
|
||||
const end = trimmed.lastIndexOf("}");
|
||||
if (start === -1 || end === -1 || end <= start) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed.slice(start, end + 1)) as unknown;
|
||||
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed as Record<string, unknown> : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function compactCapture(result: SshCaptureResult, options: { full?: boolean } = {}): Record<string, unknown> {
|
||||
const full = options.full ?? false;
|
||||
return {
|
||||
|
||||
@@ -72,6 +72,38 @@ export function fingerprintValues(values: Record<string, string>, keys: string[]
|
||||
return `sha256:${hash.digest("hex")}`;
|
||||
}
|
||||
|
||||
export function fingerprintEnvValues(values: Record<string, string>, keys: string[]): string {
|
||||
const material = keys
|
||||
.slice()
|
||||
.sort()
|
||||
.map((key) => `${key}=${values[key] ?? ""}`)
|
||||
.join("\n");
|
||||
return `sha256:${createHash("sha256").update(material).digest("hex")}`;
|
||||
}
|
||||
|
||||
export function parseEnvFile(text: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/u)) {
|
||||
const line = rawLine.trim();
|
||||
if (line.length === 0 || line.startsWith("#")) continue;
|
||||
const eq = line.indexOf("=");
|
||||
if (eq <= 0) continue;
|
||||
const key = line.slice(0, eq).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue;
|
||||
result[key] = unquoteEnvValue(line.slice(eq + 1).trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function unquoteEnvValue(value: string): string {
|
||||
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) return value.slice(1, -1);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function compactText(value: string, maxChars = 500): string {
|
||||
return value.replace(/\s+/gu, " ").trim().slice(0, maxChars);
|
||||
}
|
||||
|
||||
export function shQuote(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
normalizeRemotePath,
|
||||
numberField,
|
||||
optionalStringField,
|
||||
parseEnvFile,
|
||||
parseOpsApplyOptions,
|
||||
parseOpsCommonOptions,
|
||||
readYamlRecord,
|
||||
@@ -2467,22 +2468,3 @@ function readArchiveCallbackToken(config: WechatArchiveConfig): { sourceRef: str
|
||||
fingerprint: fingerprintValues({ [config.archiveCallback.tokenKey]: value }, [config.archiveCallback.tokenKey]),
|
||||
};
|
||||
}
|
||||
|
||||
function parseEnvFile(text: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/u)) {
|
||||
const line = rawLine.trim();
|
||||
if (line.length === 0 || line.startsWith("#")) continue;
|
||||
const eq = line.indexOf("=");
|
||||
if (eq <= 0) continue;
|
||||
const key = line.slice(0, eq).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue;
|
||||
result[key] = unquoteEnvValue(line.slice(eq + 1).trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function unquoteEnvValue(value: string): string {
|
||||
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) return value.slice(1, -1);
|
||||
return value;
|
||||
}
|
||||
|
||||
+3
-19
@@ -12,11 +12,14 @@ import {
|
||||
yamlIntegerField,
|
||||
yamlKubernetesNameField,
|
||||
yamlObjectField,
|
||||
parseEnvFile,
|
||||
yamlRecord,
|
||||
yamlStringArrayField,
|
||||
yamlStringField,
|
||||
} from "./platform-infra-ops-library";
|
||||
|
||||
export { parseEnvFile } from "./platform-infra-ops-library";
|
||||
|
||||
const defaultConfigPath = "config/secrets-distribution.yaml";
|
||||
const fieldManager = "unidesk-secret-distribution";
|
||||
|
||||
@@ -727,25 +730,6 @@ export function readEnvSourceFile(params: { root: string; sourceRef: string; mis
|
||||
};
|
||||
}
|
||||
|
||||
export function parseEnvFile(text: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/u)) {
|
||||
const line = rawLine.trim();
|
||||
if (line.length === 0 || line.startsWith("#")) continue;
|
||||
const eq = line.indexOf("=");
|
||||
if (eq <= 0) continue;
|
||||
const key = line.slice(0, eq).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key)) continue;
|
||||
result[key] = unquoteEnvValue(line.slice(eq + 1).trim());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function unquoteEnvValue(value: string): string {
|
||||
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith("\"") && value.endsWith("\""))) return value.slice(1, -1);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function requiredEnvValue(values: Record<string, string>, key: string, sourceRef: string): string {
|
||||
const value = values[key];
|
||||
if (value === undefined || value.length === 0) throw new Error(`${sourceRef} is missing required key ${key}`);
|
||||
|
||||
Reference in New Issue
Block a user