Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env tsx
|
||||
import { backfillLegacyApiKeyHashes, DEFAULT_LEGACY_API_KEY_HASH_BACKFILL_BATCH_SIZE } from "@toolctrl/db/api-keys.server";
|
||||
import type { LegacyApiKeyHashBackfillResult } from "@toolctrl/db/api-keys.server";
|
||||
|
||||
export type ApiKeyMigrationCliEnv = NodeJS.ProcessEnv & {
|
||||
API_KEY_HASH_SECRET?: string;
|
||||
JWT_SECRET?: string;
|
||||
};
|
||||
|
||||
export type ApiKeyMigrationCliDependencies = {
|
||||
backfillLegacyApiKeyHashes?: typeof backfillLegacyApiKeyHashes;
|
||||
stdout?: Pick<typeof process.stdout, "write">;
|
||||
stderr?: Pick<typeof process.stderr, "write">;
|
||||
};
|
||||
|
||||
type ApiKeyMigrationCliOptions = {
|
||||
dryRun: boolean;
|
||||
batchSize: number;
|
||||
};
|
||||
|
||||
function parseApiKeyMigrationArgs(argv: string[]): ApiKeyMigrationCliOptions {
|
||||
let dryRun = true;
|
||||
let batchSize = DEFAULT_LEGACY_API_KEY_HASH_BACKFILL_BATCH_SIZE;
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
|
||||
if (arg === "--dry-run") {
|
||||
dryRun = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--execute") {
|
||||
dryRun = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--batch-size") {
|
||||
const value = argv[index + 1];
|
||||
if (!value || value.startsWith("--")) {
|
||||
throw new Error("Invalid --batch-size: expected a positive integer.");
|
||||
}
|
||||
batchSize = parseBatchSize(value);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith("--batch-size=")) {
|
||||
batchSize = parseBatchSize(arg.slice("--batch-size=".length));
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
|
||||
return { dryRun, batchSize };
|
||||
}
|
||||
|
||||
function parseBatchSize(value: string): number {
|
||||
if (!/^\d+$/.test(value)) {
|
||||
throw new Error("Invalid --batch-size: expected a positive integer.");
|
||||
}
|
||||
|
||||
const batchSize = Number(value);
|
||||
if (!Number.isSafeInteger(batchSize) || batchSize < 1) {
|
||||
throw new Error("Invalid --batch-size: expected a positive integer.");
|
||||
}
|
||||
|
||||
return batchSize;
|
||||
}
|
||||
|
||||
function resolveHashSecret(env: ApiKeyMigrationCliEnv): string | undefined {
|
||||
return env.API_KEY_HASH_SECRET?.trim() || env.JWT_SECRET?.trim() || undefined;
|
||||
}
|
||||
|
||||
function writeLine(stream: Pick<typeof process.stdout, "write">, value: string): void {
|
||||
stream.write(`${value}\n`);
|
||||
}
|
||||
|
||||
function summarizeResult(result: LegacyApiKeyHashBackfillResult): Omit<LegacyApiKeyHashBackfillResult, "failures"> & { failureIds: string[] } {
|
||||
return {
|
||||
scanned: result.scanned,
|
||||
updated: result.updated,
|
||||
skipped: result.skipped,
|
||||
failed: result.failed,
|
||||
failureIds: result.failures.map((failure) => failure.id),
|
||||
};
|
||||
}
|
||||
|
||||
function redactCliError(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.name || "Error";
|
||||
}
|
||||
|
||||
return "UnknownError";
|
||||
}
|
||||
|
||||
export async function runApiKeyMigrationCli(
|
||||
argv: string[] = process.argv.slice(2),
|
||||
env: ApiKeyMigrationCliEnv = process.env,
|
||||
dependencies: ApiKeyMigrationCliDependencies = {},
|
||||
): Promise<number> {
|
||||
const stdout = dependencies.stdout ?? process.stdout;
|
||||
const stderr = dependencies.stderr ?? process.stderr;
|
||||
const migrate = dependencies.backfillLegacyApiKeyHashes ?? backfillLegacyApiKeyHashes;
|
||||
|
||||
let options: ApiKeyMigrationCliOptions;
|
||||
try {
|
||||
options = parseApiKeyMigrationArgs(argv);
|
||||
} catch (error) {
|
||||
writeLine(stderr, error instanceof Error ? error.message : "Invalid arguments.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
writeLine(
|
||||
stdout,
|
||||
JSON.stringify({
|
||||
mode: "dry-run",
|
||||
batchSize: options.batchSize,
|
||||
scanned: 0,
|
||||
updated: 0,
|
||||
skipped: 0,
|
||||
failed: 0,
|
||||
message: "Dry run only; no database writes performed. Use --execute to apply the backfill.",
|
||||
}),
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await migrate({ batchSize: options.batchSize, hashSecret: resolveHashSecret(env) });
|
||||
writeLine(stdout, JSON.stringify({ mode: "execute", batchSize: options.batchSize, ...summarizeResult(result) }));
|
||||
return result.failed > 0 ? 1 : 0;
|
||||
} catch (error) {
|
||||
writeLine(
|
||||
stderr,
|
||||
JSON.stringify({ mode: "execute", batchSize: options.batchSize, scanned: 0, updated: 0, skipped: 0, failed: 1, error: redactCliError(error) }),
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
runApiKeyMigrationCli()
|
||||
.then((exitCode) => {
|
||||
process.exitCode = exitCode;
|
||||
})
|
||||
.catch(() => {
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user