From 68fe0ba3b14f4d92ef9024de554dca3c89947257 Mon Sep 17 00:00:00 2001 From: IT-WIBRC Date: Sat, 4 Oct 2025 00:29:46 +0100 Subject: [PATCH] feat: add short aliases (pm, packageManager, lang, lang, cache) for configuration keys --- .changeset/mean-bushes-help.md | 5 + packages/devkit/README.md | 42 +-- packages/devkit/TODO.md | 4 +- .../integrations/config/index.spec.ts | 335 +++++++++++------- .../units/commands/config/get/index.spec.ts | 103 ++++++ .../units/commands/config/index.spec.ts | 198 +++-------- .../units/commands/config/logic.spec.ts | 2 +- .../units/commands/config/set/index.spec.ts | 112 ++++++ .../units/commands/config/utils.spec.ts | 125 +++++++ packages/devkit/locales/en.json | 4 +- packages/devkit/locales/fr.json | 4 +- .../devkit/src/commands/config/get/index.ts | 30 ++ packages/devkit/src/commands/config/index.ts | 50 +-- packages/devkit/src/commands/config/logic.ts | 2 +- .../devkit/src/commands/config/set/index.ts | 24 ++ packages/devkit/src/commands/config/utils.ts | 35 ++ .../src/utils/validations/configAliases.ts | 1 + 17 files changed, 719 insertions(+), 357 deletions(-) create mode 100644 .changeset/mean-bushes-help.md create mode 100644 packages/devkit/__tests__/units/commands/config/get/index.spec.ts create mode 100644 packages/devkit/__tests__/units/commands/config/set/index.spec.ts create mode 100644 packages/devkit/__tests__/units/commands/config/utils.spec.ts create mode 100644 packages/devkit/src/commands/config/get/index.ts create mode 100644 packages/devkit/src/commands/config/set/index.ts create mode 100644 packages/devkit/src/commands/config/utils.ts diff --git a/.changeset/mean-bushes-help.md b/.changeset/mean-bushes-help.md new file mode 100644 index 0000000..8a096d6 --- /dev/null +++ b/.changeset/mean-bushes-help.md @@ -0,0 +1,5 @@ +--- +"scaffolder-toolkit": patch +--- + +feat: Add short aliases (pm, lang, cache) for configuration keys diff --git a/packages/devkit/README.md b/packages/devkit/README.md index fe682ea..5d1a0e6 100644 --- a/packages/devkit/README.md +++ b/packages/devkit/README.md @@ -189,7 +189,7 @@ Here are some examples of how to use the new options: ```bash # List all templates, including the Scaffolder's built-in defaults -dk list --include-defaults +dk list -d # List templates only from the local configuration file dk list --local @@ -212,28 +212,28 @@ dk list --mode table -d --- -### Manage your CLI configuration +### Manage your CLI configuration (`dk config` / `dk conf`) -The `dk config` command is a central hub for all configuration and template management. It works with subcommands to handle different tasks. +The `dk config` command is the central hub for managing your CLI settings and templates. -#### Get and Set Configuration Values +#### Get and Set Configuration Values (Core) -The core `dk config` command allows you to **get** or **set** configuration values directly using arguments and options. +The main `dk config` command allows you to **get** or **set** simple configuration values like your preferred package manager or language. -- To **get** a value, provide the key as a direct argument (e.g., `dk config language`). You can retrieve multiple values by listing their keys. -- To **set** one or more values, use the `--set` or `-s` flag followed by key-value pairs (e.g., `dk config --set language fr`). - -> **Note:** Running `dk config` without arguments or options is not supported and will result in an error. +| Action | Command Syntax | Description | +| :-------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **GET** (Read) | `dk config ` | Retrieve the value(s) for the specified configuration key(s). Supported keys include: **pm** / **defaultPackageManager**, **lang** / **language**, and **cache** / **cacheStrategy**. | +| **SET** (Write) | `dk config --set ` | Set one or more configuration properties in bulk (key-value pairs). | ```bash -# Get the value of the 'defaultPackageManager' setting -dk config defaultPackageManager +# Get the value of the 'defaultPackageManager' setting using its short alias +dk config pm -# Get the value of the 'language' setting from the global config -dk config language --global +# Get the value of both 'language' and 'cacheStrategy' from the global config +dk config language cacheStrategy --global # Set your default package manager to pnpm and the language to French in a single command (local) -dk config --set defaultPackageManager pnpm language fr +dk config --set pm pnpm lang fr # Set your default package manager to npm in your global config dk config --set defaultPackageManager npm --global @@ -322,17 +322,6 @@ dk config remove node node-api --global --- -#### Manage cache strategy for a template - -Use the `dk config cache` command to update the cache strategy for a specific template. - -```bash -# Set the cache strategy for the 'react' template to 'always-refresh' -dk config cache react always-refresh -``` - ---- - ### Shortcuts For a faster workflow, the following commands have shortcuts: @@ -349,7 +338,6 @@ For a faster workflow, the following commands have shortcuts: | `config remove` | `conf rm` | | `config update` | `conf up` | | `config list` | `conf ls` | -| `cache` | `c` | | `version` | `v` | | `help` | `h` | @@ -419,7 +407,7 @@ You can also define an **`alias`** to make it easier to reference a specific tem Once an alias is configured, you can use it in place of the full template name for faster commands. ```bash -# Create a new project using the alias 'gh-template' +# Create a new project from the alias 'gh-template' dk new javascript my-new-project-name -t gh-template ``` diff --git a/packages/devkit/TODO.md b/packages/devkit/TODO.md index baf7325..2dce8ee 100644 --- a/packages/devkit/TODO.md +++ b/packages/devkit/TODO.md @@ -61,9 +61,9 @@ This document tracks all planned and completed tasks for the Dev Kit project. - [x] **Enhance `list` Command**: Add options to **filter by properties** (e.g., `packageManager`, `alias`, etc.). - [x] **Enhance `list` Command**: Add flag to also see default config `--include-defaults`. - [x] Explain the usage of `{pm}` role inside the config in the documentation. -- [x] Add a `--settings, -s` to the `dk list` command to display the current configuration settings only +- [x] Add a `--settings, -s` to the `dk list` command to display the current configuration settings Only - [x] Add wildcard support for template name in the `dk config update` and `dk config remove` commands. -- [ ] add short keys to get config using `dk config` like `dk config lang` for `dk config language` +- [x] add short keys to get config using `dk config` like `dk config lang` for `dk config language` - [ ] ** Enhance for organization Purpose **: Add new language `Typescript(ts)` with same code as javascript(js), also support for nodejs(node) template name for those who prefer it than the programming language name - [ ] Add a configuration validation step when updating the config file to ensure all required fields are present and correctly formatted. - [ ] **Testing**: Stabilize the integration test of the `new` command diff --git a/packages/devkit/__tests__/integrations/config/index.spec.ts b/packages/devkit/__tests__/integrations/config/index.spec.ts index 7a70a59..cb9a348 100644 --- a/packages/devkit/__tests__/integrations/config/index.spec.ts +++ b/packages/devkit/__tests__/integrations/config/index.spec.ts @@ -34,6 +34,7 @@ const createLocalConfig = async () => { ...defaultCliConfig.settings, language: "fr", cacheStrategy: "always-refresh", + defaultPackageManager: "npm", }, }; await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig); @@ -46,6 +47,7 @@ const createGlobalConfig = async () => { ...defaultCliConfig.settings, language: "en", cacheStrategy: "daily", + defaultPackageManager: "bun", }, }; await fs.writeJson( @@ -78,157 +80,236 @@ describe("dk config", () => { await fs.remove(globalConfigDir); }); - it("should warn the user when no command or option is provided", async () => { - const { all, exitCode } = await execute("bun", [CLI_PATH, "config"], { - all: true, - reject: false, + describe("Core Behavior", () => { + it("should warn the user when no command or option is provided", async () => { + const { all, exitCode } = await execute("bun", [CLI_PATH, "config"], { + all: true, + reject: false, + }); + + expect(exitCode).toBe(0); + expect(all).toContain( + "Warning: No command or option provided. Use `dk config --help` to see available commands.", + ); }); - expect(exitCode).toBe(0); - expect(all).toContain( - "Warning: No command or option provided. Use `dk config --help` to see available commands.", - ); + it("should throw an error if no config file is found for setting", async () => { + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "conf", "--set", "language", "en"], + { all: true, reject: false }, + ); + + expect(exitCode).toBe(1); + expect(all).toContain( + "::[DEV]>> Devkit encountered an unexpected internal issue: No local configuration file found. Run 'devkit config init --local' to create one.", + ); + }); }); - it("should get a single setting from the local config", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "language"], - { all: true }, - ); + describe("GET Functionality (Reading Config)", () => { + it("should get a single setting from the local config using the full key", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "language"], + { all: true }, + ); - expect(exitCode).toBe(0); - expect(all).toContain("language: fr"); - expect(all).toContain("Paramètres de configuration récupérés avec succès."); - }); + expect(exitCode).toBe(0); + expect(all).toContain("language: fr"); + expect(all).toContain( + "Paramètres de configuration récupérés avec succès.", + ); + }); - it("should get multiple settings from the local config", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "language", "cacheStrategy"], - { all: true }, - ); + it("should get a single setting from the local config using a short alias (lang)", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "lang"], + { all: true }, + ); - expect(exitCode).toBe(0); - expect(all).toContain("language: fr"); - expect(all).toContain("cacheStrategy: always-refresh"); - expect(all).toContain("Paramètres de configuration récupérés avec succès."); - }); + expect(exitCode).toBe(0); + expect(all).toContain("lang: fr"); + expect(all).toContain( + "Paramètres de configuration récupérés avec succès.", + ); + }); - it("should get a setting from the global config with --global flag", async () => { - await createGlobalConfig(); - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "language", "--global"], - { all: true, env: { HOME: globalConfigDir } }, - ); + it("should get multiple settings from the local config using full keys", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "language", "cacheStrategy"], + { all: true }, + ); - expect(exitCode).toBe(0); - expect(all).toContain("language: en"); - expect(all).toContain("Paramètres de configuration récupérés avec succès."); - }); + expect(exitCode).toBe(0); + expect(all).toContain("language: fr"); + expect(all).toContain("cacheStrategy: always-refresh"); + expect(all).toContain( + "Paramètres de configuration récupérés avec succès.", + ); + }); - it("should set a single setting in the local config", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "--set", "language", "en"], - { all: true }, - ); + it("should get multiple settings from the local config using short aliases (pm, cache)", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "pm", "cache"], + { all: true }, + ); - const updatedConfig = await fs.readJson( - path.join(tempDir, LOCAL_CONFIG_FILE_NAME), - ); + expect(exitCode).toBe(0); + expect(all).toContain("pm: npm"); + expect(all).toContain("cache: always-refresh"); + expect(all).toContain( + "Paramètres de configuration récupérés avec succès.", + ); + }); - expect(exitCode).toBe(0); - expect(all).toContain("Configuration mise à jour avec succès !"); - expect(updatedConfig.settings.language).toBe("en"); - }); + it("should get a setting from the global config with --global flag", async () => { + await createGlobalConfig(); + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "language", "--global"], + { all: true, env: { HOME: globalConfigDir } }, + ); - it("should set a single setting in the global config with --global flag", async () => { - await createGlobalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "--global", "--set", "language", "fr"], - { all: true, env: { HOME: globalConfigDir } }, - ); + expect(exitCode).toBe(0); + expect(all).toContain("language: en"); + expect(all).toContain( + "Paramètres de configuration récupérés avec succès.", + ); + }); - const updatedConfig = await fs.readJson( - path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME), - ); + it("should fail gracefully if a key to get is not found", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "non_existent_key"], + { all: true }, + ); - expect(exitCode).toBe(0); - expect(all).toContain("Configuration updated successfully!"); - expect(updatedConfig.settings.language).toBe("fr"); + expect(exitCode).toBe(0); + expect(all).toContain( + "Clé de configuration 'non_existent_key' non trouvée.", + ); + }); }); - it("should set multiple settings in the local config", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [ - CLI_PATH, - "config", - "--set", - "language", - "en", - "cacheStrategy", - "never-refresh", - ], - { all: true }, - ); + describe("SET Functionality (Updating Config)", () => { + it("should set a single setting in the local config using full key", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "--set", "language", "en"], + { all: true }, + ); - const updatedConfig = await fs.readJson( - path.join(tempDir, LOCAL_CONFIG_FILE_NAME), - ); + const updatedConfig = await fs.readJson( + path.join(tempDir, LOCAL_CONFIG_FILE_NAME), + ); - expect(exitCode).toBe(0); - expect(all).toContain("Configuration mise à jour avec succès !"); - expect(updatedConfig.settings.language).toBe("en"); - expect(updatedConfig.settings.cacheStrategy).toBe("never-refresh"); - }); + expect(exitCode).toBe(0); + expect(all).toContain("Configuration mise à jour avec succès !"); + expect(updatedConfig.settings.language).toBe("en"); + }); - it("should fail if --set has an odd number of arguments", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "--set", "language", "en", "invalid"], - { all: true, reject: false }, - ); + it("should set a single setting in the local config using a short alias (lang)", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "--set", "lang", "en"], + { all: true }, + ); - expect(exitCode).toBe(0); - expect(all).toContain( - "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (ex: --set clé1 valeur1 clé2 valeur2).", - ); - }); + const updatedConfig = await fs.readJson( + path.join(tempDir, LOCAL_CONFIG_FILE_NAME), + ); - it("should fail gracefully if a key to get is not found", async () => { - await createLocalConfig(); - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "config", "non_existent_key"], - { all: true }, - ); + expect(exitCode).toBe(0); + expect(all).toContain("Configuration mise à jour avec succès !"); + expect(updatedConfig.settings.language).toBe("en"); + expect(updatedConfig.settings.defaultPackageManager).toBe("npm"); + }); - expect(exitCode).toBe(0); - expect(all).toContain( - "Clé de configuration 'non_existent_key' non trouvée.", - ); - }); + it("should set multiple settings in the local config using full keys", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [ + CLI_PATH, + "config", + "--set", + "language", + "en", + "cacheStrategy", + "never-refresh", + ], + { all: true }, + ); - it("should throw an error if no config file is found for setting", async () => { - const { all, exitCode } = await execute( - "bun", - [CLI_PATH, "conf", "--set", "language", "en"], - { all: true, reject: false }, - ); + const updatedConfig = await fs.readJson( + path.join(tempDir, LOCAL_CONFIG_FILE_NAME), + ); - expect(exitCode).toBe(1); - expect(all).toContain( - "::[DEV]>> Devkit encountered an unexpected internal issue: No local configuration file found. Run 'devkit config init --local' to create one.", - ); + expect(exitCode).toBe(0); + expect(all).toContain("Configuration mise à jour avec succès !"); + expect(updatedConfig.settings.language).toBe("en"); + expect(updatedConfig.settings.cacheStrategy).toBe("never-refresh"); + }); + + it("should set multiple settings in the local config using short aliases (pm, cache)", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "-s", "pm", "pnpm", "cache", "never-refresh"], + { all: true }, + ); + + const updatedConfig = await fs.readJson( + path.join(tempDir, LOCAL_CONFIG_FILE_NAME), + ); + + expect(exitCode).toBe(0); + expect(all).toContain("Configuration mise à jour avec succès !"); + expect(updatedConfig.settings.defaultPackageManager).toBe("pnpm"); + expect(updatedConfig.settings.cacheStrategy).toBe("never-refresh"); + }); + + it("should set a single setting in the global config with --global flag", async () => { + await createGlobalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "--global", "--set", "language", "fr"], + { all: true, env: { HOME: globalConfigDir } }, + ); + + const updatedConfig = await fs.readJson( + path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME), + ); + + expect(exitCode).toBe(0); + expect(all).toContain("Configuration updated successfully!"); + expect(updatedConfig.settings.language).toBe("fr"); + }); + + it("should fail if --set has an odd number of arguments", async () => { + await createLocalConfig(); + const { all, exitCode } = await execute( + "bun", + [CLI_PATH, "config", "--set", "language", "en", "invalid"], + { all: true, reject: false }, + ); + + expect(exitCode).toBe(0); + expect(all).toContain( + "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (ex: --set clé1 valeur1 clé2 valeur2).", + ); + }); }); }); diff --git a/packages/devkit/__tests__/units/commands/config/get/index.spec.ts b/packages/devkit/__tests__/units/commands/config/get/index.spec.ts new file mode 100644 index 0000000..3915914 --- /dev/null +++ b/packages/devkit/__tests__/units/commands/config/get/index.spec.ts @@ -0,0 +1,103 @@ +import { vi, describe, it, expect, beforeEach } from "vitest"; +import { handleGetAction } from "../../../../../src/commands/config/get/index.js"; +import { + mockLogger, + mockSpinner, + mocktFn, +} from "../../../../../vitest.setup.js"; +import { CONFIG_KEY_ALIASES } from "../../../../../src/commands/config/utils.js"; + +const { mockGetSettingsConfig, mockResolveKeys } = vi.hoisted(() => ({ + mockResolveKeys: vi.fn(), + mockGetSettingsConfig: vi.fn(), +})); + +vi.mock("#core/config/loader.js", () => ({ + readConfigSources: vi.fn(), +})); + +vi.mock("#commands/config/utils.js", async (importOriginal) => { + const originalModule = await importOriginal>(); + return { + ...originalModule, + resolveKeys: mockResolveKeys, + getSettingsConfig: mockGetSettingsConfig, + }; +}); + +const mockConfig = { + settings: { + language: "fr", + defaultPackageManager: "npm", + cacheStrategy: "daily", + emptyKey: undefined, + }, + templates: {}, +}; + +describe("handleGetAction", () => { + beforeEach(() => { + vi.clearAllMocks(); + + mockResolveKeys.mockImplementation((keys: string[]) => { + return keys.map((key) => + CONFIG_KEY_ALIASES[key] ? CONFIG_KEY_ALIASES[key] : key, + ) as (keyof typeof mockConfig.settings)[]; + }); + + mockGetSettingsConfig.mockResolvedValue(mockConfig); + }); + + it("should retrieve and log a single key using its full name", async () => { + await handleGetAction(["language"], false, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledWith("language: fr"); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + }); + + it("should retrieve and log a single key using its short alias", async () => { + await handleGetAction(["lang"], false, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledWith("lang: fr"); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + }); + + it("should retrieve and log multiple mixed keys (alias and full)", async () => { + await handleGetAction(["pm", "language"], true, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledTimes(2); + expect(mockLogger.log).toHaveBeenCalledWith("pm: npm"); + expect(mockLogger.log).toHaveBeenCalledWith("language: fr"); + }); + + it("should log an error for a non-existent key", async () => { + await handleGetAction(["nonexistent"], false, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledWith( + expect.stringContaining( + mocktFn("errors.config.get_key_not_found", { key: "nonexistent" }), + ), + ); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + }); + + it("should handle multiple keys with some existing and some not", async () => { + await handleGetAction(["language", "nonexistent", "pm"], true, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledTimes(3); + expect(mockLogger.log).toHaveBeenCalledWith("language: fr"); + expect(mockLogger.log).toHaveBeenCalledWith("pm: npm"); + expect(mockLogger.log).toHaveBeenCalledWith( + expect.stringContaining( + mocktFn("errors.config.get_key_not_found", { key: "nonexistent" }), + ), + ); + }); + + it("should correctly handle an existing key with an 'undefined' value", async () => { + await handleGetAction(["emptyKey"], true, mockSpinner); + + expect(mockLogger.log).toHaveBeenCalledWith("emptyKey: undefined"); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + }); +}); diff --git a/packages/devkit/__tests__/units/commands/config/index.spec.ts b/packages/devkit/__tests__/units/commands/config/index.spec.ts index 407038f..174c4e4 100644 --- a/packages/devkit/__tests__/units/commands/config/index.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/index.spec.ts @@ -1,55 +1,29 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { setupConfigCommand } from "../../../../src/commands/config/index.js"; import { mockSpinner, mocktFn, mockLogger } from "../../../../vitest.setup.js"; -import type { CliConfig } from "../../../integrations/common.js"; - -const MOCK_LOCAL_CONFIG: CliConfig = { - settings: { language: "en", packageManager: "npm" } as any, - templates: {}, -}; - -const MOCK_GLOBAL_CONFIG: CliConfig = { - settings: { language: "fr", packageManager: "yarn" } as any, - templates: {}, -}; - -const MOCK_CONFIG_SOURCES = { - local: MOCK_LOCAL_CONFIG, - global: MOCK_GLOBAL_CONFIG, - default: null, - configFound: true, -}; const { - mockReadConfigSources, - mockHandleNonInteractiveSettingsUpdate, mockHandleErrorAndExit, mockSetupAddCommand, mockSetupRemoveCommand, mockSetupUpdateCommand, mockSetupListCommand, + mockHandleGetAction, + mockHandleSetAction, } = vi.hoisted(() => ({ - mockReadConfigSources: vi.fn(), - mockHandleNonInteractiveSettingsUpdate: vi.fn(), mockHandleErrorAndExit: vi.fn(), mockSetupAddCommand: vi.fn(), mockSetupRemoveCommand: vi.fn(), mockSetupUpdateCommand: vi.fn(), mockSetupListCommand: vi.fn(), -})); - -vi.mock("../../../../src/commands/config/logic.js", () => ({ - handleNonInteractiveSettingsUpdate: mockHandleNonInteractiveSettingsUpdate, + mockHandleSetAction: vi.fn(), + mockHandleGetAction: vi.fn(), })); vi.mock("../../../../src/utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, })); -vi.mock("#core/config/loader.js", () => ({ - readConfigSources: mockReadConfigSources, -})); - vi.mock("../../../../src/commands/config/add.js", () => ({ setupAddCommand: mockSetupAddCommand, })); @@ -66,7 +40,13 @@ vi.mock("../../../../src/commands/config/list.js", () => ({ setupListCommand: mockSetupListCommand, })); -console.log = mockLogger.log; +vi.mock("../../../../src/commands/config/set/index.js", () => ({ + handleSetAction: mockHandleSetAction, +})); + +vi.mock("../../../../src/commands/config/get/index.js", () => ({ + handleGetAction: mockHandleGetAction, +})); describe("setupConfigCommand", () => { let mockProgram: any; @@ -74,10 +54,6 @@ describe("setupConfigCommand", () => { const DESC_KEY = "commands.config.command.description"; const NO_COMMAND_WARN_KEY = "warnings.no_command_provided"; - const SET_SUCCESS_KEY = "messages.success.config_updated"; - const GET_SUCCESS_KEY = "messages.success.config_read"; - const INVALID_FORMAT_KEY = "errors.command.set_invalid_format"; - const GET_NOT_FOUND_KEY = "errors.config.get_key_not_found"; const CONFIG_LOADING_KEY = "messages.status.config_loading"; beforeEach(() => { @@ -92,7 +68,6 @@ describe("setupConfigCommand", () => { return mockProgram; }), }; - mockReadConfigSources.mockResolvedValue(MOCK_CONFIG_SOURCES); }); it("should set up the config command with correct options and subcommands", () => { @@ -100,6 +75,16 @@ describe("setupConfigCommand", () => { expect(mockProgram.command).toHaveBeenCalledWith("config [keys...]"); expect(mockProgram.alias).toHaveBeenCalledWith("conf"); + expect(mockProgram.option).toHaveBeenCalledWith( + "-g, --global", + mocktFn("commands.config.set.option.global"), + false, + ); + expect(mockProgram.option).toHaveBeenCalledWith( + "-s, --set ", + mocktFn("commands.config.set.option.bulk"), + false, + ); expect(mockProgram.description).toHaveBeenCalledWith(mocktFn(DESC_KEY)); expect(mockSetupAddCommand).toHaveBeenCalledWith(mockProgram); expect(mockSetupRemoveCommand).toHaveBeenCalledWith(mockProgram); @@ -108,160 +93,75 @@ describe("setupConfigCommand", () => { }); describe("action handler", () => { - it("should default to warning if no keys or options are provided", async () => { + it("should delegate to warning logic if no keys or options are provided", async () => { setupConfigCommand(mockProgram); await mockAction([], { global: false }); expect(mockSpinner.start).toHaveBeenCalledWith( mockLogger.colors.cyan(mocktFn(CONFIG_LOADING_KEY)), ); + expect(mockSpinner.stop).toHaveBeenCalledOnce(); - expect(mockReadConfigSources).toHaveBeenCalledWith({ - forceGlobal: false, - forceLocal: true, - }); + expect(mockHandleSetAction).not.toHaveBeenCalled(); + expect(mockHandleGetAction).not.toHaveBeenCalled(); expect(mockSpinner.warn).toHaveBeenCalledWith( mocktFn(NO_COMMAND_WARN_KEY), ); }); - it("should call handleNonInteractiveSettingsUpdate for --set flag and respect the --global flag", async () => { + it("should delegate to handleSetAction for --set flag and pass all arguments", async () => { setupConfigCommand(mockProgram); const cmdOptions = { set: ["language", "typescript", "pm", "bun"], global: true, }; - await mockAction([], cmdOptions); + const keys: string[] = []; + await mockAction(keys, cmdOptions); - expect(mockReadConfigSources).toHaveBeenCalledWith({ - forceGlobal: true, - forceLocal: false, - }); + expect(mockSpinner.stop).toHaveBeenCalledOnce(); + expect(mockHandleGetAction).not.toHaveBeenCalled(); - expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( - "language", - "typescript", + expect(mockHandleSetAction).toHaveBeenCalledWith( + cmdOptions.set, true, - ); - - expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( - "pm", - "bun", - true, - ); - - expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green(mocktFn(SET_SUCCESS_KEY)), - ); - }); - - it("should fail for invalid --set format (odd number of arguments)", async () => { - setupConfigCommand(mockProgram); - - const cmdOptions = { set: ["language"], global: false }; - await mockAction([], cmdOptions); - - expect(mockHandleNonInteractiveSettingsUpdate).not.toHaveBeenCalled(); - - expect(mockReadConfigSources).toHaveBeenCalledWith({ - forceGlobal: false, - forceLocal: true, - }); - - expect(mockSpinner.fail).toHaveBeenCalledWith( - mockLogger.colors.redBright(mocktFn(INVALID_FORMAT_KEY)), + mockSpinner, ); }); - it("should print a single config value when a key is provided (GET functionality) from Local by default", async () => { - mockReadConfigSources.mockResolvedValue({ - ...MOCK_CONFIG_SOURCES, - local: { - settings: {}, - templates: { - javascript: {}, - }, - } as unknown as CliConfig, - }); - + it("should delegate to handleGetAction when keys are provided (GET functionality)", async () => { setupConfigCommand(mockProgram); - await mockAction(["language"], { global: false }); + const keys = ["language", "pm"]; + const cmdOptions = { global: false }; - expect(mockReadConfigSources).toHaveBeenCalledWith({ - forceGlobal: false, - forceLocal: true, - }); + await mockAction(keys, cmdOptions); - expect(mockLogger.log).toHaveBeenCalledWith( - mockLogger.colors.yellowBold( - mocktFn("errors.config.get_key_not_found", { - key: "language", - }), - ), - ); + expect(mockSpinner.stop).toHaveBeenCalledOnce(); + expect(mockHandleSetAction).not.toHaveBeenCalled(); - expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), + expect(mockHandleGetAction).toHaveBeenCalledWith( + keys, + false, + mockSpinner, ); }); - it("should print a single config value when a key is provided (GET functionality) from Global when --global is set", async () => { - mockReadConfigSources.mockResolvedValue({ - ...MOCK_CONFIG_SOURCES, - global: { settings: { language: "fr" }, templates: {} } as CliConfig, - }); + it("should handle errors gracefully by calling handleErrorAndExit", async () => { + const mockError = new Error("Config action failed"); - setupConfigCommand(mockProgram); - await mockAction(["language"], { global: true }); - - expect(mockReadConfigSources).toHaveBeenCalledWith({ - forceGlobal: true, - forceLocal: false, - }); - - expect(mockLogger.log).toHaveBeenCalledWith( - mockLogger.colors.yellowBold("language") + ": " + "fr", - ); - - expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), - ); - }); - - it("should handle a non-existent key gracefully (GET functionality)", async () => { - mockReadConfigSources.mockResolvedValue({ - ...MOCK_CONFIG_SOURCES, - local: { settings: {}, templates: {} } as CliConfig, - }); + mockHandleSetAction.mockRejectedValue(mockError); setupConfigCommand(mockProgram); - await mockAction(["nonexistent_key"], {}); - - expect(mockLogger.log).toHaveBeenCalledWith( - mockLogger.colors.redBright( - mocktFn(GET_NOT_FOUND_KEY, { - key: "nonexistent_key", - }), - ), - ); - expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), - ); - }); - it("should handle errors gracefully during config loading", async () => { - const mockError = new Error("Config read failed"); - mockReadConfigSources.mockRejectedValue(mockError); - - setupConfigCommand(mockProgram); - await mockAction([], {}); + await mockAction([], { set: ["a", "b"] }); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( mockError, mockSpinner, ); + + expect(mockSpinner.stop).toHaveBeenCalledOnce(); }); }); }); diff --git a/packages/devkit/__tests__/units/commands/config/logic.spec.ts b/packages/devkit/__tests__/units/commands/config/logic.spec.ts index 5de5ce5..525f440 100644 --- a/packages/devkit/__tests__/units/commands/config/logic.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/logic.spec.ts @@ -205,7 +205,7 @@ describe("Non-interactive Config Logic", () => { expect(updatedConfig.settings.defaultPackageManager).toBe("bun"); expect(mockValidateConfigValue).toHaveBeenCalledWith( - "defaultPackageManager", + "packageManager", "bun", ); }); diff --git a/packages/devkit/__tests__/units/commands/config/set/index.spec.ts b/packages/devkit/__tests__/units/commands/config/set/index.spec.ts new file mode 100644 index 0000000..a5b0853 --- /dev/null +++ b/packages/devkit/__tests__/units/commands/config/set/index.spec.ts @@ -0,0 +1,112 @@ +import { vi, describe, it, expect, beforeEach } from "vitest"; +import { handleSetAction } from "../../../../../src/commands/config/set/index.js"; +import { CONFIG_KEY_ALIASES } from "../../../../../src/commands/config/utils.js"; +import { mockSpinner, mocktFn } from "../../../../../vitest.setup.js"; + +const { mockHandleNonInteractiveSettingsUpdate, mockResolveSingleKey } = + vi.hoisted(() => ({ + mockHandleNonInteractiveSettingsUpdate: vi.fn(), + mockResolveSingleKey: vi.fn(), + })); + +vi.mock("#core/config/loader.js", () => ({ + readConfigSources: vi.fn(), +})); + +vi.mock("../../../../../src/commands/config/logic.js", () => ({ + handleNonInteractiveSettingsUpdate: mockHandleNonInteractiveSettingsUpdate, +})); + +vi.mock("#commands/config/utils.js", async (importOriginal) => { + const originalModule = await importOriginal>(); + return { + ...originalModule, + resolveSingleKey: mockResolveSingleKey, + }; +}); + +mockResolveSingleKey.mockImplementation((key: string) => { + return CONFIG_KEY_ALIASES[key] ? CONFIG_KEY_ALIASES[key] : key; +}); + +describe("handleSetAction", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockHandleNonInteractiveSettingsUpdate.mockResolvedValue(undefined); + }); + + it("should successfully set a single key using its full name", async () => { + const values = ["language", "fr"]; + await handleSetAction(values, false, mockSpinner); + + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( + "language", + "fr", + false, + ); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + expect(mockSpinner.fail).not.toHaveBeenCalled(); + }); + + it("should successfully set a single key using its short alias", async () => { + const values = ["pm", "bun"]; + await handleSetAction(values, true, mockSpinner); + + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( + "pm", + "bun", + true, + ); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + expect(mockSpinner.fail).not.toHaveBeenCalled(); + }); + + it("should successfully set multiple keys, mixing aliases and full names", async () => { + const values = ["lang", "en", "packageManager", "pnpm"]; + await handleSetAction(values, false, mockSpinner); + + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledTimes(2); + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( + "lang", + "en", + false, + ); + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( + "packageManager", + "pnpm", + false, + ); + expect(mockSpinner.succeed).toHaveBeenCalledOnce(); + expect(mockSpinner.fail).not.toHaveBeenCalled(); + }); + + it("should fail and not process if the number of arguments is odd", async () => { + const values = ["lang", "en", "pm"]; + await handleSetAction(values, false, mockSpinner); + + expect(mockHandleNonInteractiveSettingsUpdate).not.toHaveBeenCalled(); + expect(mockSpinner.fail).toHaveBeenCalledOnce(); + expect(mockSpinner.fail).toHaveBeenCalledWith( + expect.stringContaining(mocktFn("errors.command.set_invalid_format")), + ); + }); + + it("should stop processing and throw if an update fails (e.g., validation error)", async () => { + const values = ["lang", "en", "pm", "pnpm"]; + const validationError = new Error("Invalid package manager value."); + + mockHandleNonInteractiveSettingsUpdate.mockResolvedValueOnce(undefined); + mockHandleNonInteractiveSettingsUpdate.mockRejectedValueOnce( + validationError, + ); + + await expect(handleSetAction(values, false, mockSpinner)).rejects.toThrow( + validationError, + ); + + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledTimes(2); + + expect(mockSpinner.succeed).not.toHaveBeenCalled(); + expect(mockSpinner.fail).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/devkit/__tests__/units/commands/config/utils.spec.ts b/packages/devkit/__tests__/units/commands/config/utils.spec.ts new file mode 100644 index 0000000..f3ace61 --- /dev/null +++ b/packages/devkit/__tests__/units/commands/config/utils.spec.ts @@ -0,0 +1,125 @@ +import { + resolveKeys, + resolveSingleKey, + getSettingsConfig, +} from "../../../../src/commands/config/utils.js"; +import { vi, describe, it, expect, beforeEach } from "vitest"; + +const mockReadConfigSources = vi.hoisted(() => vi.fn()); + +vi.mock("#core/config/loader.js", () => ({ + readConfigSources: mockReadConfigSources, +})); + +const mockLocalConfig = { + settings: { + language: "fr", + defaultPackageManager: "npm", + cacheStrategy: "daily", + }, + templates: {}, +}; + +const mockGlobalConfig = { + settings: { + language: "en", + defaultPackageManager: "bun", + }, + templates: {}, +}; + +describe("Config Utils", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("resolveKeys", () => { + it("should resolve known short keys to full keys", () => { + const shortKeys = ["lang", "pm", "cache"]; + const resolved = resolveKeys(shortKeys); + expect(resolved).toEqual([ + "language", + "defaultPackageManager", + "cacheStrategy", + ]); + }); + + it("should return full keys unchanged", () => { + const fullKeys = ["language", "defaultPackageManager"]; + const resolved = resolveKeys(fullKeys); + expect(resolved).toEqual(fullKeys); + }); + + it("should handle a mix of short and full keys", () => { + const mixedKeys = ["lang", "defaultPackageManager", "pm", "nonexistent"]; + const resolved = resolveKeys(mixedKeys); + expect(resolved).toEqual([ + "language", + "defaultPackageManager", + "defaultPackageManager", + "nonexistent", + ]); + }); + + it("should handle the 'lg' alias", () => { + const keys = ["lg"]; + const resolved = resolveKeys(keys); + expect(resolved).toEqual(["language"]); + }); + }); + + describe("resolveSingleKey", () => { + it("should resolve a known short key to a full key", () => { + const resolved = resolveSingleKey("pm"); + expect(resolved).toBe("defaultPackageManager"); + }); + + it("should return a full key unchanged", () => { + const resolved = resolveSingleKey("language"); + expect(resolved).toBe("language"); + }); + }); + + describe("getSettingsConfig", () => { + it("should return local config when isGlobal is false", async () => { + mockReadConfigSources.mockResolvedValueOnce({ + local: mockLocalConfig as any, + global: mockGlobalConfig as any, + }); + + const config = await getSettingsConfig(false); + expect(mockReadConfigSources).toHaveBeenCalledWith({ + forceGlobal: false, + forceLocal: true, + }); + expect(config.settings).toEqual(mockLocalConfig.settings); + }); + + it("should return global config when isGlobal is true", async () => { + mockReadConfigSources.mockResolvedValueOnce({ + local: mockLocalConfig as any, + global: mockGlobalConfig as any, + }); + + const config = await getSettingsConfig(true); + expect(mockReadConfigSources).toHaveBeenCalledWith({ + forceGlobal: true, + forceLocal: false, + }); + expect(config.settings).toEqual(mockGlobalConfig.settings); + }); + + it("should return empty settings when config source is null", async () => { + mockReadConfigSources.mockResolvedValueOnce({ + local: null, + global: null, + }); + + const localConfig = await getSettingsConfig(false); + const globalConfig = await getSettingsConfig(true); + + expect(localConfig.settings).toEqual({}); + expect(globalConfig.settings).toEqual({}); + }); + }); +}); diff --git a/packages/devkit/locales/en.json b/packages/devkit/locales/en.json index 9189dbe..ca4c91f 100644 --- a/packages/devkit/locales/en.json +++ b/packages/devkit/locales/en.json @@ -65,7 +65,7 @@ }, "config": { "command": { - "description": "Manage DevKit settings" + "description": "Manage DevKit's global and local settings, including package managers, cache strategy, and templates." }, "interactive": { "prompt_action": "What would you like to configure?", @@ -89,7 +89,7 @@ }, "option": { "global": "Update the global configuration instead of the local one.", - "bulk": "Sets multiple configuration properties." + "bulk": "Set one or more configuration properties in bulk (e.g., KEY VALUE). Supported keys include: **pm** / **packageManager**, **cache** / **cacheStrategy**, and **lang** / **language**." } }, "get": { diff --git a/packages/devkit/locales/fr.json b/packages/devkit/locales/fr.json index bfa9f8f..1a0f8d6 100644 --- a/packages/devkit/locales/fr.json +++ b/packages/devkit/locales/fr.json @@ -65,7 +65,7 @@ }, "config": { "command": { - "description": "Gérer les paramètres DevKit" + "description": "Gérer les paramètres globaux et locaux de DevKit, y compris les gestionnaires de paquets, la stratégie de cache et les modèles (templates)." }, "interactive": { "prompt_action": "Que souhaitez-vous configurer ?", @@ -89,7 +89,7 @@ }, "option": { "global": "Mettre à jour la configuration globale au lieu de la locale.", - "bulk": "Définit plusieurs propriétés de configuration." + "bulk": "Définit une ou plusieurs propriétés de configuration en vrac (bulk) dans le format CLÉ VALEUR. Clés supportées : **pm** / **packageManager**, **cache** / **cacheStrategy**, **lang** / **language**." } }, "get": { diff --git a/packages/devkit/src/commands/config/get/index.ts b/packages/devkit/src/commands/config/get/index.ts new file mode 100644 index 0000000..2e61a89 --- /dev/null +++ b/packages/devkit/src/commands/config/get/index.ts @@ -0,0 +1,30 @@ +import { t } from "#utils/i18n/translator.js"; +import { logger, type TSpinner } from "#utils/logger.js"; +import { resolveKeys, getSettingsConfig } from "../utils.js"; +import type { CliConfig } from "#utils/schema/schema.js"; + +export async function handleGetAction( + keys: string[], + isGlobal: boolean, + spinner: TSpinner, +): Promise { + const config = await getSettingsConfig(isGlobal); + + const resolvedKeys = resolveKeys(keys); + + resolvedKeys.forEach((key: keyof CliConfig["settings"], index: number) => { + const inputKey = keys[index]; + const configValue = config.settings[key]; + + if (Object.prototype.hasOwnProperty.call(config.settings, key)) { + logger.log(logger.colors.yellowBold(inputKey) + ": " + configValue); + } else { + logger.log( + logger.colors.redBright( + t("errors.config.get_key_not_found", { key: inputKey }), + ), + ); + } + }); + spinner.succeed(logger.colors.green(t("messages.success.config_read"))); +} diff --git a/packages/devkit/src/commands/config/index.ts b/packages/devkit/src/commands/config/index.ts index 64fd084..8a003b5 100644 --- a/packages/devkit/src/commands/config/index.ts +++ b/packages/devkit/src/commands/config/index.ts @@ -1,6 +1,5 @@ import { t } from "#utils/i18n/translator.js"; import { handleErrorAndExit } from "#utils/errors/handler.js"; -import { handleNonInteractiveSettingsUpdate } from "./logic.js"; import { type Command } from "commander"; import { logger, type TSpinner } from "#utils/logger.js"; @@ -8,70 +7,29 @@ import { setupAddCommand } from "./add.js"; import { setupRemoveCommand } from "./remove.js"; import { setupUpdateCommand } from "./update.js"; import { setupListCommand } from "./list.js"; -import type { CliConfig } from "#utils/schema/schema.js"; -import { readConfigSources } from "#core/config/loader.js"; +import { handleSetAction } from "./set/index.js"; +import { handleGetAction } from "./get/index.js"; interface ConfigOptions { global?: boolean; set?: string[]; } -async function getSettingsConfig(isGlobal: boolean): Promise { - const isLocal = !isGlobal; - const configSources = await readConfigSources({ - forceGlobal: isGlobal, - forceLocal: isLocal, - }); - - function getConfig(config: CliConfig | null): CliConfig { - return (config || {}) as CliConfig; - } - - return isLocal - ? getConfig(configSources?.local) - : getConfig(configSources?.global); -} - async function handleConfigAction( keys: string[], cmdOptions: ConfigOptions, spinner: TSpinner, ): Promise { const { global: isGlobal, set: bulkSetValues } = cmdOptions; - - const config = await getSettingsConfig(!!isGlobal); spinner.stop(); if (bulkSetValues && bulkSetValues.length > 0) { - if (bulkSetValues.length % 2 !== 0) { - spinner.fail( - logger.colors.redBright(t("errors.command.set_invalid_format")), - ); - return; - } - for (let i = 0; i < bulkSetValues.length; i += 2) { - const bulkKey = bulkSetValues[i]; - const bulkValue = bulkSetValues[i + 1]; - await handleNonInteractiveSettingsUpdate(bulkKey, bulkValue, !!isGlobal); - } - spinner.succeed(logger.colors.green(t("messages.success.config_updated"))); + await handleSetAction(bulkSetValues, !!isGlobal, spinner); return; } if (keys && keys.length > 0) { - keys.forEach((key) => { - const configValue = config.settings[key as keyof typeof config.settings]; - if (configValue !== undefined) { - logger.log(logger.colors.yellowBold(key) + ": " + configValue); - } else { - logger.log( - logger.colors.redBright( - t("errors.config.get_key_not_found", { key }), - ), - ); - } - }); - spinner.succeed(logger.colors.green(t("messages.success.config_read"))); + await handleGetAction(keys, !!isGlobal, spinner); return; } diff --git a/packages/devkit/src/commands/config/logic.ts b/packages/devkit/src/commands/config/logic.ts index bbcaad7..4f543e5 100644 --- a/packages/devkit/src/commands/config/logic.ts +++ b/packages/devkit/src/commands/config/logic.ts @@ -62,7 +62,7 @@ export async function handleNonInteractiveSettingsUpdate( configAliases as Record )[key]; - validateConfigValue(canonicalKey, value); + validateConfigValue(key, value); (config.settings[canonicalKey] as unknown) = value; diff --git a/packages/devkit/src/commands/config/set/index.ts b/packages/devkit/src/commands/config/set/index.ts new file mode 100644 index 0000000..8db9af2 --- /dev/null +++ b/packages/devkit/src/commands/config/set/index.ts @@ -0,0 +1,24 @@ +import { t } from "#utils/i18n/translator.js"; +import { logger, type TSpinner } from "#utils/logger.js"; +import { handleNonInteractiveSettingsUpdate } from "../logic.js"; + +export async function handleSetAction( + bulkSetValues: string[], + isGlobal: boolean, + spinner: TSpinner, +): Promise { + if (bulkSetValues.length % 2 !== 0) { + spinner.fail( + logger.colors.redBright(t("errors.command.set_invalid_format")), + ); + return; + } + + for (let i = 0; i < bulkSetValues.length; i += 2) { + const bulkKey = bulkSetValues[i]; + const bulkValue = bulkSetValues[i + 1]; + + await handleNonInteractiveSettingsUpdate(bulkKey, bulkValue, isGlobal); + } + spinner.succeed(logger.colors.green(t("messages.success.config_updated"))); +} diff --git a/packages/devkit/src/commands/config/utils.ts b/packages/devkit/src/commands/config/utils.ts new file mode 100644 index 0000000..3255698 --- /dev/null +++ b/packages/devkit/src/commands/config/utils.ts @@ -0,0 +1,35 @@ +import type { CliConfig } from "#utils/schema/schema.js"; +import { readConfigSources } from "#core/config/loader.js"; +import { configAliases } from "#utils/validations/configAliases"; + +export const CONFIG_KEY_ALIASES = configAliases; + +export function resolveKeys( + keys: string[], +): Array { + return keys.map((key) => { + const alias = CONFIG_KEY_ALIASES[key]; + return (alias || key) as keyof CliConfig["settings"]; + }); +} + +export function resolveSingleKey(key: string): keyof CliConfig["settings"] { + const alias = CONFIG_KEY_ALIASES[key]; + return (alias || key) as keyof CliConfig["settings"]; +} + +export async function getSettingsConfig(isGlobal: boolean): Promise { + const isLocal = !isGlobal; + const configSources = await readConfigSources({ + forceGlobal: isGlobal, + forceLocal: isLocal, + }); + + function getConfig(config: CliConfig | null): CliConfig { + return (config || { settings: {} }) as CliConfig; + } + + return isLocal + ? getConfig(configSources?.local) + : getConfig(configSources?.global); +} diff --git a/packages/devkit/src/utils/validations/configAliases.ts b/packages/devkit/src/utils/validations/configAliases.ts index 3747aa5..78099a8 100644 --- a/packages/devkit/src/utils/validations/configAliases.ts +++ b/packages/devkit/src/utils/validations/configAliases.ts @@ -7,4 +7,5 @@ export const configAliases: Record = { cacheStrategy: "cacheStrategy", language: "language", lg: "language", + lang: "language", };