Skip to content

Commit f0b1a7f

Browse files
authored
Merge pull request #441 from OkToPals/grantfox-me-wave-3
complete implementation
2 parents c0cb6d4 + f009d5a commit f0b1a7f

14 files changed

Lines changed: 507 additions & 122 deletions

File tree

cli/src/commands/cancel.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as readline from "readline";
1010
import { Command } from "commander";
1111
import type { InvoiceSummary, CancelResult } from "./cancel-types.js";
1212
import { validatePendingState, formatConfirmMessage } from "./cancel-helpers.js";
13+
import { formatOutput, formatError, isJsonMode } from "../format.js";
1314

1415
export type InvoiceFetcher = (id: string) => Promise<InvoiceSummary>;
1516
export type CancelExecutor = (id: string) => Promise<CancelResult>;
@@ -43,23 +44,29 @@ export function makeCancelCommand(
4344
.requiredOption("--id <invoice-id>", "Invoice ID to cancel")
4445
.option("--yes", "Skip confirmation prompt")
4546
.action(async (opts: { id: string; yes?: boolean }) => {
47+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
48+
const json = isJsonMode(parentOpts);
49+
4650
try {
4751
const invoice = await fetchInvoice(opts.id);
4852
validatePendingState(invoice);
4953

5054
if (!opts.yes) {
5155
const confirmed = await confirm(formatConfirmMessage(invoice));
5256
if (!confirmed) {
53-
console.log("Cancelled — no changes made.");
57+
formatOutput({ aborted: true, message: "no changes made" }, json, () => {
58+
console.log("Cancelled — no changes made.");
59+
});
5460
return;
5561
}
5662
}
5763

5864
const result = await cancelExecutor(opts.id);
59-
console.log(`Invoice #${result.invoiceId} cancelled. TX: ${result.txHash}`);
65+
formatOutput(result, json, () => {
66+
console.log(`Invoice #${result.invoiceId} cancelled. TX: ${result.txHash}`);
67+
});
6068
} catch (err) {
61-
console.error(`Error: ${(err as Error).message}`);
62-
process.exit(1);
69+
formatError((err as Error).message, "CANCEL_ERROR", json);
6370
}
6471
});
6572

cli/src/commands/config.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
resetConfig,
1717
setConfigValue,
1818
} from "../config.js";
19+
import { formatOutput, formatError, isJsonMode } from "../format.js";
1920

