Skip to content

Commit ac136fc

Browse files
committed
(feat) add config module for persistent global json config + add default RIK contract address to default config
1 parent c91e868 commit ac136fc

2 files changed

Lines changed: 72 additions & 7 deletions

File tree

cli/src/index.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { exit } from "node:process";
55

66
import { Command } from "commander";
77
import type { Abi } from "viem";
8-
import { createPublicClient, createWalletClient, http, parseAbi, parseAbiItem, parseEther, toHex } from "viem";
8+
import { createPublicClient, createWalletClient, http, isAddress, parseAbi, parseAbiItem, parseEther, toHex } from "viem";
99
import { privateKeyToAccount } from "viem/accounts";
1010
import { foundry, baseSepolia } from "viem/chains";
1111
import { DopplerSDK } from "@whetstone-research/doppler-sdk/evm";
@@ -21,6 +21,8 @@ import { createWallet } from "./wallet/create.js";
2121
import { DEFAULT_PRIVATE_KEY_SECRET_NAME, linkWallet } from "./wallet/link.js";
2222
import { getLocalWallet, walletFilePath } from "./wallet/store.js";
2323
import { wallet } from "viem/tempo/actions";
24+
import { getConfig, type Config } from "@/utils/config.js";
25+
import { get } from "node:http";
2426

2527
const COMMAND_NAME = "fcf";
2628
const COMMAND_DESCRIPTION = "FCF CLI tool.";
@@ -190,7 +192,7 @@ function addRegisterCommand(program: Command): void {
190192
.command("register")
191193
.option("--oidc-token <token>", "GitHub's OIDC repository token")
192194
// needs to be removed in prod or be a default
193-
.requiredOption("--contract <addr>", "deployed RIK address")
195+
.option("--contract <addr>", "deployed RIK address")
194196
.action(async (opts) => {
195197
const { account, publicClient, walletClient } = clients();
196198
const oidcToken = opts.oidcToken ??
@@ -208,8 +210,12 @@ function addRegisterCommand(program: Command): void {
208210
const ownerId = BigInt(jwt.payload.repository_owner_id);
209211
if (!jwt.header.kid) die(new Error("jwt kid missing"));
210212

213+
const { rikContractAddress } = getConfig();
214+
if (!rikContractAddress && !opts.contract) die(new Error("failed to get RIK contract address"));
215+
if (!isAddress(rikContractAddress as `0x${string}`) && !opts.contract) die(new Error("invalid RIK contract address"));
216+
211217
const hash = await walletClient.writeContract({
212-
address: opts.contract as `0x${string}`,
218+
address: (opts.contract ?? rikContractAddress) as `0x${string}`,
213219
abi,
214220
functionName: "register",
215221
args: [
@@ -233,18 +239,22 @@ function addKeysSyncCommand(program: Command): void {
233239
.command("keys")
234240
.command("sync")
235241
// needs to be removed in prod or be a default
236-
.requiredOption("--contract <addr>", "deployed RIK address")
242+
.option("--contract <addr>", "deployed RIK address")
237243
.action(async (opts) => {
238244
const { account, publicClient, walletClient } = clients();
239245
const config = await fetchJson(`${GITHUB_ISSUER}/.well-known/openid-configuration`);
240246
const jwks = await fetchJson(config.jwks_uri);
241247

248+
const { rikContractAddress } = getConfig();
249+
if (!rikContractAddress && !opts.contract) die(new Error("failed to get RIK contract address"));
250+
if (!isAddress(rikContractAddress as `0x${string}`) && !opts.contract) die(new Error("invalid RIK contract address"));
251+
242252
for (const key of jwks.keys ?? []) {
243253
if (key.kty !== "RSA" || !key.kid || !key.n || !key.e) continue;
244254

245255
const kid = jwtKid(key.kid);
246256
const hash = await walletClient.writeContract({
247-
address: opts.contract as `0x${string}`,
257+
address: (opts.contract ?? rikContractAddress) as `0x${string}`,
248258
abi,
249259
functionName: "addKey",
250260
args: [kid, b64urlToHex(key.n), b64urlToHex(key.e)],
@@ -260,16 +270,20 @@ function addListCommand(program: Command): void {
260270
program
261271
.command("list")
262272
// needs to be removed in prod or be a default
263-
.requiredOption("--contract <addr>", "deployed RIK address")
273+
.option("--contract <addr>", "deployed RIK address")
264274
.option("--from-block <n>", "starting block")
265275
.action(async (opts) => {
266276
const { publicClient } = clients();
267277
const fromBlock = opts.fromBlock
268278
? BigInt(opts.fromBlock)
269279
: await publicClient.getBlockNumber() - DEFAULT_LIST_BLOCK_RANGE;
270280

281+
const { rikContractAddress } = getConfig();
282+
if (!rikContractAddress && !opts.contract) die(new Error("failed to get RIK contract address"));
283+
if (!isAddress(rikContractAddress as `0x${string}`) && !opts.contract) die(new Error("invalid RIK contract address"));
284+
271285
const logs = await publicClient.getLogs({
272-
address: opts.contract as `0x${string}`,
286+
address: (opts.contract ?? rikContractAddress) as `0x${string}`,
273287
event: RepoRegisteredEvent,
274288
fromBlock,
275289
toBlock: "latest",

cli/src/utils/config.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2+
import { homedir } from "node:os";
3+
import { dirname, join, resolve } from "node:path";
4+
5+
const RIK_CONTRACT_ADDRESS = "0xc03a52cD0EB2d5d456e64bda0557Db04608d1eac" as `0x${string}`;
6+
7+
export type Config = Record<string, unknown>;
8+
9+
let config: Config | undefined;
10+
11+
export function getConfig(): Config {
12+
if (!config) config = readConfig();
13+
return config;
14+
}
15+
16+
export function readConfig(): Config {
17+
try {
18+
const value = JSON.parse(readFileSync(configFilePath(), "utf8"));
19+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid config file");
20+
return value as Config;
21+
} catch (err: any) {
22+
if (err?.code === "ENOENT") return initNewConfig();
23+
throw err;
24+
}
25+
}
26+
27+
function initNewConfig(): Config {
28+
// define initial config shape and data
29+
const stub = {
30+
rikContractAddress: RIK_CONTRACT_ADDRESS
31+
};
32+
writeConfig(stub);
33+
return stub;
34+
}
35+
36+
export function writeConfig(value: Config): void {
37+
const filePath = configFilePath();
38+
mkdirSync(dirname(filePath), { recursive: true, mode: 0o700 });
39+
40+
const tmpPath = `${filePath}.${process.pid}.tmp`;
41+
writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
42+
renameSync(tmpPath, filePath);
43+
chmodSync(filePath, 0o600);
44+
config = value;
45+
}
46+
47+
function configFilePath(): string {
48+
if (process.env.FCF_CONFIG_HOME) return join(resolve(process.env.FCF_CONFIG_HOME), "config.json");
49+
if (process.env.XDG_CONFIG_HOME) return join(resolve(process.env.XDG_CONFIG_HOME), "fcf", "config.json");
50+
return join(homedir(), ".config", "fcf", "config.json");
51+
}

0 commit comments

Comments
 (0)