@aixyz/cli 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin.ts CHANGED
@@ -1,200 +1,23 @@
1
1
  #!/usr/bin/env bun
2
- import { program } from "commander";
3
- import { build } from "./build";
4
- import { dev } from "./dev";
5
- import { register } from "./register/register";
6
- import { update } from "./register/update";
2
+ import { Command } from "commander";
3
+ import { devCommand } from "./dev";
4
+ import { buildCommand } from "./build";
5
+ import { erc8004Command } from "./register";
7
6
  import pkg from "./package.json";
8
7
 
9
- function handleAction(
10
- action: (options: Record<string, unknown>) => Promise<void>,
11
- ): (options: Record<string, unknown>) => Promise<void> {
12
- return async (options) => {
13
- try {
14
- await action(options);
15
- } catch (error) {
16
- if (error instanceof Error && error.name === "ExitPromptError") {
17
- process.exit(130);
18
- }
19
- console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
20
- process.exit(1);
21
- }
22
- };
8
+ const cli = new Command();
9
+ cli.name("aixyz").description("CLI for building and deploying aixyz agents").version(pkg.version);
10
+
11
+ cli.addCommand(devCommand);
12
+ cli.addCommand(buildCommand);
13
+ cli.addCommand(erc8004Command);
14
+
15
+ try {
16
+ await cli.parseAsync();
17
+ } catch (error) {
18
+ if (error instanceof Error && error.name === "ExitPromptError") {
19
+ process.exit(130);
20
+ }
21
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
22
+ process.exit(1);
23
23
  }
24
-
25
- program.name("aixyz").description("CLI for building and deploying aixyz agents").version(pkg.version);
26
-
27
- program
28
- .command("dev")
29
- .description("Start a local development server")
30
- .option("-p, --port <port>", "Port to listen on", "3000")
31
- .action(handleAction(dev));
32
-
33
- program
34
- .command("build")
35
- .description("Build the aixyz agent")
36
- .option("--output <type>", "Output format: 'standalone' or 'vercel'")
37
- .addHelpText(
38
- "after",
39
- `
40
- Details:
41
- Bundles your aixyz agent for deployment.
42
-
43
- Default behavior (auto-detected):
44
- Bundles into a single executable file for Standalone at ./.aixyz/output/server.js
45
-
46
- With --output vercel or VERCEL=1 env:
47
- Generates Vercel Build Output API v3 structure at .vercel/output/
48
- (Automatically detected when deploying to Vercel)
49
-
50
- The build process:
51
- 1. Loads aixyz.config.ts from the current directory
52
- 2. Detects entrypoint (app/server.ts or auto-generates from app/agent.ts + app/tools/)
53
- 3. Bundles the application
54
- 4. Copies static assets from public/ (if present)
55
-
56
- Prerequisites:
57
- - An aixyz.config.ts with a default export
58
- - An entrypoint at app/server.ts, or app/agent.ts + app/tools/ for auto-generation
59
-
60
- Examples:
61
- $ aixyz build # Build standalone (default)
62
- $ aixyz build --output standalone # Build standalone explicitly
63
- $ aixyz build --output vercel # Build for Vercel deployment
64
- $ VERCEL=1 aixyz build # Auto-detected Vercel build`,
65
- )
66
- .action(handleAction(build));
67
-
68
- const erc8004 = program.command("erc-8004").description("ERC-8004 IdentityRegistry operations");
69
-
70
- erc8004
71
- .command("register")
72
- .description("Register a new agent to the ERC-8004 IdentityRegistry")
73
- .option("--url <url>", "Agent deployment URL (e.g., https://my-agent.example.com)")
74
- .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
75
- .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
76
- .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
77
- .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
78
- .option("--browser", "Use browser extension wallet (any extension)")
79
- .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
80
- .option("--out-dir <path>", "Write deployment result as JSON to the given directory")
81
- .addHelpText(
82
- "after",
83
- `
84
- Option Details:
85
- --url <url>
86
- Agent deployment URL (e.g., https://my-agent.example.com).
87
- The registration URI will be derived as <url>/_aixyz/erc-8004.json.
88
- If omitted, you will be prompted to enter the URL interactively.
89
-
90
- --chain <chain>
91
- Target chain for registration. Supported values:
92
- mainnet Ethereum mainnet (chain ID 1)
93
- sepolia Ethereum Sepolia testnet (chain ID 11155111)
94
- base-sepolia Base Sepolia testnet (chain ID 84532)
95
- localhost Local Foundry/Anvil node (chain ID 31337)
96
- If omitted, you will be prompted to select a chain interactively.
97
-
98
- --rpc-url <url>
99
- Custom RPC endpoint URL. Overrides the default RPC for the selected
100
- chain. Cannot be used with --browser since the browser wallet manages
101
- its own RPC connection.
102
-
103
- --registry <address>
104
- Contract address of the ERC-8004 IdentityRegistry. Only required for
105
- localhost, where there is no default deployment.
106
-
107
- --keystore <path>
108
- Path to an Ethereum keystore (V3) JSON file. You will be prompted for
109
- the keystore password to decrypt the private key for signing.
110
-
111
- --browser
112
- Opens a local page in your default browser for signing with any
113
- EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
114
-
115
- --broadcast
116
- Sign and broadcast the transaction on-chain. Without this flag the
117
- command performs a dry-run.
118
-
119
- --out-dir <path>
120
- Directory to write the deployment result as a JSON file.
121
-
122
- Behavior:
123
- If app/erc-8004.ts does not exist, you will be prompted to create it
124
- (selecting supported trust mechanisms). After a successful on-chain
125
- registration, the new registration entry is written back to app/erc-8004.ts.
126
-
127
- Environment Variables:
128
- PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
129
-
130
- Examples:
131
- # Dry-run (default)
132
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia
133
-
134
- # Sign and broadcast
135
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
136
- $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --browser --broadcast`,
137
- )
138
- .action(handleAction(register));
139
-
140
- erc8004
141
- .command("update")
142
- .description("Update the metadata URI of a registered agent")
143
- .option("--url <url>", "New agent deployment URL (e.g., https://my-agent.example.com)")
144
- .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
145
- .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
146
- .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
147
- .option("--browser", "Use browser extension wallet (any extension)")
148
- .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
149
- .option("--out-dir <path>", "Write result as JSON to the given directory")
150
- .addHelpText(
151
- "after",
152
- `
153
- Option Details:
154
- --url <url>
155
- New agent deployment URL (e.g., https://my-agent.example.com).
156
- The URI will be derived as <url>/_aixyz/erc-8004.json.
157
- If omitted, you will be prompted to enter the URL interactively.
158
-
159
- --rpc-url <url>
160
- Custom RPC endpoint URL. Overrides the default RPC for the selected
161
- chain. Cannot be used with --browser.
162
-
163
- --registry <address>
164
- Contract address of the ERC-8004 IdentityRegistry. Only required for
165
- localhost, where there is no default deployment.
166
-
167
- --keystore <path>
168
- Path to an Ethereum keystore (V3) JSON file.
169
-
170
- --browser
171
- Opens a local page in your default browser for signing with any
172
- EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
173
-
174
- --broadcast
175
- Sign and broadcast the transaction on-chain. Without this flag the
176
- command performs a dry-run.
177
-
178
- --out-dir <path>
179
- Directory to write the result as a JSON file.
180
-
181
- Behavior:
182
- Reads existing registrations from app/erc-8004.ts. If there is one
183
- registration, confirms it. If multiple, prompts you to select which
184
- one to update. The chain and registry address are derived from the
185
- selected registration's agentRegistry field.
186
-
187
- Environment Variables:
188
- PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
189
-
190
- Examples:
191
- # Dry-run (default)
192
- $ aixyz erc-8004 update --url "https://new-domain.example.com"
193
-
194
- # Sign and broadcast
195
- $ aixyz erc-8004 update --url "https://new-domain.example.com" --keystore ~/.foundry/keystores/default --broadcast
196
- $ aixyz erc-8004 update --url "https://new-domain.example.com" --browser --broadcast`,
197
- )
198
- .action(handleAction(update));
199
-
200
- program.parse();
package/build/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { resolve } from "path";
2
2
  import { existsSync, mkdirSync, cpSync, rmSync } from "fs";
3
+ import { Command } from "commander";
3
4
  import { AixyzConfigPlugin } from "./AixyzConfigPlugin";
4
5
  import { AixyzServerPlugin, getEntrypointMayGenerate } from "./AixyzServerPlugin";
5
6
  import { findIconFile, copyAgentIcon, generateFavicon } from "./icons";
@@ -11,7 +12,41 @@ interface BuildOptions {
11
12
  output?: string;
12
13
  }
13
14
 
14
- export async function build(options: BuildOptions = {}): Promise<void> {
15
+ export const buildCommand = new Command("build")
16
+ .description("Build the aixyz agent")
17
+ .option("--output <type>", "Output format: 'standalone' or 'vercel'")
18
+ .addHelpText(
19
+ "after",
20
+ `
21
+ Details:
22
+ Bundles your aixyz agent for deployment.
23
+
24
+ Default behavior (auto-detected):
25
+ Bundles into a single executable file for Standalone at ./.aixyz/output/server.js
26
+
27
+ With --output vercel or VERCEL=1 env:
28
+ Generates Vercel Build Output API v3 structure at .vercel/output/
29
+ (Automatically detected when deploying to Vercel)
30
+
31
+ The build process:
32
+ 1. Loads aixyz.config.ts from the current directory
33
+ 2. Detects entrypoint (app/server.ts or auto-generates from app/agent.ts + app/tools/)
34
+ 3. Bundles the application
35
+ 4. Copies static assets from public/ (if present)
36
+
37
+ Prerequisites:
38
+ - An aixyz.config.ts with a default export
39
+ - An entrypoint at app/server.ts, or app/agent.ts + app/tools/ for auto-generation
40
+
41
+ Examples:
42
+ $ aixyz build # Build standalone (default)
43
+ $ aixyz build --output standalone # Build standalone explicitly
44
+ $ aixyz build --output vercel # Build for Vercel deployment
45
+ $ VERCEL=1 aixyz build # Auto-detected Vercel build`,
46
+ )
47
+ .action(action);
48
+
49
+ async function action(options: BuildOptions = {}): Promise<void> {
15
50
  const cwd = process.cwd();
16
51
  loadEnvConfig(cwd, false);
17
52
  const entrypoint = getEntrypointMayGenerate(cwd, "build");
package/dev/index.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { resolve, relative } from "path";
2
2
  import { existsSync, watch } from "fs";
3
3
  import { loadEnvConfig } from "@next/env";
4
+ import { Command } from "commander";
4
5
  import { getEntrypointMayGenerate } from "../build/AixyzServerPlugin";
5
6
  import pkg from "../package.json";
6
7
 
7
- export async function dev(options: { port?: string }): Promise<void> {
8
+ export const devCommand = new Command("dev")
9
+ .description("Start a local development server")
10
+ .option("-p, --port <port>", "Port to listen on", "3000")
11
+ .action(action);
12
+
13
+ async function action(options: { port?: string }): Promise<void> {
8
14
  const cwd = process.cwd();
9
15
 
10
16
  // Load environment config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aixyz/cli",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,8 +27,8 @@
27
27
  "bin.ts"
28
28
  ],
29
29
  "dependencies": {
30
- "@aixyz/config": "0.9.0",
31
- "@aixyz/erc-8004": "0.9.0",
30
+ "@aixyz/config": "0.10.0",
31
+ "@aixyz/erc-8004": "0.10.0",
32
32
  "@inquirer/prompts": "^8.3.0",
33
33
  "@next/env": "^16.1.6",
34
34
  "boxen": "^8.0.1",
package/register/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { Command } from "commander";
2
+ import { register } from "./register";
3
+ import { update } from "./update";
1
4
  import type { WalletOptions } from "./wallet";
2
5
 
3
6
  export interface BaseOptions extends WalletOptions {
@@ -6,3 +9,135 @@ export interface BaseOptions extends WalletOptions {
6
9
  registry?: string;
7
10
  outDir?: string;
8
11
  }
12
+
13
+ export const erc8004Command = new Command("erc-8004").description("ERC-8004 IdentityRegistry operations");
14
+
15
+ erc8004Command
16
+ .command("register")
17
+ .description("Register a new agent to the ERC-8004 IdentityRegistry")
18
+ .option("--url <url>", "Agent deployment URL (e.g., https://my-agent.example.com)")
19
+ .option("--chain <chain>", "Target chain (mainnet, sepolia, base-sepolia, localhost)")
20
+ .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
21
+ .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
22
+ .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
23
+ .option("--browser", "Use browser extension wallet (any extension)")
24
+ .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
25
+ .option("--out-dir <path>", "Write deployment result as JSON to the given directory")
26
+ .addHelpText(
27
+ "after",
28
+ `
29
+ Option Details:
30
+ --url <url>
31
+ Agent deployment URL (e.g., https://my-agent.example.com).
32
+ The registration URI will be derived as <url>/_aixyz/erc-8004.json.
33
+ If omitted, you will be prompted to enter the URL interactively.
34
+
35
+ --chain <chain>
36
+ Target chain for registration. Supported values:
37
+ mainnet Ethereum mainnet (chain ID 1)
38
+ sepolia Ethereum Sepolia testnet (chain ID 11155111)
39
+ base-sepolia Base Sepolia testnet (chain ID 84532)
40
+ localhost Local Foundry/Anvil node (chain ID 31337)
41
+ If omitted, you will be prompted to select a chain interactively.
42
+
43
+ --rpc-url <url>
44
+ Custom RPC endpoint URL. Overrides the default RPC for the selected
45
+ chain. Cannot be used with --browser since the browser wallet manages
46
+ its own RPC connection.
47
+
48
+ --registry <address>
49
+ Contract address of the ERC-8004 IdentityRegistry. Only required for
50
+ localhost, where there is no default deployment.
51
+
52
+ --keystore <path>
53
+ Path to an Ethereum keystore (V3) JSON file. You will be prompted for
54
+ the keystore password to decrypt the private key for signing.
55
+
56
+ --browser
57
+ Opens a local page in your default browser for signing with any
58
+ EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
59
+
60
+ --broadcast
61
+ Sign and broadcast the transaction on-chain. Without this flag the
62
+ command performs a dry-run.
63
+
64
+ --out-dir <path>
65
+ Directory to write the deployment result as a JSON file.
66
+
67
+ Behavior:
68
+ If app/erc-8004.ts does not exist, you will be prompted to create it
69
+ (selecting supported trust mechanisms). After a successful on-chain
70
+ registration, the new registration entry is written back to app/erc-8004.ts.
71
+
72
+ Environment Variables:
73
+ PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
74
+
75
+ Examples:
76
+ # Dry-run (default)
77
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia
78
+
79
+ # Sign and broadcast
80
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --keystore ~/.foundry/keystores/default --broadcast
81
+ $ aixyz erc-8004 register --url "https://my-agent.example.com" --chain sepolia --browser --broadcast`,
82
+ )
83
+ .action(register);
84
+
85
+ erc8004Command
86
+ .command("update")
87
+ .description("Update the metadata URI of a registered agent")
88
+ .option("--url <url>", "New agent deployment URL (e.g., https://my-agent.example.com)")
89
+ .option("--rpc-url <url>", "Custom RPC URL (uses default if not provided)")
90
+ .option("--registry <address>", "Contract address of the IdentityRegistry (required for localhost)")
91
+ .option("--keystore <path>", "Path to Ethereum keystore (V3) JSON file for local signing")
92
+ .option("--browser", "Use browser extension wallet (any extension)")
93
+ .option("--broadcast", "Sign and broadcast the transaction (default: dry-run)")
94
+ .option("--out-dir <path>", "Write result as JSON to the given directory")
95
+ .addHelpText(
96
+ "after",
97
+ `
98
+ Option Details:
99
+ --url <url>
100
+ New agent deployment URL (e.g., https://my-agent.example.com).
101
+ The URI will be derived as <url>/_aixyz/erc-8004.json.
102
+ If omitted, you will be prompted to enter the URL interactively.
103
+
104
+ --rpc-url <url>
105
+ Custom RPC endpoint URL. Overrides the default RPC for the selected
106
+ chain. Cannot be used with --browser.
107
+
108
+ --registry <address>
109
+ Contract address of the ERC-8004 IdentityRegistry. Only required for
110
+ localhost, where there is no default deployment.
111
+
112
+ --keystore <path>
113
+ Path to an Ethereum keystore (V3) JSON file.
114
+
115
+ --browser
116
+ Opens a local page in your default browser for signing with any
117
+ EIP-6963 compatible wallet extension (MetaMask, Rabby, etc.).
118
+
119
+ --broadcast
120
+ Sign and broadcast the transaction on-chain. Without this flag the
121
+ command performs a dry-run.
122
+
123
+ --out-dir <path>
124
+ Directory to write the result as a JSON file.
125
+
126
+ Behavior:
127
+ Reads existing registrations from app/erc-8004.ts. If there is one
128
+ registration, confirms it. If multiple, prompts you to select which
129
+ one to update. The chain and registry address are derived from the
130
+ selected registration's agentRegistry field.
131
+
132
+ Environment Variables:
133
+ PRIVATE_KEY Private key (hex, with or without 0x prefix) used for signing.
134
+
135
+ Examples:
136
+ # Dry-run (default)
137
+ $ aixyz erc-8004 update --url "https://new-domain.example.com"
138
+
139
+ # Sign and broadcast
140
+ $ aixyz erc-8004 update --url "https://new-domain.example.com" --keystore ~/.foundry/keystores/default --broadcast
141
+ $ aixyz erc-8004 update --url "https://new-domain.example.com" --browser --broadcast`,
142
+ )
143
+ .action(update);
@@ -1,6 +1,6 @@
1
1
  import { encodeFunctionData, formatEther, parseEventLogs, type Chain, type Log } from "viem";
2
2
  import { IdentityRegistryAbi } from "@aixyz/erc-8004";
3
- import { selectWalletMethod, type WalletOptions } from "./wallet";
3
+ import { selectWalletMethod } from "./wallet";
4
4
  import { signTransaction } from "./wallet/sign";
5
5
  import {
6
6
  resolveChainConfig,
@@ -2,13 +2,7 @@ import { encodeFunctionData, formatEther, parseEventLogs, type Chain, type Log }
2
2
  import { IdentityRegistryAbi } from "@aixyz/erc-8004";
3
3
  import { selectWalletMethod } from "./wallet";
4
4
  import { signTransaction } from "./wallet/sign";
5
- import {
6
- resolveChainConfig,
7
- resolveRegistryAddress,
8
- validateBrowserRpcConflict,
9
- getExplorerUrl,
10
- CHAINS,
11
- } from "./utils/chain";
5
+ import { resolveChainConfig, validateBrowserRpcConflict, getExplorerUrl, CHAINS } from "./utils/chain";
12
6
  import { writeResultJson } from "./utils/result";
13
7
  import { label, truncateUri, broadcastAndConfirm, logSignResult } from "./utils/transaction";
14
8
  import { promptAgentUrl, promptSelectRegistration, deriveAgentUri } from "./utils/prompt";
@@ -1,23 +1,6 @@
1
1
  import { checkbox, confirm, input, select } from "@inquirer/prompts";
2
2
  import type { RegistrationEntry } from "@aixyz/erc-8004/schemas/registration";
3
3
 
4
- export async function promptAgentId(): Promise<string> {
5
- return input({
6
- message: "Agent ID (token ID) to update:",
7
- validate: (value) => {
8
- const n = Number(value);
9
- if (value.trim() === "" || !Number.isInteger(n) || n < 0) return "Must be a non-negative integer";
10
- return true;
11
- },
12
- });
13
- }
14
-
15
- export async function promptUri(): Promise<string> {
16
- return input({
17
- message: "New agent metadata URI or path to .json file (leave empty to clear):",
18
- });
19
- }
20
-
21
4
  export async function promptAgentUrl(): Promise<string> {
22
5
  return input({
23
6
  message: "Agent deployment URL (e.g., https://my-agent.example.com):",
@@ -1,81 +0,0 @@
1
- import { describe, expect, test, afterAll, beforeAll } from "bun:test";
2
- import { rmSync } from "fs";
3
- import { mkdir } from "node:fs/promises";
4
- import { resolveUri } from "./utils";
5
- import { join } from "path";
6
-
7
- describe("resolveUri", () => {
8
- const testDir = join(import.meta.dir, "__test_fixtures__");
9
- const testJsonPath = join(testDir, "test-metadata.json");
10
- const testMetadata = { name: "Test Agent", description: "A test agent" };
11
-
12
- beforeAll(() => {
13
- // ensure test directory exists
14
- mkdir(testDir, { recursive: true });
15
- });
16
-
17
- afterAll(() => {
18
- // clean up test directory
19
- rmSync(testDir, { recursive: true, force: true });
20
- });
21
-
22
- test("returns ipfs:// URIs unchanged", () => {
23
- const uri = "ipfs://QmTest123";
24
- expect(resolveUri(uri)).toStrictEqual(uri);
25
- });
26
-
27
- test("returns https:// URIs unchanged", () => {
28
- const uri = "https://example.com/metadata.json";
29
- expect(resolveUri(uri)).toStrictEqual(uri);
30
- });
31
-
32
- test("returns http:// URIs unchanged", () => {
33
- const uri = "http://example.com/metadata.json";
34
- expect(resolveUri(uri)).toStrictEqual(uri);
35
- });
36
-
37
- test("converts .json file to base64 data URI", async () => {
38
- await Bun.write(testJsonPath, JSON.stringify(testMetadata));
39
-
40
- try {
41
- const result = resolveUri(testJsonPath);
42
- expect(result.startsWith("data:application/json;base64,")).toStrictEqual(true);
43
-
44
- // Decode and verify content
45
- const base64 = result.replace("data:application/json;base64,", "");
46
- const decoded = JSON.parse(Buffer.from(base64, "base64").toString("utf-8"));
47
- expect(decoded).toStrictEqual(testMetadata);
48
- } finally {
49
- await Bun.file(testJsonPath).unlink();
50
- }
51
- });
52
-
53
- test("throws for directory path", async () => {
54
- await mkdir(testDir, { recursive: true });
55
- const dirWithJsonSuffix = join(testDir, "not-a-file.json");
56
- await mkdir(dirWithJsonSuffix, { recursive: true });
57
-
58
- try {
59
- expect(() => resolveUri(dirWithJsonSuffix)).toThrow(Error);
60
- expect(() => resolveUri(dirWithJsonSuffix)).toThrow("Not a file");
61
- } finally {
62
- rmSync(dirWithJsonSuffix, { recursive: true, force: true });
63
- }
64
- });
65
-
66
- test("throws for non-existent .json file", () => {
67
- expect(() => resolveUri("./non-existent.json")).toThrow(Error);
68
- expect(() => resolveUri("./non-existent.json")).toThrow("File not found");
69
- });
70
-
71
- test("throws for invalid JSON content", async () => {
72
- await Bun.write(testJsonPath, "not valid json {{{");
73
-
74
- try {
75
- expect(() => resolveUri(testJsonPath)).toThrow(Error);
76
- expect(() => resolveUri(testJsonPath)).toThrow("Invalid JSON");
77
- } finally {
78
- await Bun.file(testJsonPath).unlink();
79
- }
80
- });
81
- });
package/register/utils.ts DELETED
@@ -1,38 +0,0 @@
1
- import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { resolve } from "node:path";
3
-
4
- export function resolveUri(uri: string): string {
5
- // Return as-is for URLs (ipfs://, https://, data:, etc.)
6
- if (uri.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("ipfs://") || uri.startsWith("data:")) {
7
- return uri;
8
- }
9
-
10
- // Check if it's a file path (ends with .json or exists as a file)
11
- if (uri.endsWith(".json") || existsSync(uri)) {
12
- const filePath = resolve(uri);
13
-
14
- if (!existsSync(filePath)) {
15
- throw new Error(`File not found: ${filePath}`);
16
- }
17
-
18
- if (!statSync(filePath).isFile()) {
19
- throw new Error(`Not a file: ${filePath}`);
20
- }
21
-
22
- const content = readFileSync(filePath, "utf-8");
23
-
24
- // Validate it's valid JSON
25
- try {
26
- JSON.parse(content);
27
- } catch {
28
- throw new Error(`Invalid JSON in file: ${filePath}`);
29
- }
30
-
31
- // Convert to base64 data URI
32
- const base64 = Buffer.from(content).toString("base64");
33
- return `data:application/json;base64,${base64}`;
34
- }
35
-
36
- // Return as-is for other URIs
37
- return uri;
38
- }