2021
export function makeConfigCommand(): Command {
2122
const cmd = new Command("config").description(
@@ -29,12 +30,16 @@ export function makeConfigCommand(): Command {
2930
"Set a config value (keys: network, rpcUrl, defaultProfile)"
3031
)
3132
.action((key: string, value: string) => {
33+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
34+
const json = isJsonMode(parentOpts);
35+
3236
try {
3337
setConfigValue(key, value);
34-
console.log(`✓ Set ${key} = ${value}`);
38+
formatOutput({ key, value, set: true }, json, () => {
39+
console.log(`✓ Set ${key} = ${value}`);
40+
});
3541
} catch (err) {
36-
console.error(`Error: ${(err as Error).message}`);
37-
process.exit(1);
42+
formatError((err as Error).message, "CONFIG_ERROR", json);
3843
}
3944
});
4045

@@ -43,37 +48,47 @@ export function makeConfigCommand(): Command {
4348
.command("get <key>")
4449
.description("Get a single config value")
4550
.action((key: string) => {
51+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
52+
const json = isJsonMode(parentOpts);
53+
4654
const val = getConfigValue(key as "network" | "rpcUrl" | "defaultProfile");
4755
if (val === undefined) {
48-
console.error(`Key "${key}" not found in config.`);
49-
process.exit(1);
56+
formatError(`Key "${key}" not found in config.`, "KEY_NOT_FOUND", json);
5057
}
51-
console.log(val);
58+
formatOutput({ key, value: val }, json, () => {
59+
console.log(val);
60+
});
5261
});
5362

5463
// iln config list
5564
cmd
5665
.command("list")
5766
.description("Show all current config values")
58-
.option("--json", "Output as JSON")
67+
.option("--json", "Output as JSON (also available as global flag)")
5968
.action((opts: { json?: boolean }) => {
69+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
70+
const json = opts.json || isJsonMode(parentOpts);
71+
6072
const cfg = loadConfig();
61-
if (opts.json) {
62-
console.log(JSON.stringify(cfg, null, 2));
63-
} else {
73+
formatOutput(cfg, json, () => {
6474
for (const [k, v] of Object.entries(cfg)) {
6575
console.log(`${k}: ${v}`);
6676
}
67-
}
77+
});
6878
});
6979

7080
// iln config reset
7181
cmd
7282
.command("reset")
7383
.description("Restore all config values to defaults")
7484
.action(() => {
85+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
86+
const json = isJsonMode(parentOpts);
87+
7588
resetConfig();
76-
console.log("✓ Config reset to defaults.");
89+
formatOutput({ reset: true }, json, () => {
90+
console.log("✓ Config reset to defaults.");
91+
});
7792
});
7893

7994
return cmd;

cli/src/commands/export.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212
import fs from "fs";
1313
import { Command } from "commander";
14+
import { formatError, isJsonMode } from "../format.js";
1415

1516
export interface InvoiceRow {
1617
id: string;
@@ -100,6 +101,9 @@ export function makeExportCommand(
100101
from?: string;
101102
to?: string;
102103
}) => {
104+
const rootOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
105+
const json = isJsonMode(rootOpts);
106+
103107
try {
104108
let rows = await fetchInvoices({
105109
submitter: opts.submitter,
@@ -118,8 +122,7 @@ export function makeExportCommand(
118122
process.stdout.write(content + "\n");
119123
}
120124
} catch (err) {
121-
console.error(`Export failed: ${(err as Error).message}`);
122-
process.exit(1);
125+
formatError((err as Error).message, "EXPORT_ERROR", json);
123126
}
124127
}
125128
);

cli/src/commands/fund.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import * as readline from "readline";
1010
import { Command } from "commander";
1111
import type { MarketplaceListing, FundResult } from "./marketplace-types.js";
12+
import { formatOutput, formatError, isJsonMode } from "../format.js";
1213

1314
export type InvoiceFetcher = (id: string) => Promise<MarketplaceListing>;
1415
export type FundExecutor = (id: string) => Promise<FundResult>;
@@ -42,23 +43,29 @@ export function makeFundCommand(
4243
.requiredOption("--id <invoice-id>", "Invoice ID to fund")
4344
.option("--yes", "Skip confirmation prompt")
4445
.action(async (opts: { id: string; yes?: boolean }) => {
46+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
47+
const json = isJsonMode(parentOpts);
48+
4549
try {
4650
const invoice = await fetchInvoice(opts.id);
4751

4852
if (!opts.yes) {
4953
const msg = `Fund invoice #${invoice.id} (${invoice.amount} ${invoice.token}, ${invoice.yieldPct}% yield)? [y/N]`;
5054
const confirmed = await confirm(msg);
5155
if (!confirmed) {
52-
console.log("Aborted — invoice not funded.");
56+
formatOutput({ aborted: true, message: "invoice not funded" }, json, () => {
57+
console.log("Aborted — invoice not funded.");
58+
});
5359
return;
5460
}
5561
}
5662

5763
const result = await executeFund(opts.id);
58-
console.log(`Funded invoice #${result.invoiceId}. TX: ${result.txHash}`);
64+
formatOutput(result, json, () => {
65+
console.log(`Funded invoice #${result.invoiceId}. TX: ${result.txHash}`);
66+
});
5967
} catch (err) {
60-
console.error(`Fund error: ${(err as Error).message}`);
61-
process.exit(1);
68+
formatError((err as Error).message, "FUND_ERROR", json);
6269
}
6370
});
6471

cli/src/commands/marketplace.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
import { Command } from "commander";
1111
import type { MarketplaceListing, MarketplaceOptions } from "./marketplace-types.js";
12+
import { formatOutput, formatError, isJsonMode } from "../format.js";
1213

1314
export type ListingsFetcher = () => Promise<MarketplaceListing[]>;
1415

@@ -73,14 +74,18 @@ export function makeMarketplaceCommand(
7374
.option("--sort <yield|amount|due>", "Sort order", "yield")
7475
.option("--filter <key=value>", "Filter (e.g. token=USDC)")
7576
.action(async (opts: { sort?: string; filter?: string }) => {
77+
const parentOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
78+
const json = isJsonMode(parentOpts);
79+
7680
try {
7781
let listings = await fetchListings();
7882
listings = applyFilter(listings, opts.filter);
7983
listings = applySort(listings, opts.sort as MarketplaceOptions["sort"]);
80-
printListingsTable(listings);
84+
formatOutput(listings, json, () => {
85+
printListingsTable(listings);
86+
});
8187
} catch (err) {
82-
console.error(`Marketplace error: ${(err as Error).message}`);
83-
process.exit(1);
88+
formatError((err as Error).message, "MARKETPLACE_ERROR", json);
8489
}
8590
});
8691

cli/src/commands/pause.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Command } from "commander";
22
import * as readline from "readline";
33
import { resolveProfile } from "../config.js";
4+
import { formatOutput, formatError, isJsonMode } from "../format.js";
45

56
export interface PauseResult {
67
txHash: string;
@@ -50,20 +51,23 @@ export function makePauseCommand(
5051
cmd
5152
.option("--yes", "Skip confirmation prompt")
5253
.action(async (opts: { yes?: boolean }) => {
54+
const rootOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
55+
const json = isJsonMode(rootOpts);
56+
5357
try {
5458
// Require admin authentication
55-
const parentOpts = cmd.parent?.opts() as { profile?: string } | undefined;
56-
const profile = resolveProfile(parentOpts?.profile);
59+
const profile = resolveProfile(rootOpts?.profile as string | undefined);
5760
if (!profile) {
58-
console.error("Error: No connected wallet found. Run: iln wallet generate");
59-
process.exit(1);
61+
formatError("No connected wallet found. Run: iln wallet generate", "NO_WALLET", json);
6062
return;
6163
}
6264

6365
// Check current state
6466
const isCurrentlyPaused = await stateChecker();
6567
if (isCurrentlyPaused) {
66-
console.log("Contract is already paused. No changes made.");
68+
formatOutput({ paused: true, message: "contract is already paused" }, json, () => {
69+
console.log("Contract is already paused. No changes made.");
70+
});
6771
return;
6872
}
6973

@@ -72,7 +76,9 @@ export function makePauseCommand(
7276
const msg = "Confirm pause of contract? [y/N]";
7377
const confirmed = await confirm(msg);
7478
if (!confirmed) {
75-
console.log("Aborted — contract not paused.");
79+
formatOutput({ aborted: true, message: "contract not paused" }, json, () => {
80+
console.log("Aborted — contract not paused.");
81+
});
7682
return;
7783
}
7884
}
@@ -81,11 +87,12 @@ export function makePauseCommand(
8187
// Update defaultState if using defaultStateChecker
8288
defaultState = true;
8389

84-
console.log(`Contract paused. TX: ${result.txHash}`);
85-
console.log(`Contract State: Paused`);
90+
formatOutput({ ...result, state: "Paused" }, json, () => {
91+
console.log(`Contract paused. TX: ${result.txHash}`);
92+
console.log(`Contract State: Paused`);
93+
});
8694
} catch (err) {
87-
console.error(`Error: ${(err as Error).message}`);
88-
process.exit(1);
95+
formatError((err as Error).message, "PAUSE_ERROR", json);
8996
}
9097
});
9198

@@ -102,20 +109,23 @@ export function makeUnpauseCommand(
102109
cmd
103110
.option("--yes", "Skip confirmation prompt")
104111
.action(async (opts: { yes?: boolean }) => {
112+
const rootOpts = cmd.parent?.opts() as Record<string, unknown> | undefined;
113+
const json = isJsonMode(rootOpts);
114+
105115
try {
106116
// Require admin authentication
107-
const parentOpts = cmd.parent?.opts() as { profile?: string } | undefined;
108-
const profile = resolveProfile(parentOpts?.profile);
117+
const profile = resolveProfile(rootOpts?.profile as string | undefined);
109118
if (!profile) {
110-
console.error("Error: No connected wallet found. Run: iln wallet generate");
111-
process.exit(1);
119+
formatError("No connected wallet found. Run: iln wallet generate", "NO_WALLET", json);
112120
return;
113121
}
114122

115123
// Check current state
116124
const isCurrentlyPaused = await stateChecker();
117125
if (!isCurrentlyPaused) {
118-
console.log("Contract is already unpaused. No changes made.");
126+
formatOutput({ paused: false, message: "contract is already unpaused" }, json, () => {
127+
console.log("Contract is already unpaused. No changes made.");
128+
});
119129
return;
120130
}
121131

@@ -124,7 +134,9 @@ export function makeUnpauseCommand(
124134
const msg = "Confirm unpause of contract? [y/N]";
125135
const confirmed = await confirm(msg);
126136
if (!confirmed) {
127-
console.log("Aborted — contract not unpaused.");
137+
formatOutput({ aborted: true, message: "contract not unpaused" }, json, () => {
138+
console.log("Aborted — contract not unpaused.");
139+
});
128140
return;
129141
}
130142
}
@@ -133,11 +145,12 @@ export function makeUnpauseCommand(
133145
// Update defaultState if using defaultStateChecker
134146
defaultState = false;
135147

136-
console.log(`Contract unpaused. TX: ${result.txHash}`);
137-
console.log(`Contract State: Active`);
148+
formatOutput({ ...result, state: "Active" }, json, () => {
149+
console.log(`Contract unpaused. TX: ${result.txHash}`);
150+
console.log(`Contract State: Active`);
151+
});
138152
} catch (err) {
139-
console.error(`Error: ${(err as Error).message}`);
140-
process.exit(1);
153+
formatError((err as Error).message, "UNPAUSE_ERROR", json);
141154
}
142155
});
143156

0 commit comments

Comments
 (0)