diff --git a/.changeset/config-update-path-persistence.md b/.changeset/config-update-path-persistence.md new file mode 100644 index 0000000..ab03e4f --- /dev/null +++ b/.changeset/config-update-path-persistence.md @@ -0,0 +1,5 @@ +--- +"@godaddy/cli": patch +--- + +Fix application config updates so action, subscription, and extension additions write back to the resolved config file instead of re-resolving the environment path. diff --git a/src/services/config.ts b/src/services/config.ts index d62d378..a566c8b 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -1,5 +1,5 @@ import * as nodeFs from "node:fs"; -import { join } from "node:path"; +import { isAbsolute, join } from "node:path"; import { FileSystem } from "@effect/platform/FileSystem"; import * as TOML from "@iarna/toml"; import { type ArkErrors, type } from "arktype"; @@ -207,7 +207,9 @@ export function getConfigFilePath( configPath?: string, ): string { if (configPath) { - return join(process.cwd(), configPath); + return isAbsolute(configPath) + ? configPath + : join(process.cwd(), configPath); } const resolvedEnv = resolveConfigEnvironment(env); @@ -235,7 +237,7 @@ export function getConfigFileEffect({ // If a specific config path is provided, use that if (configPath) { - const absolutePath = join(process.cwd(), configPath); + const absolutePath = getConfigFilePath(undefined, configPath); const exists = yield* fileExists(absolutePath); if (exists) { const content = yield* fs.readFileString(absolutePath); @@ -298,7 +300,7 @@ export function getConfigFile({ const resolvedEnv = resolveConfigEnvironment(env); if (configPath) { - const absolutePath = join(process.cwd(), configPath); + const absolutePath = getConfigFilePath(undefined, configPath); if (nodeFs.existsSync(absolutePath)) { const content = nodeFs.readFileSync(absolutePath, "utf-8"); return Config(TOML.parse(content)); @@ -429,7 +431,7 @@ function getConfigFilePathForUpdateEffect( const resolvedEnv = resolveConfigEnvironment(env); if (configPath) { - const absolutePath = join(process.cwd(), configPath); + const absolutePath = getConfigFilePath(undefined, configPath); const exists = yield* fileExists(absolutePath); if (exists) { return { path: absolutePath }; @@ -467,13 +469,12 @@ function getConfigFilePathForUpdateEffect( /** * Write a full Config object to the TOML file. */ -function writeConfigToFileEffect( +function writeConfigToResolvedPathEffect( data: Config, - env?: ConfigEnvironment, + filePath: string, ): Effect.Effect { return Effect.gen(function* () { const fs = yield* FileSystem; - const filePath = getConfigFilePath(env); // Try to read the existing file to preserve structure let existingConfig = {}; @@ -558,6 +559,13 @@ function writeConfigToFileEffect( ); } +function writeConfigToFileEffect( + data: Config, + env?: ConfigEnvironment, +): Effect.Effect { + return writeConfigToResolvedPathEffect(data, getConfigFilePath(env)); +} + /** * Write the config data to the appropriate TOML file. */ @@ -614,11 +622,11 @@ export function addActionToConfigEffect( actions: [...(configResult.actions || []), action], }; - const { env } = yield* getConfigFilePathForUpdateEffect( + const { path } = yield* getConfigFilePathForUpdateEffect( options.configPath, options.env, ); - yield* writeConfigToFileEffect(updatedConfig, env); + yield* writeConfigToResolvedPathEffect(updatedConfig, path); }).pipe( Effect.catchAll((error) => Effect.fail(toConfigError(error, "Unable to update actions in config")), @@ -651,11 +659,11 @@ export function addSubscriptionToConfigEffect( }, }; - const { env } = yield* getConfigFilePathForUpdateEffect( + const { path } = yield* getConfigFilePathForUpdateEffect( options.configPath, options.env, ); - yield* writeConfigToFileEffect(updatedConfig, env); + yield* writeConfigToResolvedPathEffect(updatedConfig, path); }).pipe( Effect.catchAll((error) => Effect.fail( @@ -783,11 +791,11 @@ export function addExtensionToConfigEffect( extensions: updatedExtensions, } satisfies Config; - const { env } = yield* getConfigFilePathForUpdateEffect( + const { path } = yield* getConfigFilePathForUpdateEffect( options.configPath, options.env, ); - yield* writeConfigToFileEffect(updatedConfig, env); + yield* writeConfigToResolvedPathEffect(updatedConfig, path); }).pipe( Effect.catchAll((error) => Effect.fail( diff --git a/tests/unit/services/config-routing.test.ts b/tests/unit/services/config-routing.test.ts index 86e9b02..7e94611 100644 --- a/tests/unit/services/config-routing.test.ts +++ b/tests/unit/services/config-routing.test.ts @@ -4,6 +4,7 @@ import * as path from "node:path"; import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { type Config, + addActionToConfigEffect, createConfigFileEffect, createEnvFileEffect, getConfigFilePath, @@ -67,6 +68,42 @@ describe("Config Environment Routing", () => { expect(fs.existsSync(path.join(tempDir, "godaddy.ote.toml"))).toBe(false); }); + test("adds actions to the explicit config path", async () => { + const configPath = path.join(tempDir, "godaddy.ote.toml"); + fs.writeFileSync( + configPath, + [ + 'name = "test-app"', + 'client_id = "a502484b-d7b1-4509-aa88-08b391a54c28"', + 'description = "Test app"', + 'version = "1.0.0"', + 'url = "https://example.com"', + 'proxy_url = "https://example.com/api"', + 'authorization_scopes = [ "shopper.readonly" ]', + "actions = [ ]", + "", + "[subscriptions]", + "webhook = [ ]", + "", + ].join("\n"), + ); + + await runEffect( + addActionToConfigEffect( + { + name: "commerce.communications.broadcast", + url: "/actions/broadcast", + }, + { configPath }, + ), + ); + + const content = fs.readFileSync(configPath, "utf-8"); + expect(content).toContain('name = "commerce.communications.broadcast"'); + expect(content).toContain('url = "/actions/broadcast"'); + expect(fs.existsSync(path.join(tempDir, "godaddy.toml"))).toBe(false); + }); + test("writes env file to mapped environment file", async () => { process.env.GODADDY_API_BASE_URL = "https://api.test-godaddy.com";