diff --git a/packages/cli/package.json b/packages/cli/package.json index 61639ef..8ba2407 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -66,6 +66,7 @@ }, "devDependencies": { "@types/form-data": "^2.5.2", + "@types/js-yaml": "^4.0.9", "@types/node": "^18", "ts-node": "^10.9.2" } diff --git a/packages/cli/src/commands/compute/app/deploy.ts b/packages/cli/src/commands/compute/app/deploy.ts index 3826f25..e4bb136 100644 --- a/packages/cli/src/commands/compute/app/deploy.ts +++ b/packages/cli/src/commands/compute/app/deploy.ts @@ -19,6 +19,7 @@ import { confirm, getPrivateKeyInteractive, } from "../../../utils/prompts"; +import { setAppName } from "../../../utils/appNames"; import chalk from "chalk"; export default class AppDeploy extends Command { @@ -169,6 +170,14 @@ export default class AppDeploy extends Command { logger, ); + // 11. Save the app name mapping locally + try { + await setAppName(environment, res.appId, appName); + logger.info(`App saved with name: ${appName}`); + } catch (err: any) { + logger.warn(`Failed to save app name: ${err.message}`); + } + this.log( `\n✅ ${chalk.green(`App deployed successfully ${chalk.bold(`(id: ${res.appId}, ip: ${res.ipAddress})`)}`)}`, ); diff --git a/packages/cli/src/commands/compute/environment/list.ts b/packages/cli/src/commands/compute/environment/list.ts index 36cb846..3bcf91d 100644 --- a/packages/cli/src/commands/compute/environment/list.ts +++ b/packages/cli/src/commands/compute/environment/list.ts @@ -1,9 +1,6 @@ import { Command } from "@oclif/core"; -import { - getAvailableEnvironments, - getEnvironmentConfig, - getDefaultEnvironment, -} from "@layr-labs/ecloud-sdk"; +import { getAvailableEnvironments, getEnvironmentConfig } from "@layr-labs/ecloud-sdk"; +import { getDefaultEnvironment } from "../../../utils/globalConfig"; import chalk from "chalk"; /** diff --git a/packages/cli/src/commands/compute/environment/set.ts b/packages/cli/src/commands/compute/environment/set.ts index 7920978..dcb07e0 100644 --- a/packages/cli/src/commands/compute/environment/set.ts +++ b/packages/cli/src/commands/compute/environment/set.ts @@ -3,8 +3,8 @@ import { getEnvironmentConfig, getAvailableEnvironments, isEnvironmentAvailable, - setDefaultEnvironment, } from "@layr-labs/ecloud-sdk"; +import { setDefaultEnvironment } from "../../../utils/globalConfig"; import { getEnvironmentInteractive } from "../../../utils/prompts"; import { confirm } from "@inquirer/prompts"; diff --git a/packages/cli/src/commands/compute/environment/show.ts b/packages/cli/src/commands/compute/environment/show.ts index b926be7..6f786e0 100644 --- a/packages/cli/src/commands/compute/environment/show.ts +++ b/packages/cli/src/commands/compute/environment/show.ts @@ -1,9 +1,6 @@ import { Command } from "@oclif/core"; -import { - getDefaultEnvironment, - getEnvironmentConfig, - getAvailableEnvironments, -} from "@layr-labs/ecloud-sdk"; +import { getEnvironmentConfig, getAvailableEnvironments } from "@layr-labs/ecloud-sdk"; +import { getDefaultEnvironment } from "../../../utils/globalConfig"; import chalk from "chalk"; export default class EnvironmentShow extends Command { diff --git a/packages/cli/src/flags.ts b/packages/cli/src/flags.ts index c6f8863..c1bd4a0 100644 --- a/packages/cli/src/flags.ts +++ b/packages/cli/src/flags.ts @@ -1,6 +1,6 @@ -import { getDefaultEnvironment } from "@layr-labs/ecloud-sdk"; import { Flags } from "@oclif/core"; import { getEnvironmentInteractive, getPrivateKeyInteractive } from "./utils/prompts"; +import { getDefaultEnvironment } from "./utils/globalConfig"; export type CommonFlags = { verbose: boolean; diff --git a/packages/sdk/src/client/common/registry/appNames.ts b/packages/cli/src/utils/appNames.ts similarity index 81% rename from packages/sdk/src/client/common/registry/appNames.ts rename to packages/cli/src/utils/appNames.ts index c555ddb..09b9ff4 100644 --- a/packages/sdk/src/client/common/registry/appNames.ts +++ b/packages/cli/src/utils/appNames.ts @@ -86,9 +86,9 @@ function saveAppRegistry(environment: string, registry: AppRegistry): void { } /** - * Resolve app ID or name to app ID + * Resolve app ID or name to app ID (for CLI use) */ -function resolveAppID(environment: string, appIDOrName: string): string | null { +export function resolveAppIDFromRegistry(environment: string, appIDOrName: string): string | null { // First check if it's already a valid hex address if (/^0x[a-fA-F0-9]{40}$/.test(appIDOrName)) { return appIDOrName; @@ -117,7 +117,7 @@ export async function setAppName( const registry = loadAppRegistry(environment); // Resolve the target app ID - let targetAppID: string | null = resolveAppID(environment, appIDOrName); + let targetAppID: string | null = resolveAppIDFromRegistry(environment, appIDOrName); if (!targetAppID) { // If can't resolve, check if it's a valid app ID if (/^0x[a-fA-F0-9]{40}$/.test(appIDOrName)) { @@ -187,3 +187,34 @@ export function listApps(environment: string): Record { return result; } + +/** + * Check if an app name is available in the given environment + */ +export function isAppNameAvailable(environment: string, name: string): boolean { + const apps = listApps(environment); + return !apps[name]; +} + +/** + * Find an available app name by appending numbers if needed + */ +export function findAvailableName(environment: string, baseName: string): string { + const apps = listApps(environment); + + // Check if base name is available + if (!apps[baseName]) { + return baseName; + } + + // Try with incrementing numbers + for (let i = 2; i <= 100; i++) { + const candidate = `${baseName}-${i}`; + if (!apps[candidate]) { + return candidate; + } + } + + // Fallback to timestamp if somehow we have 100+ duplicates + return `${baseName}-${Date.now()}`; +} diff --git a/packages/sdk/src/client/common/config/globalConfig.ts b/packages/cli/src/utils/globalConfig.ts similarity index 98% rename from packages/sdk/src/client/common/config/globalConfig.ts rename to packages/cli/src/utils/globalConfig.ts index 0155fe2..8fb3a04 100644 --- a/packages/sdk/src/client/common/config/globalConfig.ts +++ b/packages/cli/src/utils/globalConfig.ts @@ -14,7 +14,7 @@ import * as fs from "fs"; import * as path from "path"; import * as os from "os"; import { load as loadYaml, dump as dumpYaml } from "js-yaml"; -import { getBuildType } from "./environment"; +import { getBuildType } from "@layr-labs/ecloud-sdk"; const GLOBAL_CONFIG_FILE = "config.yaml"; diff --git a/packages/cli/src/utils/prompts.ts b/packages/cli/src/utils/prompts.ts index 52b89b6..2bd1183 100644 --- a/packages/cli/src/utils/prompts.ts +++ b/packages/cli/src/utils/prompts.ts @@ -15,25 +15,19 @@ import { getEnvironmentConfig, getAvailableEnvironments, isEnvironmentAvailable, - listApps, getAllAppsByDeveloper, - getDefaultEnvironment, getCategoryDescriptions, fetchTemplateCatalog, PRIMARY_LANGUAGES, AppProfile, -} from "@layr-labs/ecloud-sdk"; - -// Re-export helper functions from SDK for use in validation -import { validateAppName, validateImageReference, validateFilePath, validatePrivateKeyFormat, extractAppNameFromImage, - isAppNameAvailable, - findAvailableName, } from "@layr-labs/ecloud-sdk"; +import { getDefaultEnvironment } from "./globalConfig"; +import { listApps, isAppNameAvailable, findAvailableName } from "./appNames"; // Helper to add hex prefix function addHexPrefix(value: string): `0x${string}` { diff --git a/packages/sdk/src/client/common/contract/caller.ts b/packages/sdk/src/client/common/contract/caller.ts index 02e9319..c469beb 100644 --- a/packages/sdk/src/client/common/contract/caller.ts +++ b/packages/sdk/src/client/common/contract/caller.ts @@ -23,7 +23,6 @@ import { addHexPrefix, getChainFromID } from "../utils"; import { EnvironmentConfig, Logger } from "../types"; import { Release } from "../types"; -import { getAppName } from "../registry/appNames"; import AppControllerABI from "../abis/AppController.json"; import PermissionControllerABI from "../abis/PermissionController.json"; @@ -526,11 +525,7 @@ export async function executeUpgradeBatch( gas: { maxFeePerGas?: bigint; maxPriorityFeePerGas?: bigint } | undefined, logger: Logger, ): Promise { - const appName = getAppName(prepared.environmentConfig.name, prepared.appId); - let pendingMessage = "Upgrading app..."; - if (appName !== "") { - pendingMessage = `Upgrading app '${appName}'...`; - } + const pendingMessage = `Upgrading app ${prepared.appId}...`; const txHash = await executeBatch( { diff --git a/packages/sdk/src/client/common/utils/validation.ts b/packages/sdk/src/client/common/utils/validation.ts index b85a1a8..368a84b 100644 --- a/packages/sdk/src/client/common/utils/validation.ts +++ b/packages/sdk/src/client/common/utils/validation.ts @@ -9,7 +9,6 @@ import fs from "fs"; import path from "path"; import { Address, isAddress } from "viem"; import { stripHexPrefix, addHexPrefix } from "./helpers"; -import { listApps } from "../registry/appNames"; // ==================== App Name Validation ==================== @@ -29,37 +28,6 @@ export function validateAppName(name: string): void { } } -/** - * Check if an app name is available in the given environment - */ -export function isAppNameAvailable(environment: string, name: string): boolean { - const apps = listApps(environment); - return !apps[name]; -} - -/** - * Find an available app name by appending numbers if needed - */ -export function findAvailableName(environment: string, baseName: string): string { - const apps = listApps(environment); - - // Check if base name is available - if (!apps[baseName]) { - return baseName; - } - - // Try with incrementing numbers - for (let i = 2; i <= 100; i++) { - const candidate = `${baseName}-${i}`; - if (!apps[candidate]) { - return candidate; - } - } - - // Fallback to timestamp if somehow we have 100+ duplicates - return `${baseName}-${Date.now()}`; -} - // ==================== Image Reference Validation ==================== /** @@ -312,38 +280,31 @@ export function validateImagePath(filePath: string): string | undefined { return undefined; } -// ==================== App ID Resolution ==================== +// ==================== App ID Validation ==================== /** - * Resolve app ID from name or address - * @param appIDOrName - App ID (address) or app name - * @param environment - Environment name - * @returns Resolved app address - * @throws Error if app ID cannot be resolved + * Validate and normalize app ID address + * @param appID - App ID (must be a valid address) + * @returns Normalized app address + * @throws Error if app ID is not a valid address + * + * Note: Name resolution should be handled by CLI before calling SDK functions. + * The SDK only accepts resolved addresses. */ -export function resolveAppID(appIDOrName: string | Address, environment: string): Address { - if (!appIDOrName) { - throw new Error("App ID or name is required"); +export function validateAppID(appID: string | Address): Address { + if (!appID) { + throw new Error("App ID is required"); } // Normalize the input - const normalized = typeof appIDOrName === "string" ? addHexPrefix(appIDOrName) : appIDOrName; + const normalized = typeof appID === "string" ? addHexPrefix(appID) : appID; // Check if it's a valid address if (isAddress(normalized)) { return normalized as Address; } - // If not a valid address, treat as app name and look it up - const apps = listApps(environment); - const foundAppID = apps[appIDOrName as string]; - if (foundAppID) { - // Ensure it has 0x prefix - return addHexPrefix(foundAppID) as Address; - } - - // Name not found - throw new Error(`App name '${appIDOrName}' not found in environment '${environment}'`); + throw new Error(`Invalid app ID: '${appID}' is not a valid address`); } // ==================== Log Visibility Validation ==================== @@ -517,13 +478,13 @@ export interface UpgradeParams { * Validate upgrade parameters * @throws Error if required parameters are missing or invalid */ -export function validateUpgradeParams(params: Partial, environment: string): void { +export function validateUpgradeParams(params: Partial): void { // App ID is required if (!params.appID) { throw new Error("App ID is required for upgrade"); } - // Validate app ID can be resolved (throws if not) - resolveAppID(params.appID, environment); + // Validate app ID is a valid address (throws if not) + validateAppID(params.appID); // Must have either dockerfilePath or imageRef if (!params.dockerfilePath && !params.imageRef) { @@ -595,10 +556,10 @@ export interface LogsParams { * Validate logs parameters * @throws Error if required parameters are missing or invalid */ -export function validateLogsParams(params: Partial, environment: string): void { +export function validateLogsParams(params: Partial): void { if (!params.appID) { throw new Error("App ID is required for viewing logs"); } - // Validate app ID can be resolved (throws if not) - resolveAppID(params.appID, environment); + // Validate app ID is a valid address (throws if not) + validateAppID(params.appID); } diff --git a/packages/sdk/src/client/index.ts b/packages/sdk/src/client/index.ts index 547fb84..05b43d1 100644 --- a/packages/sdk/src/client/index.ts +++ b/packages/sdk/src/client/index.ts @@ -61,19 +61,6 @@ export { } from "./common/config/environment"; export { isSubscriptionActive } from "./common/utils/billing"; -// Export global config functions -export { - loadGlobalConfig, - saveGlobalConfig, - getDefaultEnvironment, - setDefaultEnvironment, - isFirstRun, - markFirstRunComplete, - getGlobalTelemetryPreference, - setGlobalTelemetryPreference, - type GlobalConfig, -} from "./common/config/globalConfig"; - // Export auth utilities export * from "./common/auth"; @@ -84,9 +71,6 @@ export { getCategoryDescriptions, } from "./common/templates/catalog"; -// Export registry utilities -export { listApps, getAppName, setAppName } from "./common/registry/appNames"; - // Export contract utilities export { getAllAppsByDeveloper, diff --git a/packages/sdk/src/client/modules/app/deploy.ts b/packages/sdk/src/client/modules/app/deploy.ts index 0d6d4de..3b465ca 100644 --- a/packages/sdk/src/client/modules/app/deploy.ts +++ b/packages/sdk/src/client/modules/app/deploy.ts @@ -32,7 +32,6 @@ import { } from "../../common/utils/validation"; import { doPreflightChecks, PreflightContext } from "../../common/utils/preflight"; import { UserApiClient } from "../../common/utils/userapi"; -import { setAppName } from "../../common/registry/appNames"; import { defaultLogger } from "../../common/utils"; /** @@ -196,7 +195,6 @@ export async function deploy( const appName = options.appName; const envFilePath = options.envFilePath || ""; const instanceType = options.instanceType; - const environment = preflightCtx.environmentConfig.name; // 5. Generate random salt const salt = generateRandomSalt(); @@ -269,15 +267,7 @@ export async function deploy( } } - // 10. Save the app name mapping - try { - await setAppName(environment, deployResult.appId, appName); - logger.info(`App saved with name: ${appName}`); - } catch (err: any) { - logger.warn(`Failed to save app name: ${err.message}`); - } - - // 11. Watch until app is running + // 10. Watch until app is running logger.info("Waiting for app to start..."); const ipAddress = await watchUntilRunning( { @@ -498,16 +488,7 @@ export async function executeDeploy( } } - // 3. Save the app name mapping - const environment = prepared.preflightCtx.environmentConfig.name; - try { - await setAppName(environment, appId, prepared.appName); - logger.info(`App saved with name: ${prepared.appName}`); - } catch (err: any) { - logger.warn(`Failed to save app name: ${err.message}`); - } - - // 4. Watch until app is running + // 3. Watch until app is running logger.info("Waiting for app to start..."); const ipAddress = await watchUntilRunning( { diff --git a/packages/sdk/src/client/modules/app/index.ts b/packages/sdk/src/client/modules/app/index.ts index 1ab3af8..353c608 100644 --- a/packages/sdk/src/client/modules/app/index.ts +++ b/packages/sdk/src/client/modules/app/index.ts @@ -8,7 +8,6 @@ import { upgrade as upgradeApp } from "./upgrade"; import { createApp, CreateAppOpts } from "./create"; import { logs, LogsOptions } from "./logs"; -import { getAppName } from "../../common/registry/appNames"; import { getEnvironmentConfig } from "../../common/config/environment"; import { sendAndWaitForTransaction, undelegate } from "../../common/contract/caller"; @@ -162,11 +161,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { }, async start(appId, opts) { - const appName = getAppName(ctx.environment, appId); - let pendingMessage = "Starting app..."; - if (appName !== "") { - pendingMessage = `Starting app '${appName}'...`; - } + const pendingMessage = `Starting app ${appId}...`; const data = encodeFunctionData({ abi: CONTROLLER_ABI, @@ -191,11 +186,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { }, async stop(appId, opts) { - const appName = getAppName(ctx.environment, appId); - let pendingMessage = "Stopping app..."; - if (appName !== "") { - pendingMessage = `Stopping app '${appName}'...`; - } + const pendingMessage = `Stopping app ${appId}...`; const data = encodeFunctionData({ abi: CONTROLLER_ABI, @@ -220,11 +211,7 @@ export function createAppModule(ctx: AppModuleConfig): AppModule { }, async terminate(appId, opts) { - const appName = getAppName(ctx.environment, appId); - let pendingMessage = "Terminating app..."; - if (appName !== "") { - pendingMessage = `Terminating app '${appName}'...`; - } + const pendingMessage = `Terminating app ${appId}...`; const data = encodeFunctionData({ abi: CONTROLLER_ABI, diff --git a/packages/sdk/src/client/modules/app/logs.ts b/packages/sdk/src/client/modules/app/logs.ts index fbd245e..eea5bc5 100644 --- a/packages/sdk/src/client/modules/app/logs.ts +++ b/packages/sdk/src/client/modules/app/logs.ts @@ -12,8 +12,7 @@ import { Logger } from "../../common/types"; import { defaultLogger } from "../../common/utils"; import { UserApiClient } from "../../common/utils/userapi"; import { getEnvironmentConfig } from "../../common/config/environment"; -import { getAppName } from "../../common/registry/appNames"; -import { resolveAppID } from "../../common/utils/validation"; +import { validateAppID } from "../../common/utils/validation"; import chalk from "chalk"; /** @@ -196,12 +195,11 @@ export async function logs( throw new Error("RPC URL is required for authenticated requests"); } - // Resolve app ID (validates and returns Address) - const appID = resolveAppID(options.appID, environment); + // Validate app ID (must be a valid address - name resolution is done by CLI) + const appID = validateAppID(options.appID); - // Get app profile name - const profileName = getAppName(environment, appID); - const formattedApp = formatAppDisplay(environmentConfig.name, appID, profileName); + // Format app display (no profile name in SDK - CLI handles that) + const formattedApp = formatAppDisplay(environmentConfig.name, appID, ""); // Create user API client const userApiClient = new UserApiClient(environmentConfig, options.privateKey, rpcUrl); diff --git a/packages/sdk/src/client/modules/app/upgrade.ts b/packages/sdk/src/client/modules/app/upgrade.ts index 8c6e620..312ef7d 100644 --- a/packages/sdk/src/client/modules/app/upgrade.ts +++ b/packages/sdk/src/client/modules/app/upgrade.ts @@ -22,7 +22,7 @@ import { import { estimateBatchGas } from "../../common/contract/eip7702"; import { watchUntilUpgradeComplete } from "../../common/contract/watcher"; import { - resolveAppID, + validateAppID, validateLogVisibility, assertValidImageReference, assertValidFilePath, @@ -101,7 +101,7 @@ export interface PrepareUpgradeResult { /** * Validate upgrade options and throw descriptive errors for missing/invalid params */ -function validateUpgradeOptions(options: SDKUpgradeOptions, environment: string): Address { +function validateUpgradeOptions(options: SDKUpgradeOptions): Address { // Private key is required if (!options.privateKey) { throw new Error("privateKey is required for upgrade"); @@ -111,8 +111,8 @@ function validateUpgradeOptions(options: SDKUpgradeOptions, environment: string) if (!options.appId) { throw new Error("appId is required for upgrade"); } - // Resolve app ID (validates and returns Address) - const resolvedAppID = resolveAppID(options.appId, environment); + // Validate app ID (must be a valid address - name resolution is done by CLI) + const resolvedAppID = validateAppID(options.appId); // Must have either dockerfilePath or imageRef if (!options.dockerfilePath && !options.imageRef) { @@ -178,8 +178,8 @@ export async function upgrade( logger, ); - // 2. Validate all required parameters upfront (now that we have environment) - const appID = validateUpgradeOptions(options, preflightCtx.environmentConfig.name); + // 2. Validate all required parameters upfront + const appID = validateUpgradeOptions(options); // Convert log visibility to internal format const { logRedirect, publicLogs } = validateLogVisibility(options.logVisibility); @@ -273,11 +273,8 @@ export async function prepareUpgrade( logger, ); - // 2. Validate all required parameters upfront (now that we have environment) - const appID = validateUpgradeOptions( - options as SDKUpgradeOptions, - preflightCtx.environmentConfig.name, - ); + // 2. Validate all required parameters upfront + const appID = validateUpgradeOptions(options as SDKUpgradeOptions); // Convert log visibility to internal format const { logRedirect, publicLogs } = validateLogVisibility(options.logVisibility); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa2e9c0..c151e3c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,6 +87,9 @@ importers: '@types/form-data': specifier: ^2.5.2 version: 2.5.2 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 '@types/node': specifier: ^18 version: 18.19.130