diff --git a/.changeset/better-geese-scream.md b/.changeset/better-geese-scream.md new file mode 100644 index 0000000..6a60952 --- /dev/null +++ b/.changeset/better-geese-scream.md @@ -0,0 +1,5 @@ +--- +"scaffolder-toolkit": patch +--- + +refactor: restructure json translation for better organization diff --git a/packages/devkit/TODO.md b/packages/devkit/TODO.md index 9c88a48..a5078c9 100644 --- a/packages/devkit/TODO.md +++ b/packages/devkit/TODO.md @@ -69,7 +69,7 @@ This document tracks all planned and completed tasks for the Dev Kit project. - [x] **Centralize Utilities**: Move `chalk` and `ora` to a single, centralized file for better code organization. - [x] Enable GitHub discussions - [x] Refactor and restructure the utilities -- [ ] Better json structure for languages +- [x] Better json structure for languages translation #### Multi-Language Support diff --git a/packages/devkit/__tests__/integrations/config/add.spec.ts b/packages/devkit/__tests__/integrations/config/add.spec.ts index 1edfd6e..e9d92bf 100644 --- a/packages/devkit/__tests__/integrations/config/add.spec.ts +++ b/packages/devkit/__tests__/integrations/config/add.spec.ts @@ -206,7 +206,7 @@ describe("dk config add", () => { ); }); - it("should fail to add a template if a language is not found", async () => { + it("should fail to add a template if the programming language language is not found", async () => { await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig); const { exitCode, all } = await execute( "bun", @@ -226,7 +226,7 @@ describe("dk config add", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Invalid value for language. Valid options are: javascript", + "[DEV]>> Devkit encountered an unexpected internal issue: Invalid value for Programming Language. Valid options are: javascript", ); }); @@ -250,7 +250,7 @@ describe("dk config add", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Template 'react-ts' already exists in the configuration. Use 'devkit config set' to update it.", + "::[DEV]>> Devkit encountered an unexpected internal issue: Template 'react-ts' already exists in the configuration. Use 'devkit config set' to update it.", ); }); @@ -283,7 +283,7 @@ describe("dk config add", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Alias 'rt' already exists for another template in this language. Please choose a different alias.", + "::[DEV]>> Devkit encountered an unexpected internal issue: Alias 'rt' already exists for another template in this language. Please choose a different alias.", ); }); }); diff --git a/packages/devkit/__tests__/integrations/config/index.spec.ts b/packages/devkit/__tests__/integrations/config/index.spec.ts index 5b4f147..35ee0ae 100644 --- a/packages/devkit/__tests__/integrations/config/index.spec.ts +++ b/packages/devkit/__tests__/integrations/config/index.spec.ts @@ -100,7 +100,7 @@ describe("dk config", () => { expect(exitCode).toBe(0); expect(all).toContain("language: fr"); - expect(all).toContain("Configuration chargée avec succès !"); + expect(all).toContain("Configuration mise à jour avec succès !"); }); it("should get multiple settings from the local config", async () => { @@ -114,7 +114,7 @@ describe("dk config", () => { expect(exitCode).toBe(0); expect(all).toContain("language: fr"); expect(all).toContain("cacheStrategy: always-refresh"); - expect(all).toContain("Configuration chargée avec succès !"); + expect(all).toContain("Configuration mise à jour avec succès !"); }); it("should get a setting from the global config with --global flag", async () => { @@ -128,7 +128,7 @@ describe("dk config", () => { expect(exitCode).toBe(0); expect(all).toContain("language: en"); - expect(all).toContain("Configuration chargée avec succès !"); + expect(all).toContain("Configuration mise à jour avec succès !"); }); it("should set a single setting in the local config", async () => { @@ -201,7 +201,7 @@ describe("dk config", () => { expect(exitCode).toBe(0); expect(all).toContain( - "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (par ex. --set key1 value1 key2 value2).", + "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (par ex., --set key1 value1 key2 value2).", ); }); @@ -228,7 +228,7 @@ describe("dk config", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: No local configuration file found. Run 'devkit config init --local' to create one.", + "::[DEV]>> Devkit encountered an unexpected internal issue: No local configuration file found. Run 'devkit config init --local' to create one.", ); }); }); diff --git a/packages/devkit/__tests__/integrations/config/list.spec.ts b/packages/devkit/__tests__/integrations/config/list.spec.ts index 50f2cfb..e81a890 100644 --- a/packages/devkit/__tests__/integrations/config/list.spec.ts +++ b/packages/devkit/__tests__/integrations/config/list.spec.ts @@ -199,7 +199,7 @@ describe("dk config list", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Global configuration file not found.", + "::[DEV]>> Devkit encountered an unexpected internal issue: Global configuration file not found.", ); }); diff --git a/packages/devkit/__tests__/integrations/config/remove.spec.ts b/packages/devkit/__tests__/integrations/config/remove.spec.ts index e86cb65..05966a3 100644 --- a/packages/devkit/__tests__/integrations/config/remove.spec.ts +++ b/packages/devkit/__tests__/integrations/config/remove.spec.ts @@ -191,7 +191,7 @@ describe("dk config remove", () => { "Successfully removed 1 template(s) (vue-basic) from javascript.", ); expect(all).toContain( - "⚠️ Warning: The following templates were not found: not-found", + "⚠️ The following templates were not found: not-found", ); expect( updatedConfig.templates.javascript.templates["vue-basic"], @@ -215,7 +215,7 @@ describe("dk config remove", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Template 'not-found-1, not-found-2' not found in configuration.", + "::[DEV]>> Devkit encountered an unexpected internal issue: Template 'not-found-1, not-found-2' not found in configuration.", ); }); @@ -229,7 +229,7 @@ describe("dk config remove", () => { expect(exitCode).toBe(1); expect(all).toContain( - "❌ Devkit encountered an unexpected internal issue: Invalid value for language. Valid options are: javascript", + "::[DEV]>> Devkit encountered an unexpected internal issue: Invalid value for Programming Language. Valid options are: javascript", ); }); diff --git a/packages/devkit/__tests__/integrations/config/update.spec.ts b/packages/devkit/__tests__/integrations/config/update.spec.ts index 432d9d8..6a55aa8 100644 --- a/packages/devkit/__tests__/integrations/config/update.spec.ts +++ b/packages/devkit/__tests__/integrations/config/update.spec.ts @@ -306,7 +306,7 @@ describe("dk config update", () => { expect(exitCode).toBe(1); expect(all).toContain( - "Failed to update 'ts-template': Programming language 'typescript' not found in configuration", + "Failed to update 'ts-template': Invalid value for Programming Language. Valid options are: javascript", ); }); diff --git a/packages/devkit/__tests__/integrations/list.spec.ts b/packages/devkit/__tests__/integrations/list.spec.ts index 9fbaa07..d7269f3 100644 --- a/packages/devkit/__tests__/integrations/list.spec.ts +++ b/packages/devkit/__tests__/integrations/list.spec.ts @@ -106,7 +106,7 @@ describe("dk list", () => { }); expect(exitCode).toBe(0); - expect(all).toContain("Using templates from local configuration."); + expect(all).toContain("Using local configuration."); expect(all).toContain("Available Templates:"); expect(all).toContain("JAVASCRIPT"); expect(all).toContain("NODE"); @@ -172,7 +172,7 @@ describe("dk list", () => { ); expect(exitCode).toBe(0); - expect(all).toContain("Using templates from local configuration."); + expect(all).toContain("Using local configuration."); expect(all).toContain("JAVASCRIPT"); expect(all).toContain("react-ts"); expect(all).toContain("vue-basic"); @@ -196,7 +196,7 @@ describe("dk list", () => { ); expect(exitCode).toBe(0); - expect(all).toContain("Using templates from local configuration."); + expect(all).toContain("Using local configuration."); expect(all).toContain("JAVASCRIPT"); expect(all).toContain("vue-basic"); expect(all).not.toContain("react-ts"); @@ -221,7 +221,7 @@ describe("dk list", () => { ); expect(exitCode).toBe(0); - expect(all).toContain("Using templates from local configuration."); + expect(all).toContain("Using local configuration."); expect(all).toContain("JAVASCRIPT"); expect(all).toContain("react-ts"); expect(all).not.toContain("vue-basic"); @@ -239,7 +239,7 @@ describe("dk list", () => { expect(exitCode).toBe(1); expect(all).toContain( - "Invalid value for language. Valid options are: javascript", + "::[DEV]>> Devkit encountered an unexpected internal issue: Invalid value for Programming Language. Valid options are: javascript", ); }); diff --git a/packages/devkit/__tests__/units/commands/config/add.spec.ts b/packages/devkit/__tests__/units/commands/config/add.spec.ts index de3b154..dbec6ea 100644 --- a/packages/devkit/__tests__/units/commands/config/add.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/add.spec.ts @@ -15,7 +15,7 @@ const { mockValidateProgrammingLanguage: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; vi.mock("#utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, @@ -36,6 +36,14 @@ vi.mock("../../../../src/commands/config/validate-and-save.js", () => ({ describe("setupAddCommand", () => { let mockConfigCommand: any; + const ADD_DESC_KEY = "commands.template.add.description"; + const DESC_OPT_KEY = "commands.template.add.options.description"; + const LOC_OPT_KEY = "commands.template.add.prompts.location"; + const ALIAS_OPT_KEY = "commands.template.add.options.alias"; + const CACHE_OPT_KEY = "commands.template.add.options.cache"; + const PM_OPT_KEY = "commands.template.add.options.package_manager"; + const MISSING_REQUIRED_KEY = "errors.command.missing_required_options"; + beforeEach(() => { vi.clearAllMocks(); actionFn = vi.fn(); @@ -59,31 +67,33 @@ describe("setupAddCommand", () => { expect(mockConfigCommand.alias).toHaveBeenCalledWith("a"); expect(mockConfigCommand.description).toHaveBeenCalledWith( - mocktFn("cli.add_template.description"), + mocktFn(ADD_DESC_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-d, --description ", - mocktFn("cli.add_template.options.description"), + mocktFn(DESC_OPT_KEY), "", ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-o, --location ", - mocktFn("new.project.template.option.description"), + mocktFn(LOC_OPT_KEY), "", ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-a, --alias ", - mocktFn("cli.add_template.options.alias"), + mocktFn(ALIAS_OPT_KEY), "", ); + expect(mockConfigCommand.option).toHaveBeenCalledWith( "-c, --cache-strategy ", - mocktFn("cli.add_template.options.cache"), + mocktFn(CACHE_OPT_KEY), "", ); + expect(mockConfigCommand.option).toHaveBeenCalledWith( "-p, --package-manager ", - mocktFn("cli.add_template.options.package_manager"), + mocktFn(PM_OPT_KEY), "", ); }); @@ -141,7 +151,7 @@ describe("setupAddCommand", () => { expect(mockHandleErrorAndExit).toHaveBeenCalledOnce(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.missing_required_options.add_template", { + mocktFn(MISSING_REQUIRED_KEY, { fields: "--description, --location", }), ), @@ -157,7 +167,7 @@ describe("setupAddCommand", () => { expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.missing_required_options.add_template", { + mocktFn(MISSING_REQUIRED_KEY, { fields: "--description, --location", }), ), diff --git a/packages/devkit/__tests__/units/commands/config/index.spec.ts b/packages/devkit/__tests__/units/commands/config/index.spec.ts index db98db9..7fb3439 100644 --- a/packages/devkit/__tests__/units/commands/config/index.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/index.spec.ts @@ -54,6 +54,16 @@ describe("setupConfigCommand", () => { let mockProgram: any; let mockAction: (keys: string[], cmdOptions: any) => Promise; + const DESC_KEY = "commands.config.command.description"; + const GLOBAL_OPT_KEY = "commands.config.set.option.global"; + const SET_BULK_OPT_KEY = "commands.config.set.option.bulk"; + const NO_COMMAND_WARN_KEY = "warnings.no_command_provided"; + const SET_SUCCESS_KEY = "messages.success.config_updated"; + const GET_SUCCESS_KEY = "messages.success.config_updated"; + 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(() => { vi.clearAllMocks(); mockProgram = { @@ -73,17 +83,16 @@ describe("setupConfigCommand", () => { expect(mockProgram.command).toHaveBeenCalledWith("config [keys...]"); expect(mockProgram.alias).toHaveBeenCalledWith("conf"); - expect(mockProgram.description).toHaveBeenCalledWith( - mocktFn("config.command.description"), - ); + // Updated translation key + expect(mockProgram.description).toHaveBeenCalledWith(mocktFn(DESC_KEY)); expect(mockProgram.option).toHaveBeenCalledWith( "-g, --global", - mocktFn("config.update.option.global"), + mocktFn(GLOBAL_OPT_KEY), false, ); expect(mockProgram.option).toHaveBeenCalledWith( "-s, --set ", - mocktFn("config.set.option.bulk"), + mocktFn(SET_BULK_OPT_KEY), false, ); expect(mockSetupAddCommand).toHaveBeenCalledWith(mockProgram); @@ -93,7 +102,7 @@ describe("setupConfigCommand", () => { }); describe("action handler", () => { - it("should call handleInteractiveConfig in interactive mode", async () => { + it("should default to warning if no keys or options are provided (non-interactive mode)", async () => { const mockConfig = { settings: {}, templates: {} }; mockReadAndMergeConfigs.mockResolvedValue({ config: mockConfig, @@ -103,14 +112,17 @@ describe("setupConfigCommand", () => { setupConfigCommand(mockProgram); await mockAction([], { global: false }); + expect(mockSpinner.start).toHaveBeenCalledWith( + mockLogger.colors.cyan(mocktFn(CONFIG_LOADING_KEY)), + ); expect(mockReadAndMergeConfigs).toHaveBeenCalledWith({ forceGlobal: false, }); - expect(mockSpinner.start).toHaveBeenCalledOnce(); expect(mockSpinner.warn).toHaveBeenCalledOnce(); + expect(mockSpinner.warn).toHaveBeenCalledWith( - mockLogger.colors.green("warning.no_command_or_option_provided"), + mocktFn(NO_COMMAND_WARN_KEY), ); }); @@ -122,7 +134,10 @@ describe("setupConfigCommand", () => { setupConfigCommand(mockProgram); - const cmdOptions = { set: ["language", "typescript"], global: false }; + const cmdOptions = { + set: ["language", "typescript", "pm", "bun"], + global: false, + }; await mockAction([], cmdOptions); expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( @@ -130,8 +145,15 @@ describe("setupConfigCommand", () => { "typescript", false, ); + + expect(mockHandleNonInteractiveSettingsUpdate).toHaveBeenCalledWith( + "pm", + "bun", + false, + ); + expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green("config.set.success"), + mockLogger.colors.green(mocktFn(SET_SUCCESS_KEY)), ); }); @@ -147,12 +169,13 @@ describe("setupConfigCommand", () => { await mockAction([], cmdOptions); expect(mockHandleNonInteractiveSettingsUpdate).not.toHaveBeenCalled(); + expect(mockSpinner.fail).toHaveBeenCalledWith( - mockLogger.colors.redBright("error.command.set.invalid_format"), + mockLogger.colors.redBright(mocktFn(INVALID_FORMAT_KEY)), ); }); - it("should print a single config value when a key is provided", async () => { + it("should print a single config value when a key is provided (GET functionality)", async () => { const mockConfig = { settings: { language: "typescript" }, templates: {}, @@ -168,12 +191,13 @@ describe("setupConfigCommand", () => { expect(mockLogger.log).toHaveBeenCalledWith( mockLogger.colors.yellowBold("language") + ": " + "typescript", ); + expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green("config.get.success"), + mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), ); }); - it("should print multiple config values when multiple keys are provided", async () => { + it("should print multiple config values when multiple keys are provided (GET functionality)", async () => { const mockConfig = { settings: { language: "typescript", @@ -195,12 +219,13 @@ describe("setupConfigCommand", () => { expect(mockLogger.log).toHaveBeenCalledWith( mockLogger.colors.yellowBold("packageManager") + ": " + "bun", ); + expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green("config.get.success"), + mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), ); }); - it("should handle a non-existent key gracefully", async () => { + it("should handle a non-existent key gracefully (GET functionality)", async () => { const mockConfig = { settings: {}, templates: {} }; mockReadAndMergeConfigs.mockResolvedValue({ config: mockConfig, @@ -212,13 +237,13 @@ describe("setupConfigCommand", () => { expect(mockLogger.log).toHaveBeenCalledWith( mockLogger.colors.redBright( - mocktFn("config.get.not_found", { + mocktFn(GET_NOT_FOUND_KEY, { key: "nonexistent_key", }), ), ); expect(mockSpinner.succeed).toHaveBeenCalledWith( - mockLogger.colors.green("config.get.success"), + mockLogger.colors.green(mocktFn(GET_SUCCESS_KEY)), ); }); diff --git a/packages/devkit/__tests__/units/commands/config/list.spec.ts b/packages/devkit/__tests__/units/commands/config/list.spec.ts index 725fa3b..2573771 100644 --- a/packages/devkit/__tests__/units/commands/config/list.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/list.spec.ts @@ -15,7 +15,7 @@ const { mockPrintTemplates: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; const mockParent = { opts: vi.fn(() => ({ global: false })), }; @@ -38,6 +38,17 @@ const consoleLogSpy = mockLogger.log; describe("setupListCommand", () => { let mockConfigCommand: any; + const CONFIG_LIST_DESC = "commands.config.list.command.description"; + const CONFIG_LIST_ALL_OPT = "commands.config.list.options.all"; + const CONFIG_SOURCE_LOCAL = "messages.status.config_source_local"; + const CONFIG_SOURCE_GLOBAL = "messages.status.config_source_global"; + const CONFIG_SOURCE_MERGED = "messages.status.config_source_local_and_global"; + const TEMPLATES_NOT_FOUND = "warnings.template_not_found"; + const ERR_GLOBAL_NOT_FOUND = "errors.config.global_not_found"; + const ERR_MUTUALLY_EXCLUSIVE = "errors.command.mutually_exclusive_options"; + const SETTINGS_HEADER = "commands.config.list.settings_header"; + const TEMPLATES_HEADER = "commands.config.list.templates_header"; + const sampleConfig = { settings: { language: "typescript", @@ -83,12 +94,13 @@ describe("setupListCommand", () => { setupListCommand(mockConfigCommand); expect(mockConfigCommand.command).toHaveBeenCalledWith("list"); expect(mockConfigCommand.alias).toHaveBeenCalledWith("ls"); + expect(mockConfigCommand.description).toHaveBeenCalledWith( - mocktFn("list.command.description"), + mocktFn(CONFIG_LIST_DESC), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-a, --all", - mocktFn("list.command.all.option"), + mocktFn(CONFIG_LIST_ALL_OPT), ); }); @@ -103,13 +115,20 @@ describe("setupListCommand", () => { await actionFn({}, { parent: mockParent }); expect(mockSpinner.start).toHaveBeenCalled(); + expect(mockSpinner.info).toHaveBeenCalledWith( - mocktFn("config.get.source.local"), + mocktFn(CONFIG_SOURCE_LOCAL), ); - expect(mockLogger.log).toHaveBeenCalled(); + expect(consoleLogSpy).toHaveBeenCalledWith( + mockLogger.colors.bold("\n" + mocktFn(SETTINGS_HEADER)), + ); expect(mockPrintSettings).toHaveBeenCalledOnce(); expect(mockPrintSettings).toHaveBeenCalledWith(sampleConfig.settings); + + expect(consoleLogSpy).toHaveBeenCalledWith( + mockLogger.colors.bold("\n" + mocktFn(TEMPLATES_HEADER)), + ); expect(mockPrintTemplates).toHaveBeenCalledWith( "javascript", sampleConfig.templates.javascript.templates, @@ -118,6 +137,7 @@ describe("setupListCommand", () => { "typescript", sampleConfig.templates.typescript.templates, ); + expect(mockSpinner.stop).toHaveBeenCalled(); }); it("should display a global configuration with --global flag", async () => { @@ -131,8 +151,9 @@ describe("setupListCommand", () => { await actionFn({}, { parent: mockParent }); expect(mockSpinner.start).toHaveBeenCalled(); + expect(mockSpinner.info).toHaveBeenCalledWith( - mocktFn("config.get.source.global"), + mocktFn(CONFIG_SOURCE_GLOBAL), ); expect(mockPrintSettings).toHaveBeenCalledWith(sampleConfig.settings); expect(mockPrintTemplates).toHaveBeenCalledWith( @@ -151,8 +172,9 @@ describe("setupListCommand", () => { await actionFn({ all: true }, { parent: mockParent }); expect(mockSpinner.start).toHaveBeenCalled(); + expect(mockSpinner.info).toHaveBeenCalledWith( - mocktFn("config.get.source.local_and_global"), + mocktFn(CONFIG_SOURCE_MERGED), ); expect(mockPrintSettings).toHaveBeenCalledWith(sampleConfig.settings); expect(mockPrintTemplates).toHaveBeenCalledWith( @@ -176,11 +198,11 @@ describe("setupListCommand", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.info).toHaveBeenCalledWith( - mocktFn("config.get.source.local"), + mocktFn(CONFIG_SOURCE_LOCAL), ); expect(mockPrintSettings).toHaveBeenCalledWith({}); expect(consoleLogSpy).toHaveBeenCalledWith( - mockLogger.colors.yellow(mocktFn("list.templates.not_found")), + mockLogger.colors.yellow(mocktFn(TEMPLATES_NOT_FOUND)), ); expect(mockPrintTemplates).not.toHaveBeenCalled(); }); @@ -197,7 +219,7 @@ describe("setupListCommand", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( - new DevkitError(mocktFn("error.config.global.not.found")), + new DevkitError(mocktFn(ERR_GLOBAL_NOT_FOUND)), mockSpinner, ); }); @@ -212,7 +234,7 @@ describe("setupListCommand", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.command.mutually_exclusive_options", { + mocktFn(ERR_MUTUALLY_EXCLUSIVE, { options: "global, all", }), ), diff --git a/packages/devkit/__tests__/units/commands/config/logic.spec.ts b/packages/devkit/__tests__/units/commands/config/logic.spec.ts index 8db0202..640edcb 100644 --- a/packages/devkit/__tests__/units/commands/config/logic.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/logic.spec.ts @@ -3,21 +3,45 @@ import { handleNonInteractiveSettingsUpdate, handleNonInteractiveTemplateUpdate, } from "../../../../src/commands/config/logic.js"; -import { VALID_CACHE_STRATEGIES } from "../../../../src/utils/schema/schema.js"; +import { + VALID_CACHE_STRATEGIES, + PackageManagers, +} from "../../../../src/utils/schema/schema.js"; import deepmerge from "deepmerge"; import { DevkitError } from "../../../../src/utils/errors/base.js"; import { mocktFn } from "../../../../vitest.setup.js"; +import { + validateAlias, + validateDescription, + validateLocation, +} from "../../../../src/utils/validations/templates.js"; +import { + validatePackageManager, + validateCacheStrategy, +} from "../../../../src/utils/validations/config.js"; const { mockReadAndMergeConfigs, mockSaveGlobalConfig, mockSaveLocalConfig, mockValidateConfigValue, + mockValidateAlias, + mockValidateDescription, + mockValidateLocation, + mockValidatePackageManager, + mockValidateCacheStrategy, + mockValidateProgrammingLanguage, } = vi.hoisted(() => ({ mockReadAndMergeConfigs: vi.fn(), mockSaveGlobalConfig: vi.fn(), mockSaveLocalConfig: vi.fn(), mockValidateConfigValue: vi.fn(), + mockValidateAlias: vi.fn(), + mockValidateDescription: vi.fn(), + mockValidateLocation: vi.fn(), + mockValidatePackageManager: vi.fn(), + mockValidateCacheStrategy: vi.fn(), + mockValidateProgrammingLanguage: vi.fn(), })); vi.mock("#core/config/loader.js", () => ({ @@ -33,7 +57,21 @@ vi.mock("#utils/validations/validateConfigValue.js", () => ({ validateConfigValue: mockValidateConfigValue, })); -vi.mock("deepmerge"); +vi.mock("#utils/validations/templates.js", () => ({ + validateAlias: mockValidateAlias, + validateDescription: mockValidateDescription, + validateLocation: mockValidateLocation, +})); + +vi.mock("#utils/validations/config.js", () => ({ + validatePackageManager: mockValidatePackageManager, + validateCacheStrategy: mockValidateCacheStrategy, + validateProgrammingLanguage: mockValidateProgrammingLanguage, +})); + +vi.mock("deepmerge", () => ({ + default: vi.fn((target, source) => ({ ...target, ...source })), +})); describe("Non-interactive Config Logic", () => { const baseConfig = { @@ -62,6 +100,11 @@ describe("Non-interactive Config Logic", () => { beforeEach(() => { vi.clearAllMocks(); + mockValidateAlias.mockReturnValue(undefined); + mockValidateDescription.mockReturnValue(undefined); + mockValidateLocation.mockReturnValue(undefined); + mockValidatePackageManager.mockReturnValue(undefined); + mockValidateCacheStrategy.mockReturnValue(undefined); }); describe("handleNonInteractiveSettingsUpdate", () => { @@ -117,7 +160,9 @@ describe("Non-interactive Config Logic", () => { await expect( handleNonInteractiveSettingsUpdate("language", "fr", false), - ).rejects.toThrow(new DevkitError("error.config.local.not.found")); + ).rejects.toThrow( + new DevkitError(mocktFn("errors.config.local_not_found")), + ); expect(mockReadAndMergeConfigs).toHaveBeenCalledOnce(); expect(mockReadAndMergeConfigs).toHaveBeenCalledWith({ @@ -139,19 +184,19 @@ describe("Non-interactive Config Logic", () => { expect(mockSaveLocalConfig).toHaveBeenCalledOnce(); const updatedConfig = mockSaveLocalConfig.mock.calls[0]![0]; expect(updatedConfig.settings.defaultPackageManager).toBe("bun"); + expect(mockValidateConfigValue).toHaveBeenCalledWith( + "defaultPackageManager", + "bun", + ); }); }); describe("handleNonInteractiveTemplateUpdate", () => { - it("should update a single template property successfully", async () => { + it("should update a single template property successfully (calls validation)", async () => { mockReadAndMergeConfigs.mockResolvedValue({ - config: baseConfig, + config: structuredClone(baseConfig), source: "local", }); - vi.mocked(deepmerge).mockReturnValueOnce({ - ...baseConfig.templates.typescript.templates.web, - description: "A new description", - }); await handleNonInteractiveTemplateUpdate( "typescript", @@ -160,6 +205,8 @@ describe("Non-interactive Config Logic", () => { false, ); + expect(mockValidateDescription).toHaveBeenCalledWith("A new description"); + expect(mockSaveLocalConfig).toHaveBeenCalled(); const updatedConfig = mockSaveLocalConfig.mock.calls[0]![0]; expect(updatedConfig.templates.typescript.templates.web.description).toBe( @@ -167,14 +214,11 @@ describe("Non-interactive Config Logic", () => { ); }); - it("should delete a template property if value is 'null'", async () => { + it("should delete a template property if value is 'null' (alias)", async () => { mockReadAndMergeConfigs.mockResolvedValue({ config: structuredClone(baseConfig), source: "local", }); - vi.mocked(deepmerge).mockReturnValue({ - ...baseConfig.templates.typescript.templates.web, - }); await handleNonInteractiveTemplateUpdate( "typescript", @@ -183,6 +227,8 @@ describe("Non-interactive Config Logic", () => { false, ); + expect(mockValidateAlias).not.toHaveBeenCalled(); + expect(mockSaveLocalConfig).toHaveBeenCalled(); const updatedConfig = mockSaveLocalConfig.mock.calls[0]![0]; expect( @@ -191,13 +237,11 @@ describe("Non-interactive Config Logic", () => { }); it("should rename a template successfully", async () => { + const initialConfig = structuredClone(baseConfig); mockReadAndMergeConfigs.mockResolvedValue({ - config: structuredClone(baseConfig), + config: initialConfig, source: "local", }); - vi.mocked(deepmerge).mockReturnValue({ - ...baseConfig.templates.typescript.templates.web, - }); await handleNonInteractiveTemplateUpdate( "typescript", @@ -214,39 +258,217 @@ describe("Non-interactive Config Logic", () => { expect(updatedConfig.templates.typescript.templates.web).toBeUndefined(); }); + it("should throw an error if local config is not found", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: structuredClone(baseConfig), + source: "default", + }); + mockValidateProgrammingLanguage.mockReturnValueOnce(undefined); + + await expect( + handleNonInteractiveTemplateUpdate( + "javascript", + "web", + { newName: "new-name" }, + false, + ), + ).rejects.toThrow( + new DevkitError(mocktFn("errors.config.local_not_found")), + ); + + expect(mockReadAndMergeConfigs).toHaveBeenCalledOnce(); + expect(mockReadAndMergeConfigs).toHaveBeenCalledWith({ + forceGlobal: false, + }); + + expect(mockValidateConfigValue).not.toHaveBeenCalledOnce(); + }); + + it("should throw an error if there is no template inside fro this programming language", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: { + ...structuredClone(baseConfig), + templates: {}, + }, + source: "local", + }); + mockValidateProgrammingLanguage.mockReturnValueOnce(undefined); + + await expect( + handleNonInteractiveTemplateUpdate( + "javascript", + "web", + { newName: "new-name" }, + false, + ), + ).rejects.toThrow( + new DevkitError( + mocktFn("errors.template.language_not_found", { + language: "javascript", + }), + ), + ); + + expect(mockReadAndMergeConfigs).toHaveBeenCalledOnce(); + expect(mockReadAndMergeConfigs).toHaveBeenCalledWith({ + forceGlobal: false, + }); + + expect(mockValidateConfigValue).not.toHaveBeenCalledOnce(); + }); + it("should throw an error for an invalid template name", async () => { mockReadAndMergeConfigs.mockResolvedValue({ - config: baseConfig, + config: structuredClone(baseConfig), source: "local", }); await expect( handleNonInteractiveTemplateUpdate("typescript", "unknown", {}, false), ).rejects.toThrow( - new DevkitError("error.template.not_found- options template:unknown"), + new DevkitError( + mocktFn("errors.template.not_found", { template: "unknown" }), + ), ); }); - it("should throw an error for an invalid cache strategy", async () => { + it("should throw an error for an invalid cache strategy value", async () => { mockReadAndMergeConfigs.mockResolvedValue({ - config: baseConfig, + config: structuredClone(baseConfig), source: "local", }); + const cacheStrategyError = new DevkitError( + mocktFn("errors.validation.invalid_cache_strategy", { + value: "invalid_strategy", + options: VALID_CACHE_STRATEGIES.join(", "), + }), + ); + mockValidateCacheStrategy.mockImplementationOnce(() => { + throw cacheStrategyError; + }); + await expect( handleNonInteractiveTemplateUpdate( "typescript", "web", - { cacheStrategy: "invalid_strategy" as "null" }, + { cacheStrategy: "invalid_strategy" }, false, ), - ).rejects.toThrow( - new DevkitError( - mocktFn("error.invalid.value", { - key: "cacheStrategy", - options: VALID_CACHE_STRATEGIES.join(", "), - }), + ).rejects.toThrow(cacheStrategyError); + expect(mockValidateCacheStrategy).toHaveBeenCalledWith( + "invalid_strategy", + ); + }); + + it("should throw an error for an invalid location value", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: structuredClone(baseConfig), + source: "local", + }); + + const locationError = new DevkitError( + mocktFn("errors.validation.invalid_location", { + value: "location", + }), + ); + mockValidateLocation.mockImplementationOnce(() => { + throw locationError; + }); + + await expect( + handleNonInteractiveTemplateUpdate( + "typescript", + "web", + { + description: "A great description", + cacheStrategy: "daily" as "null", + location: "location", + }, + false, ), + ).rejects.toThrow(locationError); + expect(mockValidateLocation).toHaveBeenCalledWith("location"); + }); + + it("should throw an error for an invalid alias value", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: structuredClone(baseConfig), + source: "local", + }); + + const aliasError = new DevkitError( + mocktFn("errors.validation.invalid_alias", { + alias: "a", + }), + ); + mockValidateAlias.mockImplementationOnce(() => { + throw aliasError; + }); + + await expect( + handleNonInteractiveTemplateUpdate( + "typescript", + "web", + { + description: "A great description", + cacheStrategy: "daily" as "null", + location: "location", + alias: "a", + }, + false, + ), + ).rejects.toThrow(aliasError); + expect(mockValidateAlias).toHaveBeenCalledWith("a"); + }); + + it("should throw an error for an invalid 'packageManager' value", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: structuredClone(baseConfig), + source: "local", + }); + + const pmError = new DevkitError( + mocktFn("errors.validation.invalid_pm", { + packageManager: "pm", + }), + ); + mockValidatePackageManager.mockImplementationOnce(() => { + throw pmError; + }); + + await expect( + handleNonInteractiveTemplateUpdate( + "typescript", + "web", + { + description: "A great description", + cacheStrategy: "daily" as "null", + location: "location", + alias: "a", + packageManager: "pm", + }, + false, + ), + ).rejects.toThrow(pmError); + expect(mockValidatePackageManager).toHaveBeenCalledWith("pm"); + }); + + it("should throw an error if the new name already exists", async () => { + mockReadAndMergeConfigs.mockResolvedValue({ + config: structuredClone(baseConfig), + source: "local", + }); + + await expect( + handleNonInteractiveTemplateUpdate( + "typescript", + "web", + { newName: "cli" }, + false, + ), + ).rejects.toThrow( + new DevkitError(mocktFn("errors.template.exists", { template: "cli" })), ); }); }); diff --git a/packages/devkit/__tests__/units/commands/config/prompts.spec.ts b/packages/devkit/__tests__/units/commands/config/prompts.spec.ts index dfcd1b9..093a3fa 100644 --- a/packages/devkit/__tests__/units/commands/config/prompts.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/prompts.spec.ts @@ -1,10 +1,10 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { handleInteractiveConfig } from "../../../../src/commands/config/prompts.ts"; +import { handleInteractiveConfig } from "../../../../src/commands/config/prompts.js"; import { handleNonInteractiveSettingsUpdate, handleNonInteractiveTemplateUpdate, -} from "../../../../src/commands/config/logic.ts"; -import { mockLogger, mocktFn } from "../../../../vitest.setup.ts"; +} from "../../../../src/commands/config/logic.js"; +import { mockLogger, mocktFn } from "../../../../vitest.setup.js"; const { mockSelect, @@ -36,6 +36,10 @@ vi.mock("#core/prompts/prompts.js", () => ({ promptForPackageManager: mockPForPackageManager, })); +const SUCCESS_CONFIG_UPDATED_KEY = "messages.success.config_updated"; +const SUCCESS_TEMPLATE_UPDATED_KEY = "messages.success.template_updated"; +const INTERACTIVE_SUCCESS_KEY = "commands.config.interactive.success"; + describe("Interactive Config Prompts", () => { const baseConfig = { settings: { @@ -69,7 +73,7 @@ describe("Interactive Config Prompts", () => { vi.restoreAllMocks(); }); - it("should handle a full settings update flow correctly (language)", async () => { + it("should handle a full settings update flow correctly (language) and log success messages", async () => { mockSelect.mockResolvedValueOnce("settings"); mockSelect.mockResolvedValueOnce("language"); mockPtForLanguage.mockResolvedValueOnce("fr"); @@ -83,10 +87,15 @@ describe("Interactive Config Prompts", () => { "fr", false, ); - expect(mockLogger.log).toHaveBeenCalledWith(mocktFn("config.set.success")); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(SUCCESS_CONFIG_UPDATED_KEY), + ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(INTERACTIVE_SUCCESS_KEY), + ); }); - it("should handle a full template update flow correctly (description)", async () => { + it("should handle a full template update flow correctly (description) and log success messages", async () => { mockSelect.mockResolvedValueOnce("templates"); mockPtForLanguage.mockResolvedValueOnce("typescript"); mockSelect.mockResolvedValueOnce("web"); @@ -105,7 +114,10 @@ describe("Interactive Config Prompts", () => { false, ); expect(mockLogger.log).toHaveBeenCalledWith( - mocktFn("config.update.success", { templateName: "web" }), + mocktFn(SUCCESS_TEMPLATE_UPDATED_KEY, { templateName: "web" }), + ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(INTERACTIVE_SUCCESS_KEY), ); }); @@ -125,5 +137,32 @@ describe("Interactive Config Prompts", () => { { packageManager: "bun" }, false, ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(SUCCESS_TEMPLATE_UPDATED_KEY, { templateName: "web" }), + ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(INTERACTIVE_SUCCESS_KEY), + ); + }); + + it("should handle a settings update with a special prompt (cacheStrategy)", async () => { + mockSelect.mockResolvedValueOnce("settings"); + mockSelect.mockResolvedValueOnce("cacheStrategy"); + mockPromptForCacheStrategy.mockResolvedValueOnce("daily"); + + await handleInteractiveConfig(baseConfig, true); + + expect(mockPromptForCacheStrategy).toHaveBeenCalledWith(true); + expect(vi.mocked(handleNonInteractiveSettingsUpdate)).toHaveBeenCalledWith( + "cacheStrategy", + "daily", + true, + ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(SUCCESS_CONFIG_UPDATED_KEY), + ); + expect(mockLogger.log).toHaveBeenCalledWith( + mocktFn(INTERACTIVE_SUCCESS_KEY), + ); }); }); diff --git a/packages/devkit/__tests__/units/commands/config/remove.spec.ts b/packages/devkit/__tests__/units/commands/config/remove.spec.ts index a5ee650..97c69b3 100644 --- a/packages/devkit/__tests__/units/commands/config/remove.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/remove.spec.ts @@ -17,7 +17,7 @@ const { mockValidateProgrammingLanguage: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; vi.mock("#utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, @@ -36,6 +36,13 @@ vi.mock("#utils/validations/config.js", () => ({ validateProgrammingLanguage: mockValidateProgrammingLanguage, })); +const CMD_DESCRIPTION_KEY = "commands.template.remove.command.description"; +const STATUS_REMOVING_KEY = "messages.status.template_removing"; +const SUCCESS_REMOVED_KEY = "messages.success.template_removed"; +const WARNING_NOT_FOUND_KEY = "warnings.templates_not_found"; +const ERROR_TEMPLATE_NOT_FOUND_KEY = "errors.template.not_found"; +const ERROR_LANG_NOT_FOUND_KEY = "errors.template.language_not_found"; + describe("setupRemoveCommand", () => { let mockConfigCommand: any; @@ -55,6 +62,9 @@ describe("setupRemoveCommand", () => { }, }, }, + typescript: { + templates: {}, + }, }, }; @@ -81,24 +91,42 @@ describe("setupRemoveCommand", () => { expect(mockConfigCommand.alias).toHaveBeenCalledWith("rm"); expect(mockConfigCommand.description).toHaveBeenCalledWith( - mocktFn("remove_template.command.description"), + mocktFn(CMD_DESCRIPTION_KEY), ); }); describe("action handler", () => { + const callAction = ( + language: string, + templateNames: string[], + isGlobal: boolean, + ) => { + const parentOpts = { global: isGlobal }; + return actionFn( + language, + templateNames, + {}, + { + parent: { + opts: vi.fn(() => parentOpts), + }, + }, + ); + }; + it("should remove a template by its name", async () => { const initialConfig = structuredClone(sampleConfig); mockReadAndMergeConfigs.mockResolvedValueOnce({ config: structuredClone(initialConfig), source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["vue-basic"], { global: false }); + await callAction("javascript", ["vue-basic"], false); expect(mockSpinner.start).toHaveBeenCalledWith( - mockLogger.colors.cyan(mocktFn("remove_template.start")), + mockLogger.colors.cyan(mocktFn(STATUS_REMOVING_KEY)), ); expect(mockSaveLocalConfig).toHaveBeenCalledWith({ settings: {}, @@ -111,10 +139,13 @@ describe("setupRemoveCommand", () => { }, }, }, + typescript: { + templates: {}, + }, }, }); expect(mockSpinner.succeed).toHaveBeenCalledWith( - mocktFn("remove_template.success", { + mocktFn(SUCCESS_REMOVED_KEY, { count: "1", templateName: "vue-basic", language: "javascript", @@ -129,26 +160,30 @@ describe("setupRemoveCommand", () => { config: initialConfig, source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["vb"], { global: false }); - - expect(mockSaveLocalConfig).toHaveBeenCalledWith({ - settings: {}, - templates: { - javascript: { - templates: { - "react-basic": { - description: "A basic React template", - location: "https://github.com/facebook/react", + await callAction("javascript", ["vb"], false); + + expect(mockSaveLocalConfig).toHaveBeenCalledWith( + expect.objectContaining({ + templates: { + javascript: { + templates: { + "react-basic": { + description: "A basic React template", + location: "https://github.com/facebook/react", + }, }, }, + typescript: { + templates: {}, + }, }, - }, - }); + }), + ); expect(mockSpinner.succeed).toHaveBeenCalledWith( - mocktFn("remove_template.success", { + mocktFn(SUCCESS_REMOVED_KEY, { count: "1", templateName: "vue-basic", language: "javascript", @@ -156,29 +191,31 @@ describe("setupRemoveCommand", () => { ); }); - it("should remove multiple templates at once", async () => { + it("should remove multiple templates at once (name and alias)", async () => { const initialConfig = structuredClone(sampleConfig); mockReadAndMergeConfigs.mockResolvedValueOnce({ config: initialConfig, source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["vue-basic", "react-basic"], { - global: false, - }); + await callAction("javascript", ["vue-basic", "react-basic"], false); - expect(mockSaveLocalConfig).toHaveBeenCalledWith({ - settings: {}, - templates: { - javascript: { - templates: {}, + expect(mockSaveLocalConfig).toHaveBeenCalledWith( + expect.objectContaining({ + templates: { + javascript: { + templates: {}, + }, + typescript: { + templates: {}, + }, }, - }, - }); + }), + ); expect(mockSpinner.succeed).toHaveBeenCalledWith( - mocktFn("remove_template.success", { + mocktFn(SUCCESS_REMOVED_KEY, { count: "2", templateName: "vue-basic, react-basic", language: "javascript", @@ -186,85 +223,74 @@ describe("setupRemoveCommand", () => { ); }); - it("should remove from global config with --global flag", async () => { + it("should remove from global config when isGlobal is true", async () => { const initialConfig = structuredClone(sampleConfig); mockReadAndMergeConfigs.mockResolvedValueOnce({ config: initialConfig, source: "global", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn( - "javascript", - ["vue-basic"], - {}, - { - parent: { - opts: vi.fn(() => ({ global: true })), - }, - }, - ); + await callAction("javascript", ["vue-basic"], true); expect(mockSaveGlobalConfig).toHaveBeenCalledOnce(); - expect(mockSaveGlobalConfig).toHaveBeenCalledWith({ - settings: {}, - templates: { - javascript: { - templates: { - "react-basic": { - description: "A basic React template", - location: "https://github.com/facebook/react", + expect(mockSaveGlobalConfig).toHaveBeenCalledWith( + expect.objectContaining({ + templates: { + javascript: { + templates: { + "react-basic": { + description: "A basic React template", + location: "https://github.com/facebook/react", + }, }, }, + typescript: { + templates: {}, + }, }, - }, - }); + }), + ); }); it("should throw an error if no templates are found for the given language", async () => { const initialConfig = { settings: {}, - templates: { - javascript: { - templates: {}, - }, - }, + templates: {}, }; mockReadAndMergeConfigs.mockResolvedValueOnce({ config: structuredClone(initialConfig), source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["react-basic"], { global: false }); + await callAction("python", ["basic-script"], false); expect(mockHandleErrorAndExit).toHaveBeenCalledOnce(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.template.not_found", { template: "react-basic" }), + mocktFn(ERROR_LANG_NOT_FOUND_KEY, { language: "python" }), ), mockSpinner, ); }); - it("should throw an error if the templates are not found", async () => { + it("should throw an error if none of the provided template names exist", async () => { const initialConfig = structuredClone(sampleConfig); mockReadAndMergeConfigs.mockResolvedValueOnce({ config: initialConfig, source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["non-existent", "another-one"], { - global: false, - }); + await callAction("javascript", ["non-existent", "another-one"], false); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.template.not_found", { + mocktFn(ERROR_TEMPLATE_NOT_FOUND_KEY, { template: "non-existent, another-one", }), ), @@ -273,44 +299,45 @@ describe("setupRemoveCommand", () => { }); it("should remove existing templates and warn about non-existent ones", async () => { - const consoleLogSpy = mockLogger.log; const initialConfig = structuredClone(sampleConfig); mockReadAndMergeConfigs.mockResolvedValueOnce({ config: initialConfig, source: "local", }); - mockValidateProgrammingLanguage.mockReturnValueOnce(true); + mockValidateProgrammingLanguage.mockReturnValue(true); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["vue-basic", "non-existent"], { - global: false, - }); + await callAction("javascript", ["vue-basic", "non-existent"], false); expect(mockSaveLocalConfig).toHaveBeenCalledOnce(); - expect(mockSaveLocalConfig).toHaveBeenCalledWith({ - settings: {}, - templates: { - javascript: { - templates: { - "react-basic": { - description: "A basic React template", - location: "https://github.com/facebook/react", + expect(mockSaveLocalConfig).toHaveBeenCalledWith( + expect.objectContaining({ + templates: { + javascript: { + templates: { + "react-basic": { + description: "A basic React template", + location: "https://github.com/facebook/react", + }, }, }, + typescript: { + templates: {}, + }, }, - }, - }); + }), + ); expect(mockSpinner.succeed).toHaveBeenCalledWith( - mocktFn("remove_template.success", { + mocktFn(SUCCESS_REMOVED_KEY, { count: "1", templateName: "vue-basic", language: "javascript", }), ); - expect(consoleLogSpy).toHaveBeenCalledWith( + expect(mockLogger.warning).toHaveBeenCalledWith( mockLogger.colors.yellow( - mocktFn("remove_template.not_found_warning", { - template: "non-existent", + mocktFn(WARNING_NOT_FOUND_KEY, { + templates: "non-existent", }), ), ); @@ -321,7 +348,7 @@ describe("setupRemoveCommand", () => { mockReadAndMergeConfigs.mockRejectedValue(mockError); setupRemoveCommand(mockConfigCommand); - await actionFn("javascript", ["vue-basic"], { global: false }); + await callAction("javascript", ["vue-basic"], false); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( @@ -331,15 +358,13 @@ describe("setupRemoveCommand", () => { }); it("should handle an invalid language gracefully", async () => { - const mockError = new DevkitError( - "error.language_config_not_found - keys: language, values: invalid-lang", - ); + const mockError = new DevkitError("Invalid language provided"); mockValidateProgrammingLanguage.mockImplementation(() => { throw mockError; }); setupRemoveCommand(mockConfigCommand); - await actionFn("invalid-lang", ["vue-basic"], { global: false }); + await callAction("invalid-lang", ["vue-basic"], false); expect(mockReadAndMergeConfigs).not.toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( diff --git a/packages/devkit/__tests__/units/commands/config/update.spec.ts b/packages/devkit/__tests__/units/commands/config/update.spec.ts index 1f23336..66fbfd8 100644 --- a/packages/devkit/__tests__/units/commands/config/update.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/update.spec.ts @@ -9,7 +9,7 @@ const { mockHandleErrorAndExit, mockHandleNonInteractiveTemplateUpdate } = mockHandleNonInteractiveTemplateUpdate: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; vi.mock("#utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, @@ -24,6 +24,24 @@ const mockProcessExit = vi .spyOn(process, "exit") .mockImplementation((() => {}) as unknown as never); +const CMD_DESCRIPTION_KEY = + "commands.config.update_template.command.description"; +const OPT_NEW_NAME_KEY = "commands.config.update_template.options.new_name"; +const OPT_DESCRIPTION_KEY = + "commands.config.update_template.options.description"; +const OPT_ALIAS_KEY = "commands.config.update_template.options.alias"; +const OPT_LOCATION_KEY = "commands.config.update_template.options.location"; +const OPT_CACHE_STRATEGY_KEY = + "commands.config.update_template.options.cache_strategy"; +const OPT_PACKAGE_MANAGER_KEY = + "commands.config.update_template.options.package_manager"; +const OPT_GLOBAL_KEY = "commands.config.update_template.options.global"; +const STATUS_UPDATING_KEY = "messages.status.template_updating"; +const VALIDATION_REQUIRED_KEY = "errors.validation.template_name_required"; +const TEMPLATE_NOT_FOUND_KEY = "errors.template.not_found"; +const SINGLE_FAIL_KEY = "errors.template.single_fail"; +const SUCCESS_SUMMARY_KEY = "messages.success.template_summary_updated"; + describe("setupUpdateCommand", () => { let mockConfigCommand: any; @@ -50,35 +68,35 @@ describe("setupUpdateCommand", () => { expect(mockConfigCommand.alias).toHaveBeenCalledWith("up"); expect(mockConfigCommand.description).toHaveBeenCalledWith( - mocktFn("config.update.command.description"), + mocktFn(CMD_DESCRIPTION_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-n, --new-name ", - mocktFn("config.update.option.new_name"), + mocktFn(OPT_NEW_NAME_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-d, --description ", - mocktFn("config.update.option.description"), + mocktFn(OPT_DESCRIPTION_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-a, --alias ", - mocktFn("config.update.option.alias"), + mocktFn(OPT_ALIAS_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-l, --location ", - mocktFn("config.update.option.location"), + mocktFn(OPT_LOCATION_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "--cache-strategy ", - mocktFn("config.update.option.cache_strategy"), + mocktFn(OPT_CACHE_STRATEGY_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "--package-manager ", - mocktFn("config.update.option.package_manager"), + mocktFn(OPT_PACKAGE_MANAGER_KEY), ); expect(mockConfigCommand.option).toHaveBeenCalledWith( "-g, --global", - mocktFn("config.update.option.global"), + mocktFn(OPT_GLOBAL_KEY), false, ); }); @@ -95,11 +113,13 @@ describe("setupUpdateCommand", () => { mockHandleNonInteractiveTemplateUpdate.mockResolvedValueOnce(undefined); setupUpdateCommand(mockConfigCommand); - await actionFn("javascript", ["my-template"], defaultCmdOptions); + await actionFn("javascript", ["my-template"], defaultCmdOptions, { + parent: { opts: () => ({ global: false }) }, + }); expect(mockSpinner.start).toHaveBeenCalledWith( mockLogger.colors.cyan( - mocktFn("config.update.updating", { templateName: "my-template" }), + mocktFn(STATUS_UPDATING_KEY, { templateName: "my-template" }), ), ); expect(mockHandleNonInteractiveTemplateUpdate).toHaveBeenCalledWith( @@ -115,7 +135,7 @@ describe("setupUpdateCommand", () => { expect(mockSpinner.stop).toHaveBeenCalled(); expect(consoleLogSpy).toHaveBeenCalledWith( mockLogger.colors.green( - `\n✔ ${mocktFn("config.update.success_summary", { + `\n✔ ${mocktFn(SUCCESS_SUMMARY_KEY, { count: "1", templateName: "my-template", language: "javascript", @@ -129,24 +149,26 @@ describe("setupUpdateCommand", () => { mockHandleNonInteractiveTemplateUpdate.mockResolvedValue(undefined); setupUpdateCommand(mockConfigCommand); - await actionFn("javascript", ["temp1", "temp2"], defaultCmdOptions); + await actionFn("javascript", ["temp1", "temp2"], defaultCmdOptions, { + parent: { opts: () => ({ global: false }) }, + }); expect(mockHandleNonInteractiveTemplateUpdate).toHaveBeenCalledTimes(2); expect(mockHandleNonInteractiveTemplateUpdate).toHaveBeenCalledWith( "javascript", "temp1", - expect.any(Object), + expect.objectContaining({ language: "javascript", isGlobal: false }), false, ); expect(mockHandleNonInteractiveTemplateUpdate).toHaveBeenCalledWith( "javascript", "temp2", - expect.any(Object), + expect.objectContaining({ language: "javascript", isGlobal: false }), false, ); expect(consoleLogSpy).toHaveBeenCalledWith( mockLogger.colors.green( - `\n✔ ${mocktFn("config.update.success_summary", { + `\n✔ ${mocktFn(SUCCESS_SUMMARY_KEY, { count: "2", templateName: "temp1, temp2", language: "javascript", @@ -157,10 +179,10 @@ describe("setupUpdateCommand", () => { it("should handle mixed success and failure and exit with code 1", async () => { mockHandleNonInteractiveTemplateUpdate - .mockResolvedValue(undefined) - .mockRejectedValue( + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce( new DevkitError( - mocktFn("error.template.not_found", { template: "temp2" }), + mocktFn(TEMPLATE_NOT_FOUND_KEY, { template: "temp2" }), ), ) .mockResolvedValueOnce(undefined); @@ -170,31 +192,34 @@ describe("setupUpdateCommand", () => { "javascript", ["temp1", "temp2", "temp3"], defaultCmdOptions, + { parent: { opts: () => ({ global: false }) } }, ); expect(mockHandleNonInteractiveTemplateUpdate).toHaveBeenCalledTimes(3); expect(consoleLogSpy).toHaveBeenCalledWith( mockLogger.colors.yellow( - `\n${mocktFn("config.update.single_fail", { + `\n${mocktFn(SINGLE_FAIL_KEY, { templateName: "temp2", - error: mocktFn("error.template.not_found", { template: "temp2" }), + error: mocktFn(TEMPLATE_NOT_FOUND_KEY, { template: "temp2" }), })}`, ), ); expect(consoleLogSpy).toHaveBeenCalledWith( - expect.stringContaining("config.update.success_summary"), + expect.stringContaining(SUCCESS_SUMMARY_KEY), ); expect(mockProcessExit).toHaveBeenCalledOnce(); expect(mockProcessExit).toHaveBeenCalledWith(1); }); - it("should handle an invalid template name", async () => { + it("should handle an invalid template name (empty array)", async () => { setupUpdateCommand(mockConfigCommand); - await actionFn("javascript", [], defaultCmdOptions); + await actionFn("javascript", [], defaultCmdOptions, { + parent: { opts: () => ({ global: false }) }, + }); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( - new DevkitError(mocktFn("error.template_name_required")), + new DevkitError(mocktFn(VALIDATION_REQUIRED_KEY)), mockSpinner, ); expect(mockHandleNonInteractiveTemplateUpdate).not.toHaveBeenCalled(); @@ -202,16 +227,18 @@ describe("setupUpdateCommand", () => { it("should handle unexpected errors gracefully", async () => { const mockError = new Error("Unexpected error"); - mockHandleNonInteractiveTemplateUpdate.mockRejectedValue(mockError); + mockHandleNonInteractiveTemplateUpdate.mockRejectedValueOnce(mockError); setupUpdateCommand(mockConfigCommand); - await actionFn("javascript", ["my-template"], defaultCmdOptions); + await actionFn("javascript", ["my-template"], defaultCmdOptions, { + parent: { opts: () => ({ global: false }) }, + }); expect(mockSpinner.stop).toHaveBeenCalled(); expect(mockHandleErrorAndExit).not.toHaveBeenCalled(); expect(consoleLogSpy).toHaveBeenCalledWith( mockLogger.colors.yellow( - `\n${mocktFn("config.update.single_fail", { + `\n${mocktFn(SINGLE_FAIL_KEY, { templateName: "my-template", error: "unknown error", })}`, diff --git a/packages/devkit/__tests__/units/commands/config/validate-and-save.spec.ts b/packages/devkit/__tests__/units/commands/config/validate-and-save.spec.ts index b443d28..3798e9f 100644 --- a/packages/devkit/__tests__/units/commands/config/validate-and-save.spec.ts +++ b/packages/devkit/__tests__/units/commands/config/validate-and-save.spec.ts @@ -70,6 +70,11 @@ const mockTemplateDetails: AddTemplateSchema = { packageManager: "npm", }; +const TEMPLATE_EXISTS_KEY = "errors.template.exists"; +const ALIAS_EXISTS_KEY = "errors.validation.alias_exists"; +const INVALID_CACHE_STRATEGY_KEY = "errors.validation.invalid_cache_strategy"; +const INVALID_PACKAGE_MANAGER_KEY = "errors.validation.invalid_package_manager"; + describe("validateAndSaveTemplate", () => { beforeEach(() => { vi.clearAllMocks(); @@ -227,7 +232,7 @@ describe("validateAndSaveTemplate", () => { validateAndSaveTemplate(duplicateDetails, mockConfig, false, mockSpinner), ).rejects.toThrowError( new DevkitError( - mocktFn("error.template.exists", { + mocktFn(TEMPLATE_EXISTS_KEY, { template: duplicateDetails.templateName, }), ), @@ -243,7 +248,7 @@ describe("validateAndSaveTemplate", () => { validateAndSaveTemplate(duplicateDetails, mockConfig, false, mockSpinner), ).rejects.toThrowError( new DevkitError( - mocktFn("error.alias.exists", { alias: duplicateDetails.alias }), + mocktFn(ALIAS_EXISTS_KEY, { alias: duplicateDetails.alias }), ), ); @@ -256,7 +261,7 @@ describe("validateAndSaveTemplate", () => { cacheStrategy: "invalid-cache", }; const error = new DevkitError( - mocktFn("error.invalid.cache_strategy", { + mocktFn(INVALID_CACHE_STRATEGY_KEY, { value: "invalid-cache", options: VALID_CACHE_STRATEGIES.join(", "), }), @@ -279,7 +284,7 @@ describe("validateAndSaveTemplate", () => { packageManager: "invalid-pm", }; const error = new DevkitError( - mocktFn("error.invalid.package_manager", { + mocktFn(INVALID_PACKAGE_MANAGER_KEY, { value: "invalid-pm", options: Object.values(PackageManagers).join(", "), }), diff --git a/packages/devkit/__tests__/units/commands/index.spec.ts b/packages/devkit/__tests__/units/commands/index.spec.ts index 9eede7f..f7f4a17 100644 --- a/packages/devkit/__tests__/units/commands/index.spec.ts +++ b/packages/devkit/__tests__/units/commands/index.spec.ts @@ -123,7 +123,7 @@ describe("index.ts (Entry point)", () => { await vi.runAllTimersAsync(); expect(warnSpy).toHaveBeenCalledOnce(); - expect(warnSpy).toHaveBeenCalledWith("\nwarning.no_config_found\n"); + expect(warnSpy).toHaveBeenCalledWith("\nwarnings.not_found\n"); }); }); diff --git a/packages/devkit/__tests__/units/commands/info.spec.ts b/packages/devkit/__tests__/units/commands/info.spec.ts index cda8c42..183225b 100644 --- a/packages/devkit/__tests__/units/commands/info.spec.ts +++ b/packages/devkit/__tests__/units/commands/info.spec.ts @@ -46,9 +46,28 @@ const MOCKED_SYSTEM_INFO: SystemInfo = { }, }; +const CMD_DESCRIPTION_KEY = "commands.info.command.description"; +const SUCCESS_MESSAGE_KEY = "messages.success.info_collected"; +const CLI_HEADER_KEY = "commands.info.header.cli"; +const CLI_VERSION_KEY = "commands.info.cli.version"; +const RUNTIME_HEADER_KEY = "commands.info.header.runtime"; +const RUNTIME_NAME_KEY = "commands.info.runtime.runtime_name"; +const RUNTIME_VERSION_KEY = "commands.info.runtime.runtime_version"; +const PACKAGE_MANAGER_KEY = "commands.info.runtime.package_manager"; +const OS_HEADER_KEY = "commands.info.header.os_details"; +const OS_TYPE_VERSION_KEY = "commands.info.os.type_version"; +const OS_ARCHITECTURE_KEY = "commands.info.os.architecture"; +const OS_SHELL_KEY = "commands.info.os.shell"; +const OS_HOME_DIR_KEY = "commands.info.os.home_dir"; +const CONFIG_HEADER_KEY = "commands.info.header.config_files"; +const CONFIG_GLOBAL_PATH_KEY = "commands.info.config.global_path"; +const CONFIG_LOCAL_PATH_KEY = "commands.info.config.local_path"; +const CONFIG_FOUND_KEY = "commands.info.config.found"; +const CONFIG_NOT_FOUND_KEY = "commands.info.config.not_found"; + describe("setupInfoCommand", () => { let mockProgram: any; - let actionFn: (options: any) => Promise; + let actionFn: (options: unknown) => Promise; beforeEach(() => { vi.clearAllMocks(); @@ -80,9 +99,7 @@ describe("setupInfoCommand", () => { setupInfoCommand({ program: mockProgram }); expect(mockProgram.command).toHaveBeenCalledWith("info"); expect(mockProgram.alias).toHaveBeenCalledWith("in"); - expect(mockProgram.description).toHaveBeenCalledWith( - "info.command.description", - ); + expect(mockProgram.description).toHaveBeenCalledWith(CMD_DESCRIPTION_KEY); }); describe("Command Action and printInfo Output", () => { @@ -95,30 +112,30 @@ describe("setupInfoCommand", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalled(); - expect(mockSpinner.succeed).toHaveBeenCalledWith("info.success_message"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_MESSAGE_KEY); expect(mockCollectSystemInfo).toHaveBeenCalledWith(MOCKED_CLI_VERSION); const pad = (label: string) => label.padEnd(27, " "); expect(consoleOutput).toEqual([ "\n", - `--- info.header.cli ---`, - `${pad("info.cli.version")}: ${MOCKED_CLI_VERSION}`, + `--- ${CLI_HEADER_KEY} ---`, + `${pad(CLI_VERSION_KEY)}: ${MOCKED_CLI_VERSION}`, "\n", - `--- info.header.runtime ---`, - `${pad("info.runtime.runtime_name")}: ${MOCKED_SYSTEM_INFO.runtimeName}`, - `${pad("info.runtime.runtime_version")}: ${MOCKED_SYSTEM_INFO.runtimeVersion}`, - `${pad("info.runtime.package_manager")}: ${MOCKED_SYSTEM_INFO.packageManagerVersion}`, + `--- ${RUNTIME_HEADER_KEY} ---`, + `${pad(RUNTIME_NAME_KEY)}: ${MOCKED_SYSTEM_INFO.runtimeName}`, + `${pad(RUNTIME_VERSION_KEY)}: ${MOCKED_SYSTEM_INFO.runtimeVersion}`, + `${pad(PACKAGE_MANAGER_KEY)}: ${MOCKED_SYSTEM_INFO.packageManagerVersion}`, "\n", - `--- info.header.os_details ---`, - `${pad("info.os.type_version")}: ${MOCKED_SYSTEM_INFO.os}`, - `${pad("info.os.architecture")}: ${MOCKED_SYSTEM_INFO.arch}`, - `${pad("info.os.shell")}: ${MOCKED_SYSTEM_INFO.shell}`, - `${pad("info.os.home_dir")}: ${MOCKED_SYSTEM_INFO.homeDir}`, + `--- ${OS_HEADER_KEY} ---`, + `${pad(OS_TYPE_VERSION_KEY)}: ${MOCKED_SYSTEM_INFO.os}`, + `${pad(OS_ARCHITECTURE_KEY)}: ${MOCKED_SYSTEM_INFO.arch}`, + `${pad(OS_SHELL_KEY)}: ${MOCKED_SYSTEM_INFO.shell}`, + `${pad(OS_HOME_DIR_KEY)}: ${MOCKED_SYSTEM_INFO.homeDir}`, "\n", - `--- info.header.config_files ---`, - `${pad("info.config.global_path")}: ${GLOBAL_PATH} info.config.found`, - `${pad("info.config.local_path")}: ${LOCAL_EXPECTED} info.config.not_found`, + `--- ${CONFIG_HEADER_KEY} ---`, + `${pad(CONFIG_GLOBAL_PATH_KEY)}: ${GLOBAL_PATH} ${CONFIG_FOUND_KEY}`, + `${pad(CONFIG_LOCAL_PATH_KEY)}: ${LOCAL_EXPECTED} ${CONFIG_NOT_FOUND_KEY}`, "\n", ]); }); diff --git a/packages/devkit/__tests__/units/commands/init.spec.ts b/packages/devkit/__tests__/units/commands/init.spec.ts index ad607c1..da8f582 100644 --- a/packages/devkit/__tests__/units/commands/init.spec.ts +++ b/packages/devkit/__tests__/units/commands/init.spec.ts @@ -33,7 +33,7 @@ const { mockGetPackageManager: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; vi.mock("os", async () => { const actual = await vi.importActual("os"); return { @@ -81,6 +81,15 @@ vi.mock("#core/config/search.js", () => ({ findGlobalConfigFile: mockFindGlobalConfigFile, })); +const LOCAL_OPTION_KEY = "commands.config.init.option.local"; +const GLOBAL_OPTION_KEY = "commands.config.init.option.global"; +const CONFIRM_OVERWRITE_KEY = "commands.config.init.confirm_overwrite"; +const SUCCESS_KEY = "messages.success.config_initialized"; +const ABORTED_KEY = "commands.config.init.aborted"; +const YES_KEY = "common.yes"; +const NO_KEY = "common.no"; +const LOCAL_GLOBAL_ERROR_KEY = "errors.config.init_local_and_global"; + describe("setupInitCommand", () => { let mockProgram: any; const localConfigFile = CONFIG_FILE_NAMES[1]; @@ -121,12 +130,12 @@ describe("setupInitCommand", () => { expect(mockProgram.alias).toHaveBeenCalledWith("i"); expect(mockProgram.option).toHaveBeenCalledWith( "-l, --local", - "config.init.option.local", + LOCAL_OPTION_KEY, false, ); expect(mockProgram.option).toHaveBeenCalledWith( "-g, --global", - "config.init.option.global", + GLOBAL_OPTION_KEY, false, ); }); @@ -146,7 +155,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, globalConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should default to homedir if findGlobalConfigFile returns null", async () => { @@ -163,7 +172,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, globalConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should overwrite a global config file when --global flag is set and user confirms", async () => { @@ -176,10 +185,10 @@ describe("setupInitCommand", () => { expect(mockFs.pathExists).toHaveBeenCalledWith(globalConfigPath); expect(mockInquirerSelect).toHaveBeenCalledWith({ - message: `config.init.confirm_overwrite- options path:${globalConfigPath}`, + message: `${CONFIRM_OVERWRITE_KEY}- options path:${globalConfigPath}`, choices: [ - { name: "common.yes", value: true }, - { name: "common.no", value: false }, + { name: YES_KEY, value: true }, + { name: NO_KEY, value: false }, ], default: true, }); @@ -188,7 +197,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, globalConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should not overwrite a global config file when user cancels", async () => { @@ -202,7 +211,7 @@ describe("setupInitCommand", () => { expect(mockFs.pathExists).toHaveBeenCalledWith(globalConfigPath); expect(mockGetPackageManager).not.toHaveBeenCalled(); expect(mockSaveConfig).not.toHaveBeenCalled(); - expect(mockSpinner.info).toHaveBeenCalledWith("config.init.aborted"); + expect(mockSpinner.info).toHaveBeenCalledWith(ABORTED_KEY); }); }); @@ -227,7 +236,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, localConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should ask to overwrite a local config file if it already exists", async () => { @@ -247,10 +256,10 @@ describe("setupInitCommand", () => { limit: "/current/directory", }); expect(mockInquirerSelect).toHaveBeenCalledWith({ - message: `config.init.confirm_overwrite- options path:${localConfigPath}`, + message: `${CONFIRM_OVERWRITE_KEY}- options path:${localConfigPath}`, choices: [ - { name: "common.yes", value: true }, - { name: "common.no", value: false }, + { name: YES_KEY, value: true }, + { name: NO_KEY, value: false }, ], default: true, }); @@ -259,7 +268,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, localConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should use the monorepo root as the limit and overwrite an existing config there", async () => { @@ -279,10 +288,10 @@ describe("setupInitCommand", () => { limit: monorepoRootPath, }); expect(mockInquirerSelect).toHaveBeenCalledWith({ - message: `config.init.confirm_overwrite- options path:${monorepoRootConfigPath}`, + message: `${CONFIRM_OVERWRITE_KEY}- options path:${monorepoRootConfigPath}`, choices: [ - { name: "common.yes", value: true }, - { name: "common.no", value: false }, + { name: YES_KEY, value: true }, + { name: NO_KEY, value: false }, ], default: true, }); @@ -291,7 +300,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, monorepoRootConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); it("should use the project root as the limit and overwrite an existing config there", async () => { @@ -311,10 +320,10 @@ describe("setupInitCommand", () => { limit: projectRootPath, }); expect(mockInquirerSelect).toHaveBeenCalledWith({ - message: `config.init.confirm_overwrite- options path:${projectRootConfigPath}`, + message: `${CONFIRM_OVERWRITE_KEY}- options path:${projectRootConfigPath}`, choices: [ - { name: "common.yes", value: true }, - { name: "common.no", value: false }, + { name: YES_KEY, value: true }, + { name: NO_KEY, value: false }, ], default: true, }); @@ -323,7 +332,7 @@ describe("setupInitCommand", () => { mockDetectedConfig, projectRootConfigPath, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("config.init.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(SUCCESS_KEY); }); }); @@ -331,7 +340,7 @@ describe("setupInitCommand", () => { setupInitCommand({ program: mockProgram }); await actionFn({ local: true, global: true }); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( - new ConfigError("error.config.init.local_and_global"), + new ConfigError(LOCAL_GLOBAL_ERROR_KEY), mockSpinner, ); expect(mockSaveConfig).not.toHaveBeenCalled(); diff --git a/packages/devkit/__tests__/units/commands/list.spec.ts b/packages/devkit/__tests__/units/commands/list.spec.ts index 9a46b7d..fdc56fd 100644 --- a/packages/devkit/__tests__/units/commands/list.spec.ts +++ b/packages/devkit/__tests__/units/commands/list.spec.ts @@ -75,6 +75,20 @@ vi.mock("#utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, })); +const CMD_DESCRIPTION_KEY = "commands.list.command.description"; +const LANG_ARGUMENT_KEY = "commands.list.command.language.argument"; +const GLOBAL_OPTION_KEY = "commands.list.options.global"; +const ALL_OPTION_KEY = "commands.list.options.all"; +const FILTER_OPTION_KEY = "commands.list.command.filter.option"; + +const USING_LOCAL_GLOBAL_KEY = "messages.config_source.using_local_and_global"; +const USING_GLOBAL_KEY = "messages.config_source.global"; +const USING_LOCAL_KEY = "messages.config_source.local"; +const GLOBAL_FALLBACK_KEY = "messages.config_source.global_fallback"; +const TEMPLATE_NOT_FOUND_KEY = "warnings.template_not_found"; +const HEADER_KEY = "commands.list.output.header"; +const MUTUALLY_EXCLUSIVE_KEY = "errors.command.mutually_exclusive_options"; + describe("list command", () => { let actionFn: Function; @@ -95,25 +109,23 @@ describe("list command", () => { expect(mockProgram.command).toHaveBeenCalledWith("list"); expect(mockProgram.alias).toHaveBeenCalledWith("ls"); - expect(mockProgram.description).toHaveBeenCalledWith( - "list.command.description", - ); + expect(mockProgram.description).toHaveBeenCalledWith(CMD_DESCRIPTION_KEY); expect(mockProgram.argument).toHaveBeenCalledWith( "[language]", - "list.command.language.argument", + LANG_ARGUMENT_KEY, "", ); expect(mockProgram.option).toHaveBeenCalledWith( "-g, --global", - "list.command.global.option", + GLOBAL_OPTION_KEY, ); expect(mockProgram.option).toHaveBeenCalledWith( "-a, --all", - "list.command.all.option", + ALL_OPTION_KEY, ); expect(mockProgram.option).toHaveBeenCalledWith( "-f, --filter ", - "list.command.filter.option", + FILTER_OPTION_KEY, ); expect(mockProgram.option).not.toHaveBeenCalledWith( "-l, --local", @@ -140,9 +152,7 @@ describe("list command", () => { expect(mockValidateProgrammingLanguage).not.toHaveBeenCalled(); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalledTimes(2); - expect(mockSpinner.info).toHaveBeenCalledWith( - "list.templates.using_local_and_global", - ); + expect(mockSpinner.info).toHaveBeenCalledWith(USING_LOCAL_GLOBAL_KEY); expect(mockPrintTemplates).toHaveBeenCalledWith( "javascript", { @@ -178,9 +188,7 @@ describe("list command", () => { expect(mockValidateProgrammingLanguage).not.toHaveBeenCalled(); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalledTimes(2); - expect(mockSpinner.info).toHaveBeenCalledWith( - "list.templates.using_global", - ); + expect(mockSpinner.info).toHaveBeenCalledWith(USING_GLOBAL_KEY); expect(mockPrintTemplates).toHaveBeenCalledWith( "typescript", { @@ -206,9 +214,7 @@ describe("list command", () => { expect(mockValidateProgrammingLanguage).not.toHaveBeenCalled(); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalledTimes(2); - expect(mockSpinner.info).toHaveBeenCalledWith( - "list.templates.using_local", - ); + expect(mockSpinner.info).toHaveBeenCalledWith(USING_LOCAL_KEY); expect(mockPrintTemplates).toHaveBeenCalledWith( "javascript", { @@ -245,11 +251,9 @@ describe("list command", () => { ); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalledTimes(2); - expect(mockSpinner.info).toHaveBeenCalledWith( - "list.templates.using_local", - ); + expect(mockSpinner.info).toHaveBeenCalledWith(USING_LOCAL_KEY); expect(mockLogger.log).toHaveBeenCalledTimes(1); - expect(mockLogger.log).toHaveBeenCalledWith("\nlist.templates.header"); + expect(mockLogger.log).toHaveBeenCalledWith(`\n${HEADER_KEY}`); expect(mockPrintTemplates).toHaveBeenCalledOnce(); expect(mockPrintTemplates).toHaveBeenCalledWith( @@ -287,9 +291,7 @@ describe("list command", () => { ); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.stop).toHaveBeenCalledTimes(2); - expect(mockSpinner.info).toHaveBeenCalledWith( - "list.templates.using_global_fallback", - ); + expect(mockSpinner.info).toHaveBeenCalledWith(GLOBAL_FALLBACK_KEY); expect(mockPrintTemplates).toHaveBeenCalledOnce(); expect(mockPrintTemplates).toHaveBeenCalledWith( "javascript", @@ -322,8 +324,8 @@ describe("list command", () => { ); expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.succeed).toHaveBeenCalledWith( - (mockLogger.colors as { yellow: Mock }).yellow( - "list.templates.not_found- options template:", + mockLogger.colors.yellow( + `${TEMPLATE_NOT_FOUND_KEY}- options template:`, ), ); expect(mockLogger.log).not.toHaveBeenCalled(); @@ -382,7 +384,7 @@ describe("list command", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - "error.command.mutually_exclusive_options- options options:global, all", + `${MUTUALLY_EXCLUSIVE_KEY}- options options:global, all`, ), mockSpinner, ); diff --git a/packages/devkit/__tests__/units/commands/new.spec.ts b/packages/devkit/__tests__/units/commands/new.spec.ts index a3bc6c1..ffd5195 100644 --- a/packages/devkit/__tests__/units/commands/new.spec.ts +++ b/packages/devkit/__tests__/units/commands/new.spec.ts @@ -14,7 +14,7 @@ const { mockValidateProgrammingLanguage: vi.fn(), })); -let actionFn: any; +let actionFn: (...options: unknown[]) => Promise; vi.mock("#utils/errors/handler.js", () => ({ handleErrorAndExit: mockHandleErrorAndExit, })); @@ -27,6 +27,15 @@ vi.mock("#utils/validations/config.js", () => ({ validateProgrammingLanguage: mockValidateProgrammingLanguage, })); +const LANGUAGE_NOT_FOUND_KEY = "errors.scaffolding.language_not_found"; +const TEMPLATE_NOT_FOUND_KEY = "errors.template.not_found"; +const NEW_PROJECT_SUCCESS_KEY = "messages.success.new_project"; + +const CMD_DESCRIPTION_KEY = "commands.new.command.description"; +const LANG_ARGUMENT_KEY = "commands.new.project.language.argument"; +const NAME_ARGUMENT_KEY = "commands.new.project.name.argument"; +const TEMPLATE_OPTION_KEY = "commands.new.project.template.option.description"; + describe("setupNewCommand", () => { let mockProgram: any; @@ -89,20 +98,18 @@ describe("setupNewCommand", () => { }); expect(mockProgram.command).toHaveBeenCalledWith("new"); expect(mockProgram.alias).toHaveBeenCalledWith("nw"); - expect(mockProgram.description).toHaveBeenCalledWith( - "new.command.description", - ); + expect(mockProgram.description).toHaveBeenCalledWith(CMD_DESCRIPTION_KEY); expect(mockProgram.argument).toHaveBeenCalledWith( "", - "new.project.language.argument", + LANG_ARGUMENT_KEY, ); expect(mockProgram.argument).toHaveBeenCalledWith( "", - "new.project.name.argument", + NAME_ARGUMENT_KEY, ); expect(mockProgram.requiredOption).toHaveBeenCalledWith( "-t, --template ", - "new.project.template.option.description", + TEMPLATE_OPTION_KEY, ); }); @@ -130,7 +137,7 @@ describe("setupNewCommand", () => { cacheStrategy: "always-refresh", }); expect(mockSpinner.succeed).toHaveBeenCalledWith( - "new.project.success- options projectName:react-project", + `${NEW_PROJECT_SUCCESS_KEY}- options projectName:react-project`, ); expect(mockHandleErrorAndExit).not.toHaveBeenCalled(); }); @@ -158,7 +165,7 @@ describe("setupNewCommand", () => { cacheStrategy: "daily", }); expect(mockSpinner.succeed).toHaveBeenCalledWith( - "new.project.success- options projectName:vue-project", + `${NEW_PROJECT_SUCCESS_KEY}- options projectName:vue-project`, ); expect(mockSpinner.stop).toHaveBeenCalledOnce(); expect(mockHandleErrorAndExit).not.toHaveBeenCalled(); @@ -174,17 +181,13 @@ describe("setupNewCommand", () => { const projectName = "my-python-project"; const cmdOptions = { template: "my-template" }; - const programmingLanguageError = new DevkitError( - "error.language_config_not_found- options language:python", - ); - mockValidateProgrammingLanguage.mockRejectedValueOnce( - programmingLanguageError, - ); + const expectedErrorMessage = `${LANGUAGE_NOT_FOUND_KEY}- options language:python`; + const languageError = new DevkitError(expectedErrorMessage); await actionFn(language, projectName, cmdOptions); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( - programmingLanguageError, + languageError, mockSpinner, ); }); @@ -203,7 +206,7 @@ describe("setupNewCommand", () => { expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - "error.template.not_found- options template:non-existent-template", + `${TEMPLATE_NOT_FOUND_KEY}- options template:non-existent-template`, ), mockSpinner, ); diff --git a/packages/devkit/__tests__/units/core/cache/git.spec.ts b/packages/devkit/__tests__/units/core/cache/git.spec.ts index fdecd2d..669270b 100644 --- a/packages/devkit/__tests__/units/core/cache/git.spec.ts +++ b/packages/devkit/__tests__/units/core/cache/git.spec.ts @@ -23,6 +23,9 @@ vi.mock("#utils/i18n/translator.js", () => ({ t: mockT, })); +const CLONE_FAIL_KEY = "errors.cache.clone_fail"; +const REFRESH_FAIL_KEY = "errors.cache.refresh_fail"; + describe("Git Functions", () => { const repoPath = "/temp/repo"; const url = "https://github.com/test-org/test-repo.git"; @@ -51,7 +54,7 @@ describe("Git Functions", () => { it("cloneRepo should throw a GitError on failure", async () => { mockExeca.mockRejectedValueOnce(new Error("Clone failed")); await expect(cloneRepo(url, repoPath)).rejects.toThrow(GitError); - expect(mockT).toHaveBeenCalledWith("cache.clone.fail"); + expect(mockT).toHaveBeenCalledWith(CLONE_FAIL_KEY); }); it("pullRepo should successfully pull a repository", async () => { @@ -66,7 +69,7 @@ describe("Git Functions", () => { it("pullRepo should throw a GitError on failure", async () => { mockExeca.mockRejectedValueOnce(new Error("Pull failed")); await expect(pullRepo(repoPath)).rejects.toThrow(GitError); - expect(mockT).toHaveBeenCalledWith("cache.refresh.fail"); + expect(mockT).toHaveBeenCalledWith(REFRESH_FAIL_KEY); }); describe("isRepoFresh", () => { diff --git a/packages/devkit/__tests__/units/core/cache/index.spec.ts b/packages/devkit/__tests__/units/core/cache/index.spec.ts index b9ab4d3..8f37591 100644 --- a/packages/devkit/__tests__/units/core/cache/index.spec.ts +++ b/packages/devkit/__tests__/units/core/cache/index.spec.ts @@ -20,6 +20,13 @@ vi.mock("os", () => ({ }, })); +const REFRESH_SUCCESS_KEY = "messages.success.template_updated"; +const CLONE_SUCCESS_KEY = "messages.success.template_added"; +const USE_INFO_KEY = "messages.status.cache_use_info"; +const COPY_START_KEY = "messages.status.cache_copy_start"; +const COPY_SUCCESS_KEY = "messages.success.new_project"; +const COPY_FAIL_KEY = "errors.cache.copy_fail"; + describe("getTemplateFromCache", () => { const options = { url: "https://github.com/test-org/test-repo.git", @@ -49,12 +56,13 @@ describe("getTemplateFromCache", () => { await getTemplateFromCache(options); expect(mockSpinner.start).toHaveBeenCalled(); - expect(mockSpinner.text).toBe("cache.copy.start"); + expect(mockSpinner.text).toBe(COPY_START_KEY); expect(git.cloneRepo).toHaveBeenCalledWith( options.url, `${os.homedir()}/.devkit/cache/test-repo`, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("cache.copy.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(CLONE_SUCCESS_KEY); + expect(mockSpinner.succeed).toHaveBeenCalledWith(COPY_SUCCESS_KEY); expect(templateUtils.copyJavascriptTemplate).toHaveBeenCalledWith( `${os.homedir()}/.devkit/cache/test-repo`, `/current/dir/${options.projectName}`, @@ -76,7 +84,7 @@ describe("getTemplateFromCache", () => { expect(git.pullRepo).toHaveBeenCalledWith( `${os.homedir()}/.devkit/cache/test-repo`, ); - expect(mockSpinner.succeed).toHaveBeenCalledWith("cache.refresh.success"); + expect(mockSpinner.succeed).toHaveBeenCalledWith(REFRESH_SUCCESS_KEY); }); it("should use a template directly if it exists and is fresh", async () => { @@ -87,7 +95,7 @@ describe("getTemplateFromCache", () => { expect(mockSpinner.start).toHaveBeenCalled(); expect(mockSpinner.info).toHaveBeenCalledWith( - "cache.use.info- options repoName:test-repo", + `${USE_INFO_KEY}- options repoName:test-repo`, ); expect(git.pullRepo).not.toHaveBeenCalled(); }); @@ -101,6 +109,6 @@ describe("getTemplateFromCache", () => { await expect(getTemplateFromCache(options)).rejects.toThrow( "Git clone failed", ); - expect(mockSpinner.fail).toHaveBeenCalledWith("cache.copy.fail"); + expect(mockSpinner.fail).toHaveBeenCalledWith(COPY_FAIL_KEY); }); }); diff --git a/packages/devkit/__tests__/units/core/configs/writer.spec.ts b/packages/devkit/__tests__/units/core/configs/writer.spec.ts index 2376b05..f16e668 100644 --- a/packages/devkit/__tests__/units/core/configs/writer.spec.ts +++ b/packages/devkit/__tests__/units/core/configs/writer.spec.ts @@ -33,6 +33,10 @@ const mockConfig = { }, }; +const CONFIG_SAVE_FAIL_KEY = "errors.config.save_fail"; +const CONFIG_NOT_FOUND_KEY = "errors.config.not_found"; +const TEMPLATE_NOT_FOUND_KEY = "errors.template.not_found"; + describe("Configuration Writer Functions", () => { beforeEach(() => { vi.clearAllMocks(); @@ -56,7 +60,7 @@ describe("Configuration Writer Functions", () => { DevkitError, ); await expect(saveConfig({} as any, "file.json")).rejects.toThrow( - "error.config.save", + CONFIG_SAVE_FAIL_KEY, ); }); @@ -100,7 +104,7 @@ describe("Configuration Writer Functions", () => { "never-refresh", mockConfig as any, ), - ).rejects.toThrow("error.config.not.found"); + ).rejects.toThrow(CONFIG_NOT_FOUND_KEY); }); it("updateTemplateCacheStrategy should throw a ConfigError if the template is not found", async () => { @@ -118,6 +122,6 @@ describe("Configuration Writer Functions", () => { "never-refresh", mockConfig as any, ), - ).rejects.toThrow("error.template.not_found"); + ).rejects.toThrow(TEMPLATE_NOT_FOUND_KEY); }); }); diff --git a/packages/devkit/__tests__/units/core/info/info.spec.ts b/packages/devkit/__tests__/units/core/info/info.spec.ts index 8888c50..3c6869a 100644 --- a/packages/devkit/__tests__/units/core/info/info.spec.ts +++ b/packages/devkit/__tests__/units/core/info/info.spec.ts @@ -84,6 +84,11 @@ const mockGlobalBun = (version: string | undefined) => { } }; +const NEW_GLOBAL_CONFIG_KEY = "commands.info.config.global_expected_location"; +const NEW_LOCAL_CONFIG_KEY = "commands.info.config.local_expected_location"; +const NEW_SHELL_UNKNOWN_KEY = "commands.info.shell.unknown"; +const NEW_PM_NOT_FOUND_KEY = "errors.system.info_package_manager_not_found"; + describe("collectSystemInfo", () => { beforeEach(() => { mockReadAndMergeConfigs.mockResolvedValue({ @@ -109,10 +114,8 @@ describe("collectSystemInfo", () => { expect(info.globalConfig.exists).toBe(false); expect(info.localConfig.exists).toBe(false); - expect(info.globalConfig.path).toBe( - "info.config.global_expected_location", - ); - expect(info.localConfig.path).toBe("info.config.local_expected_location"); + expect(info.globalConfig.path).toBe(NEW_GLOBAL_CONFIG_KEY); + expect(info.localConfig.path).toBe(NEW_LOCAL_CONFIG_KEY); expect(mockReadAndMergeConfigs).toHaveBeenCalledWith({ mergeAll: false, @@ -169,12 +172,12 @@ describe("collectSystemInfo", () => { expect(info.shell).toBe(MOCKED_SHELL); }); - it("should fall back to 'info.shell.unknown' if shell environment variables are missing", async () => { + it("should fall back to 'commands.info.shell.unknown' if shell environment variables are missing", async () => { vi.spyOn(process, "env", "get").mockReturnValueOnce({}); const info = await collectSystemInfo(MOCKED_CLI_VERSION); - expect(info.shell).toBe("info.shell.unknown"); + expect(info.shell).toBe(NEW_SHELL_UNKNOWN_KEY); }); }); @@ -221,7 +224,13 @@ describe("collectSystemInfo", () => { it("Priority 3: Should use 'bun' default when config is missing and nothing is detected", async () => { vi.restoreAllMocks(); - mockReadAndMergeConfigs.mockResolvedValueOnce({ config: undefined }); + vi.spyOn(process, "version", "get").mockReturnValue(MOCKED_NODE_VERSION); + vi.spyOn(process, "env", "get").mockReturnValue({ SHELL: MOCKED_SHELL }); + vi.mock("os", () => ({ default: mockOs })); + + mockReadAndMergeConfigs.mockResolvedValueOnce({ + config: defaultCliConfig, + }); mockGetPackageManager.mockResolvedValueOnce(null); mockExeca.mockImplementationOnce((cmd) => cmd === "bun" @@ -249,7 +258,7 @@ describe("collectSystemInfo", () => { const info = await collectSystemInfo(MOCKED_CLI_VERSION); expect(info.packageManagerVersion).toBe( - mocktFn("info.error.package_manager_not_found", { manager: "bun" }), + mocktFn(NEW_PM_NOT_FOUND_KEY, { manager: "bun" }), ); }); }); diff --git a/packages/devkit/__tests__/units/core/info/project.spec.ts b/packages/devkit/__tests__/units/core/info/project.spec.ts index 253206a..5abd69a 100644 --- a/packages/devkit/__tests__/units/core/info/project.spec.ts +++ b/packages/devkit/__tests__/units/core/info/project.spec.ts @@ -25,6 +25,9 @@ vi.mock("#utils/schema/schema.js", () => ({ FILE_NAMES: { packageJson: "package.json" }, })); +const PACKAGE_ROOT_NOT_FOUND_KEY = "errors.system.package_root_not_found"; +const VERSION_READ_FAIL_KEY = "errors.system.version_read_fail"; + describe("getProjectVersion", () => { beforeEach(() => { vi.clearAllMocks(); @@ -47,8 +50,8 @@ describe("getProjectVersion", () => { }); it("should return '0.0.0' and log an error if package root is not found", async () => { - const rootNotFoundErrorMessage = mocktFn("error.package.root.not_found"); - const expectedErrorMessage = `error.version.read_fail: ${rootNotFoundErrorMessage}`; + const rootNotFoundErrorMessage = mocktFn(PACKAGE_ROOT_NOT_FOUND_KEY); + const expectedErrorMessage = `${VERSION_READ_FAIL_KEY}: ${rootNotFoundErrorMessage}`; mockFindPackageRoot.mockResolvedValue(null); @@ -65,7 +68,7 @@ describe("getProjectVersion", () => { it("should return '0.0.0', log an error, and log stack if reading package.json fails", async () => { const readError = new Error("Failed to read file"); readError.stack = "Mock stack trace"; - const expectedErrorMessage = `error.version.read_fail: Failed to read file`; + const expectedErrorMessage = `${VERSION_READ_FAIL_KEY}: Failed to read file`; mockFindPackageRoot.mockResolvedValue("/mock/project/root"); mockFsReadJson.mockRejectedValue(readError); @@ -85,7 +88,7 @@ describe("getProjectVersion", () => { it("should return '0.0.0' and log a generic error if non-Error is thrown", async () => { const genericError = 12345; - const expectedErrorMessage = "error.version.read_fail"; + const expectedErrorMessage = VERSION_READ_FAIL_KEY; mockFindPackageRoot.mockResolvedValue("/mock/project/root"); mockFsReadJson.mockRejectedValue(genericError); diff --git a/packages/devkit/__tests__/units/core/prompts/prompts.spec.ts b/packages/devkit/__tests__/units/core/prompts/prompts.spec.ts index 3a4a567..2de87ff 100644 --- a/packages/devkit/__tests__/units/core/prompts/prompts.spec.ts +++ b/packages/devkit/__tests__/units/core/prompts/prompts.spec.ts @@ -18,6 +18,11 @@ vi.mock("@inquirer/prompts", () => ({ select: mockSelect, })); +const NEW_LANGUAGE_KEY = "commands.template.add.prompts.language"; +const NEW_PM_KEY = "commands.template.add.prompts.package_manager"; +const NEW_CACHE_KEY = "commands.template.add.prompts.cache_strategy"; +const NEW_NONE_KEY = "common.none"; + describe("prompts", () => { beforeEach(() => { vi.clearAllMocks(); @@ -31,7 +36,7 @@ describe("prompts", () => { const result = await promptForLanguage(); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.language (required)", + message: `${NEW_LANGUAGE_KEY} (required)`, choices: Object.values(ProgrammingLanguage).map((lang) => ({ name: lang, value: lang.toLowerCase(), @@ -48,7 +53,7 @@ describe("prompts", () => { const result = await promptForLanguage(false, "javascript"); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.language (optional)", + message: `${NEW_LANGUAGE_KEY} (optional)`, choices: Object.values(ProgrammingLanguage).map((lang) => ({ name: lang, value: lang.toLowerCase(), @@ -67,7 +72,7 @@ describe("prompts", () => { const result = await promptForPackageManager(); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.package_manager (required)", + message: `${NEW_PM_KEY} (required)`, choices: VALID_PACKAGE_MANAGERS.map((pm) => ({ name: pm, value: pm })), default: undefined, }); @@ -81,10 +86,10 @@ describe("prompts", () => { const result = await promptForPackageManager(false); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.package_manager (optional)", + message: `${NEW_PM_KEY} (optional)`, choices: [ ...VALID_PACKAGE_MANAGERS.map((pm) => ({ name: pm, value: pm })), - { name: "common.none", value: null }, + { name: NEW_NONE_KEY, value: null }, ], default: undefined, }); @@ -100,7 +105,7 @@ describe("prompts", () => { const result = await promptForCacheStrategy(); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.cache_strategy (required)", + message: `${NEW_CACHE_KEY} (required)`, choices: VALID_CACHE_STRATEGIES.map((strategy) => ({ name: strategy, value: strategy, @@ -117,13 +122,13 @@ describe("prompts", () => { const result = await promptForCacheStrategy(false); expect(mockSelect).toHaveBeenCalledWith({ - message: "cli.add_template.prompts.cache_strategy (optional)", + message: `${NEW_CACHE_KEY} (optional)`, choices: [ ...VALID_CACHE_STRATEGIES.map((strategy) => ({ name: strategy, value: strategy, })), - { name: "common.none", value: null }, + { name: NEW_NONE_KEY, value: null }, ], default: undefined, }); diff --git a/packages/devkit/__tests__/units/core/template/printer.spec.ts b/packages/devkit/__tests__/units/core/template/printer.spec.ts index ff08e42..fdb7fa7 100644 --- a/packages/devkit/__tests__/units/core/template/printer.spec.ts +++ b/packages/devkit/__tests__/units/core/template/printer.spec.ts @@ -5,6 +5,11 @@ import { } from "../../../../src/core/template/printer.js"; import { mockLogger, mocktFn } from "../../../../vitest.setup.js"; +const NEW_ALIAS_KEY = "commands.template.add.options.alias"; +const NEW_DESCRIPTION_KEY = "commands.template.add.options.description"; +const NEW_CACHE_KEY = "commands.template.add.options.cache"; +const NEW_PM_KEY = "commands.template.add.options.package_manager"; + describe("print-utils", () => { beforeEach(() => { vi.clearAllMocks(); @@ -51,15 +56,15 @@ describe("print-utils", () => { ); expect(mockLogger.log).toHaveBeenCalledWith( - ` - ${c.green("node-ts-api")} ${c.cyanDim(`(${t("cli.add_template.options.alias")}: nta)`)}${c.dim(`\n ${t("cli.add_template.options.description")}`)}: A simple Node.js API with TypeScript${c.dim("\n Location")}: https://github.com/devkit/node-ts-api${c.dim(`\n ${t("cli.add_template.options.cache")}`)}: daily${c.dim(`\n ${t("cli.add_template.options.package_manager")}`)}: npm\n`, + ` - ${c.green("node-ts-api")} ${c.cyanDim(`(${t(NEW_ALIAS_KEY)}: nta)`)}${c.dim(`\n ${t(NEW_DESCRIPTION_KEY)}`)}: A simple Node.js API with TypeScript${c.dim("\n Location")}: https://github.com/devkit/node-ts-api${c.dim(`\n ${t(NEW_CACHE_KEY)}`)}: daily${c.dim(`\n ${t(NEW_PM_KEY)}`)}: npm\n`, ); expect(mockLogger.log).toHaveBeenCalledWith( - ` - ${c.green("react-component")} ${c.dim(`\n ${t("cli.add_template.options.description")}`)}: A reusable React component${c.dim("\n Location")}: /local/path/to/template\n`, + ` - ${c.green("react-component")} ${c.dim(`\n ${t(NEW_DESCRIPTION_KEY)}`)}: A reusable React component${c.dim("\n Location")}: /local/path/to/template\n`, ); expect(mockLogger.log).toHaveBeenCalledWith( - ` - ${c.green("next-app")} ${c.cyanDim(`(${t("cli.add_template.options.alias")}: nextjs)`)}${c.dim(`\n ${t("cli.add_template.options.description")}`)}: A Next.js application template\n`, + ` - ${c.green("next-app")} ${c.cyanDim(`(${t(NEW_ALIAS_KEY)}: nextjs)`)}${c.dim(`\n ${t(NEW_DESCRIPTION_KEY)}`)}: A Next.js application template\n`, ); expect(mockLogger.log).toHaveBeenCalledWith( @@ -79,7 +84,7 @@ describe("print-utils", () => { ); expect(mockLogger.log).toHaveBeenCalledTimes(4); expect(mockLogger.log).toHaveBeenCalledWith( - ` - ${c.green("react-component")} ${c.dim(`\n ${t("cli.add_template.options.description")}`)}: A reusable React component${c.dim("\n Location")}: /local/path/to/template\n`, + ` - ${c.green("react-component")} ${c.dim(`\n ${t(NEW_DESCRIPTION_KEY)}`)}: A reusable React component${c.dim("\n Location")}: /local/path/to/template\n`, ); mockLogger.log.mockRestore(); @@ -94,7 +99,7 @@ describe("print-utils", () => { ); expect(mockLogger.log).toHaveBeenCalledTimes(4); expect(mockLogger.log).toHaveBeenCalledWith( - ` - ${c.green("node-ts-api")} ${c.cyanDim(`(${t("cli.add_template.options.alias")}: nta)`)}${c.dim(`\n ${t("cli.add_template.options.description")}`)}: A simple Node.js API with TypeScript${c.dim("\n Location")}: https://github.com/devkit/node-ts-api${c.dim(`\n ${t("cli.add_template.options.cache")}`)}: daily${c.dim(`\n ${t("cli.add_template.options.package_manager")}`)}: npm\n`, + ` - ${c.green("node-ts-api")} ${c.cyanDim(`(${t(NEW_ALIAS_KEY)}: nta)`)}${c.dim(`\n ${t(NEW_DESCRIPTION_KEY)}`)}: A simple Node.js API with TypeScript${c.dim("\n Location")}: https://github.com/devkit/node-ts-api${c.dim(`\n ${t(NEW_CACHE_KEY)}`)}: daily${c.dim(`\n ${t(NEW_PM_KEY)}`)}: npm\n`, ); mockLogger.log.mockRestore(); diff --git a/packages/devkit/__tests__/units/core/template/update-project-name.spec.ts b/packages/devkit/__tests__/units/core/template/update-project-name.spec.ts index 4f03150..fcba8b2 100644 --- a/packages/devkit/__tests__/units/core/template/update-project-name.spec.ts +++ b/packages/devkit/__tests__/units/core/template/update-project-name.spec.ts @@ -65,7 +65,7 @@ describe("update-project-name.ts", () => { expect(mockLogger.error).toHaveBeenCalledOnce(); expect(mockLogger.error).toHaveBeenCalledWith( - "error.package.file_not_found", + "errors.system.package_file_not_found", "TEMPL", ); }); @@ -90,7 +90,7 @@ describe("update-project-name.ts", () => { name: newProjectName, }); expect(mockLogger.error).toHaveBeenCalledWith( - "error.package.failed_to_update_project_name: Permission denied", + "errors.system.package_name_update_fail: Permission denied", "TEMPL", ); }); diff --git a/packages/devkit/__tests__/units/scaffolding/cli-runner.spec.ts b/packages/devkit/__tests__/units/scaffolding/cli-runner.spec.ts index 4e8dcd2..1f7c0fb 100644 --- a/packages/devkit/__tests__/units/scaffolding/cli-runner.spec.ts +++ b/packages/devkit/__tests__/units/scaffolding/cli-runner.spec.ts @@ -41,7 +41,7 @@ describe("runCliCommand", () => { await expect(runCliCommand({ ...options, command })).rejects.toThrow( DevkitError, ); - expect(mocktFn).toHaveBeenCalledWith("error.invalid.command", { + expect(mocktFn).toHaveBeenCalledWith("errors.validation.invalid_command", { command: command, }); }); @@ -54,6 +54,6 @@ describe("runCliCommand", () => { await expect(runCliCommand({ ...options, command })).rejects.toThrow( DevkitError, ); - expect(mocktFn).toHaveBeenCalledWith("scaffolding.run.fail"); + expect(mocktFn).toHaveBeenCalledWith("errors.scaffolding.run_fail"); }); }); diff --git a/packages/devkit/__tests__/units/scaffolding/dependencies.spec.ts b/packages/devkit/__tests__/units/scaffolding/dependencies.spec.ts index 0ef6800..ca5f66b 100644 --- a/packages/devkit/__tests__/units/scaffolding/dependencies.spec.ts +++ b/packages/devkit/__tests__/units/scaffolding/dependencies.spec.ts @@ -43,6 +43,6 @@ describe("installDependencies", () => { mockExeca.mockRejectedValueOnce(error); await expect(installDependencies(options)).rejects.toThrow(DevkitError); - expect(mocktFn).toHaveBeenCalledWith("scaffolding.install.fail"); + expect(mocktFn).toHaveBeenCalledWith("errors.scaffolding.install_fail"); }); }); diff --git a/packages/devkit/__tests__/units/scaffolding/javascript.spec.ts b/packages/devkit/__tests__/units/scaffolding/javascript.spec.ts index 8234b13..398992b 100644 --- a/packages/devkit/__tests__/units/scaffolding/javascript.spec.ts +++ b/packages/devkit/__tests__/units/scaffolding/javascript.spec.ts @@ -96,6 +96,8 @@ describe("scaffoldProject", () => { const templateConfig = { location: "http://example.com" }; await scaffoldProject({ ...options, templateConfig }); expect(mockInstallDependencies).toHaveBeenCalled(); - expect(mockLogger.log).toHaveBeenCalledWith("scaffolding.complete.success"); + expect(mockLogger.log).toHaveBeenCalledWith( + expect.stringContaining("messages.success.scaffolding_complete"), + ); }); }); diff --git a/packages/devkit/__tests__/units/scaffolding/local-template.spec.ts b/packages/devkit/__tests__/units/scaffolding/local-template.spec.ts index ff5e63d..910f339 100644 --- a/packages/devkit/__tests__/units/scaffolding/local-template.spec.ts +++ b/packages/devkit/__tests__/units/scaffolding/local-template.spec.ts @@ -71,6 +71,6 @@ describe("copyLocalTemplate", () => { await expect(copyLocalTemplate({ ...options, sourcePath })).rejects.toThrow( DevkitError, ); - expect(mocktFn).toHaveBeenCalledWith("scaffolding.copy.fail"); + expect(mocktFn).toHaveBeenCalledWith("errors.scaffolding.copy_fail"); }); }); diff --git a/packages/devkit/__tests__/units/utils/errors/handler.spec.ts b/packages/devkit/__tests__/units/utils/errors/handler.spec.ts index 016e767..ecfbe1f 100644 --- a/packages/devkit/__tests__/units/utils/errors/handler.spec.ts +++ b/packages/devkit/__tests__/units/utils/errors/handler.spec.ts @@ -5,7 +5,7 @@ import { GitError, DevkitError, } from "../../../../src/utils/errors/base.js"; -import { mockLogger, mocktFn } from "../../../../vitest.setup.js"; +import { mockLogger } from "../../../../vitest.setup.js"; import type { ErrorType } from "../../../../src/utils/logger.js"; mockLogger.dimmed = vi.fn(); @@ -52,7 +52,7 @@ describe("handleErrorAndExit", () => { it("should handle ConfigError with filePath correctly", async () => { const error = new ConfigError("Invalid config", "/path/to/config.json"); const expectedErrorCall = { - message: `error.config.generic: Invalid config`, + message: `errors.config.read_fail: Invalid config`, type: "CONFIG" as ErrorType, }; const expectedDimmedCalls = ["File path: /path/to/config.json"]; @@ -63,7 +63,7 @@ describe("handleErrorAndExit", () => { it("should handle GitError with url correctly", async () => { const error = new GitError("Clone failed", "https://github.com/repo.git"); const expectedErrorCall = { - message: `error.git.generic: Clone failed`, + message: `errors.system.git_generic: Clone failed`, type: "GIT" as ErrorType, }; const expectedDimmedCalls = ["Repository URL: https://github.com/repo.git"]; @@ -74,7 +74,7 @@ describe("handleErrorAndExit", () => { it("should handle DevkitError correctly", async () => { const error = new DevkitError("CLI specific issue"); const expectedErrorCall = { - message: `error.devkit_specific: CLI specific issue`, + message: `errors.generic.devkit_specific: CLI specific issue`, type: "DEV" as ErrorType, }; @@ -84,7 +84,7 @@ describe("handleErrorAndExit", () => { it("should handle a generic Error correctly", async () => { const error = new Error("Something went wrong"); const expectedErrorCall = { - message: `error.unexpected: Something went wrong`, + message: `errors.generic.unexpected: Something went wrong`, type: "ERR" as ErrorType, }; @@ -94,7 +94,7 @@ describe("handleErrorAndExit", () => { it("should handle an unknown error correctly", async () => { const error = "A string error"; const expectedErrorCall = { - message: "error.unknown", + message: "errors.generic.unknown", type: "UNKNOWN" as ErrorType, }; @@ -107,7 +107,7 @@ describe("handleErrorAndExit", () => { cause: causeError, }); const expectedErrorCall = { - message: `error.config.generic: Invalid config`, + message: `errors.config.read_fail: Invalid config`, type: "CONFIG" as ErrorType, }; const expectedDimmedCalls = [ diff --git a/packages/devkit/__tests__/units/utils/logger.spec.ts b/packages/devkit/__tests__/units/utils/logger.spec.ts index 3aaea46..fbd2228 100644 --- a/packages/devkit/__tests__/units/utils/logger.spec.ts +++ b/packages/devkit/__tests__/units/utils/logger.spec.ts @@ -151,11 +151,13 @@ describe("logger utility", () => { logger.error(message, errorType); expect(timestampDimMock).toHaveBeenCalledWith(`[${MOCK_TIME}]`); - expect(mockChalk.bold.red).toHaveBeenCalledWith(`[${errorType}]`); + expect(mockChalk.bold.red).toHaveBeenCalledWith( + `❌${MOCK_TIMESTAMP}::[${errorType}]`, + ); - expect(mockChalk.redBright).toHaveBeenCalledWith(`❌ ${message}`); + expect(mockChalk.redBright).toHaveBeenCalledWith(`${message}`); - const expectedOutput = `${MOCK_TIMESTAMP} [bold_red] [${errorType}] [redBright] ❌ ${message}`; + const expectedOutput = `[bold_red] ❌${MOCK_TIMESTAMP}::[${errorType}]>> [redBright] ${message}`; expect(mockConsoleError).toHaveBeenCalledWith(expectedOutput); }); @@ -163,7 +165,9 @@ describe("logger utility", () => { const message = "Generic error"; logger.error(message); - expect(mockChalk.bold.red).toHaveBeenCalledWith(`[UNKNOWN]`); + expect(mockChalk.bold.red).toHaveBeenCalledWith( + `❌[dim] [10:00:00]::[UNKNOWN]`, + ); }); it("dimmed() should call console.log with dim chalk and trim the message", () => { diff --git a/packages/devkit/__tests__/units/utils/validations/config.spec.ts b/packages/devkit/__tests__/units/utils/validations/config.spec.ts index 1c9f1d5..9986819 100644 --- a/packages/devkit/__tests__/units/utils/validations/config.spec.ts +++ b/packages/devkit/__tests__/units/utils/validations/config.spec.ts @@ -14,6 +14,8 @@ import { VALID_CACHE_STRATEGIES, } from "../../../../src/utils/schema/schema.js"; +const NEW_ERROR_KEY = "errors.validation.invalid_value"; + describe("validatePackageManager", () => { it("should not throw an error for a valid package manager", () => { const validPm = PackageManagers.Npm; @@ -24,7 +26,7 @@ describe("validatePackageManager", () => { const invalidPm = "invalid-pm"; expect(() => validatePackageManager(invalidPm)).toThrow(DevkitError); expect(() => validatePackageManager(invalidPm)).toThrow( - mocktFn("error.invalid.value", { + mocktFn(NEW_ERROR_KEY, { key: "defaultPackageManager", options: Object.values(PackageManagers).join(", "), }), @@ -42,7 +44,7 @@ describe("validateCacheStrategy", () => { const invalidStrategy = "invalid-strategy"; expect(() => validateCacheStrategy(invalidStrategy)).toThrow(DevkitError); expect(() => validateCacheStrategy(invalidStrategy)).toThrow( - mocktFn("error.invalid.value", { + mocktFn(NEW_ERROR_KEY, { key: "cacheStrategy", options: VALID_CACHE_STRATEGIES.join(", "), }), @@ -60,7 +62,7 @@ describe("validateLanguage", () => { const invalidLang = "invalid-lang"; expect(() => validateLanguage(invalidLang)).toThrow(DevkitError); expect(() => validateLanguage(invalidLang)).toThrow( - mocktFn("error.invalid.value", { + mocktFn(NEW_ERROR_KEY, { key: "language", options: Object.values(TextLanguages).join(", "), }), @@ -78,8 +80,8 @@ describe("validateProgrammingLanguage", () => { const invalidLang = "invalid-prog-lang"; expect(() => validateProgrammingLanguage(invalidLang)).toThrow(DevkitError); expect(() => validateProgrammingLanguage(invalidLang)).toThrow( - mocktFn("error.invalid.value", { - key: "language", + mocktFn(NEW_ERROR_KEY, { + key: "Programming Language", options: Object.values(ProgrammingLanguage) .map((value) => value.toLowerCase()) .join(", "), diff --git a/packages/devkit/__tests__/units/utils/validations/templates.spec.ts b/packages/devkit/__tests__/units/utils/validations/templates.spec.ts index 9ae542b..c608103 100644 --- a/packages/devkit/__tests__/units/utils/validations/templates.spec.ts +++ b/packages/devkit/__tests__/units/utils/validations/templates.spec.ts @@ -69,7 +69,7 @@ describe("Templates Validation", () => { expect(mockSpinner.succeed).not.toHaveBeenCalled(); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.invalid.github-repo", { url: githubUrl }), + mocktFn("errors.validation.github_repo", { url: githubUrl }), ), mockSpinner, ); @@ -104,7 +104,7 @@ describe("Templates Validation", () => { expect(mockFs.existsSync).toHaveBeenCalledWith(normalizedPath); expect(mockHandleErrorAndExit).toHaveBeenCalledWith( new DevkitError( - mocktFn("error.invalid.local-path", { path: normalizedPath }), + mocktFn("errors.validation.local_path", { path: normalizedPath }), ), mockSpinner, ); @@ -121,14 +121,14 @@ describe("Templates Validation", () => { it("should throw a DevkitError for an empty alias", () => { expect(() => validateAlias("")).toThrow(DevkitError); expect(() => validateAlias("")).toThrow( - mocktFn("error.invalid.alias.empty"), + mocktFn("errors.validation.alias_empty"), ); }); it("should throw a DevkitError for an alias that is too short (< 2 chars)", () => { expect(() => validateAlias("a")).toThrow(DevkitError); expect(() => validateAlias("a")).toThrow( - mocktFn("error.invalid.alias.too-short"), + mocktFn("errors.validation.alias_too_short"), ); }); }); @@ -143,7 +143,7 @@ describe("Templates Validation", () => { it("should throw a DevkitError for an empty description", () => { expect(() => validateDescription("")).toThrow(DevkitError); expect(() => validateDescription("")).toThrow( - mocktFn("error.invalid.description.empty"), + mocktFn("errors.validation.description_empty"), ); }); @@ -151,7 +151,7 @@ describe("Templates Validation", () => { const shortDescription = "This short"; expect(() => validateDescription(shortDescription)).toThrow(DevkitError); expect(() => validateDescription(shortDescription)).toThrow( - mocktFn("error.invalid.description.too-short"), + mocktFn("errors.validation.description_too_short"), ); }); }); diff --git a/packages/devkit/__tests__/units/utils/validations/validateConfigValue.spec.ts b/packages/devkit/__tests__/units/utils/validations/validateConfigValue.spec.ts index f70ff0d..9df5c38 100644 --- a/packages/devkit/__tests__/units/utils/validations/validateConfigValue.spec.ts +++ b/packages/devkit/__tests__/units/utils/validations/validateConfigValue.spec.ts @@ -42,7 +42,7 @@ describe("validateConfigValue", () => { expect(() => validateConfigValue(invalidKey, value)).toThrow(DevkitError); expect(() => validateConfigValue(invalidKey, value)).toThrow( - mocktFn("error.invalid.key", { + mocktFn("errors.validation.invalid_key", { key: invalidKey, keys: Object.keys(configAliases).join(", "), }), @@ -81,7 +81,7 @@ describe("validateConfigValue", () => { const value = "someValue"; expect(() => validateConfigValue(key, value)).not.toThrowError( new DevkitError( - mocktFn("error.invalid.key", { + mocktFn("errors.validation.invalid_key", { key, keys: Object.keys(mockAliasesWithDummy).join(", "), }), diff --git a/packages/devkit/locales/en.json b/packages/devkit/locales/en.json index f39963b..826bfa0 100644 --- a/packages/devkit/locales/en.json +++ b/packages/devkit/locales/en.json @@ -1,472 +1,377 @@ { - "program": { - "description": "A powerful devkit for scaffolding new projects.", - "initialized": "CLI initialized successfully.", - "initializing": "Initializing CLI...", - "verbose_option": "Enable verbose logging for detailed output" + "common": { + "yes": "Yes", + "no": "No", + "none": "None" }, - "new": { - "command": { - "description": "Create a new project from a template" + "program": { + "program": { + "description": "A powerful devkit for scaffolding new projects.", + "verbose_option": "Enable verbose logging for detailed output" }, - "project": { - "language": { - "argument": "The programming language of the template (e.g., 'react', 'node')" - }, - "name": { - "argument": "The name of your new project" - }, - "template": { - "option": { - "description": "The name of the template to use (e.g., 'ts', 'simple-api')" - } - }, - "scaffolding": "Scaffolding project '{projectName}' with the '{template}' template...", - "success": "✔ Project '{projectName}' created successfully!", - "fail": "❌ Failed to scaffold project: {error}" - } - }, - "list": { - "command": { - "description": "List all available templates in the configuration.", - "language": { - "argument": "The language to filter templates by" - }, - "global": { - "option": "List templates from the global configuration." - }, - "local": { - "option": "List templates from the local configuration." - }, - "all": { - "option": "List templates from both local and global configurations." - }, - "filter": { - "option": "Filter templates by name or substring." - } + "version": { + "description": "Output the current version of the CLI." }, - "templates": { - "loading": "Loading templates...", - "not_found": "No templates found in the configuration file.", - "no_local_config": "No local configuration found.", - "no_global_config": "No global configuration found.", - "using_local_and_global": "Using templates from both local and global configurations.", - "using_local_only": "Using templates from local configuration only, as no global configuration was found.", - "using_global_only": "Using templates from global configuration only, as no local configuration was found.", - "using_global": "Using templates from global configuration.", - "using_local": "Using templates from local configuration.", - "using_global_fallback": "No local configuration found. Using templates from global configuration.", - "header": "Available Templates:" + "help": { + "description": "Display help for a command." }, - "config": { - "settings_header": "Configuration Settings:" + "status": { + "initializing": "Initializing CLI..." } }, - "remove_template": { - "command": { - "description": "Remove a template from the configuration." - }, - "start": "Removing template(s) from configuration...", - "success": "Successfully removed {count} template(s) ({templateName}) from {language}.", - "language": { - "argument": "The programming language of the template to remove." - }, - "name": { - "argument": "The name or alias of the template to remove." - }, - "option": { - "global": "Remove the template from the global configuration instead of the local one." - }, - "not_found_warning": "⚠️ Warning: The following templates were not found: {template}" - }, - "version": { - "description": "Output the current version of the CLI." - }, - "help": { - "description": "Display help for a command." - }, - "scaffolding": { - "project": { - "start": "Scaffolding {language} project: {project}", - "success": "✔ {language} project '{project}' created successfully!", - "fail": "❌ Failed to scaffold {language} project: {error}" - }, - "copy": { - "start": "📂 Copying local template files...", - "success": "✔ Local template copied successfully!", - "fail": "❌ Failed to copy local template." - }, - "run": { - "start": "📦 Running official CLI command: {command}...", - "success": "✔ Official CLI command ran successfully!", - "fail": "❌ Failed to run official CLI command." - }, - "install": { - "start": "📦 Installing dependencies with {pm}...", - "success": "✔ Dependencies installed successfully!", - "fail": "❌ Failed to install dependencies." - }, - "complete": { - "success": "\n🚀 Project created successfully! 🎉", - "next_steps": "\n| Next steps:\n" - } - }, - "config": { - "command": { - "description": "Manage DevKit settings" - }, - "interactive": { - "prompt_action": "What would you like to configure?", - "action": { - "settings": "Settings", - "templates": "Templates" - }, - "prompt_setting_key": "Choose a setting to modify:", - "prompt_template_name": "Choose a template to update:", - "prompt_language": "Choose a language:", - "prompt_template_property": "Choose a property to update:", - "prompt_new_value": "Enter new value for {key}:", - "success": "Configuration session complete." + "commands": { + "new": { + "command": { + "description": "Create a new project from a template" + }, + "project": { + "language": { + "argument": "The programming language of the template (e.g., 'react', 'node')" + }, + "name": { + "argument": "The name of your new project" + }, + "template": { + "option": { + "description": "The name of the template to use (e.g., 'ts', 'simple-api')" + } + } + } }, - "set": { + "list": { "command": { - "description": "Set one or more configuration values. \n\nAvailable keys:\n pm, packageManager - Sets the default package manager to use for new projects.\n Possible values: {pmValues}\n cache, cacheStrategy - Sets the global caching behavior for remote templates.\n Possible values: 'always-refresh', 'never-refresh', 'daily'\n language, lg - Sets the language of the CLI. Possible values: 'en', 'fr'\n" - }, - "argument": { - "description": "A list of key-value pairs to set (e.g., 'pm npm language fr')" + "description": "List all available templates in the configuration.", + "language": { + "argument": "The language to filter templates by" + }, + "filter": { + "option": "Filter templates by name or substring." + } }, - "option": { - "global": "Update the global configuration instead of the local one.", - "bulk": "Sets multiple configuration properties." + "options": { + "global": "List templates from the global configuration.", + "local": "List templates from the local configuration.", + "all": "List templates from both local and global configurations." }, - "updating": "Updating configuration...", - "success": "Configuration updated successfully!" + "output": { + "header": "Available Templates:", + "settings_header": "Configuration Settings:" + } }, - "loading": "Loading configuration...", - "get": { + "config": { "command": { - "description": "Get one or more configuration values." + "description": "Manage DevKit settings" + }, + "interactive": { + "prompt_action": "What would you like to configure?", + "action": { + "settings": "Settings", + "templates": "Templates" + }, + "prompt_setting_key": "Choose a setting to modify:", + "prompt_template_name": "Choose a template to update:", + "prompt_language": "Choose a language:", + "prompt_template_property": "Choose a property to update:", + "prompt_new_value": "Enter new value for {key}:", + "success": "Configuration session complete." }, - "argument": { - "description": "The key of the configuration value to get (e.g., 'pm', 'language')." + "set": { + "command": { + "description": "Set one or more configuration values. \n\nAvailable keys:\n pm, packageManager - Sets the default package manager to use for new projects.\n Possible values: {pmValues}\n cache, cacheStrategy - Sets the global caching behavior for remote templates.\n Possible values: 'always-refresh', 'never-refresh', 'daily'\n language, lg - Sets the language of the CLI. Possible values: 'en', 'fr'\n" + }, + "argument": { + "description": "A list of key-value pairs to set (e.g., 'pm npm language fr')" + }, + "option": { + "global": "Update the global configuration instead of the local one.", + "bulk": "Sets multiple configuration properties." + } }, - "option": { - "global": "Get the global configuration instead of the local one." + "get": { + "command": { + "description": "Get one or more configuration values." + }, + "argument": { + "description": "The key of the configuration value to get (e.g., 'pm', 'language')." + }, + "option": { + "global": "Get the global configuration instead of the local one." + } }, - "loading": "Loading configuration...", - "success": "Configuration loaded successfully!", - "not_found": "Configuration key '{key}' not found.", - "fallback": { - "global": "No global configuration file found. Displaying default settings instead.", - "local": "No local configuration file found. Displaying default settings instead.", - "local_to_global": "No local configuration file found. Displaying global settings instead." + "cache": { + "command": { + "description": "Update the cache strategy for a specific template." + }, + "template": { + "argument": "The name of the template to update" + }, + "strategy": { + "argument": "The new cache strategy" + }, + "success": "Cache strategy for template '{template}' updated to '{strategy}'" + }, + "update_template": { + "command": { + "description": "Update properties of a template, such as its alias, description, or location." + }, + "language": { + "argument": "The programming language of the template to update." + }, + "template": { + "argument": "The name or alias of the template to update." + }, + "options": { + "new_name": "A new name for the template.", + "description": "A new brief description for the template.", + "alias": "A new alias for the template.", + "location": "A new location for the template.", + "cache_strategy": "A new cache strategy for the template.", + "package_manager": "A new package manager for the template.", + "global": "Update the template in the global configuration instead of the local one." + } }, - "source": { - "local": "Using local configuration.", - "global": "Using global configuration.", - "local_and_global": "Using local and global configurations." + "init": { + "command": { + "description": "Initializes a configuration file with default settings." + }, + "option": { + "local": "Initialize a local configuration file instead of a global one.", + "global": "Initialize a global configuration file instead of a local one." + }, + "confirm_overwrite": "Config file already exists at {path}. Do you want to overwrite it?", + "confirm_monorepo_overwrite": "A config file exists in the monorepo root at {path}. Do you want to create a new one in the current package?", + "monorepo_location": "A monorepo root was found, but no config file exists there. Where do you want to create the new config file?", + "location_current": "Create it in the current package.", + "location_root": "Create it in the monorepo root.", + "aborted": "Operation aborted. No changes were made." + }, + "list": { + "command": { + "description": "List available settings and templates." + }, + "options": { + "all": "List templates from both local and global configurations (merged)." + }, + "settings_header": "Configuration Settings:", + "templates_header": "Available Templates:" } }, - "init": { - "command": { - "description": "Initializes a configuration file with default settings." - }, - "start": "Initializing config...", - "success": "Configuration file created successfully!", - "fail": "Failed to initialize config.", - "initializing": "Initializing configuration...", - "option": { - "local": "Initialize a local configuration file instead of a global one.", - "global": "Initialize a global configuration file instead of a local one." + "template": { + "add": { + "description": "Add a new template to the configuration", + "options": { + "description": "A brief description of the template", + "alias": "A short alias for the template", + "cache": "The cache strategy for the template", + "package_manager": "The package manager to use for the template" + }, + "prompts": { + "language": "Template Language", + "template_name": "Enter the template name", + "location": "Enter the template location", + "description": "Enter the template description", + "alias": "Enter a short alias for the template", + "cache_strategy": "Cache Strategy", + "package_manager": "Package Manager" + } }, - "confirm_overwrite": "Config file already exists at {path}. Do you want to overwrite it?", - "confirm_monorepo_overwrite": "A config file exists in the monorepo root at {path}. Do you want to create a new one in the current package?", - "found_existing": "Configuration file already exists at {path}. Skipping initialization.", - "monorepo_location": "A monorepo root was found, but no config file exists there. Where do you want to create the new config file?", - "location_current": "Create it in the current package.", - "location_root": "Create it in the monorepo root.", - "aborted": "Operation aborted. No changes were made." + "remove": { + "command": { + "description": "Remove a template from the configuration." + }, + "language": { + "argument": "The programming language of the template to remove." + }, + "name": { + "argument": "The name or alias of the template to remove." + }, + "option": { + "global": "Remove the template from the global configuration instead of the local one." + } + } }, - "update": { + "info": { "command": { - "description": "Update properties of a template, such as its alias, description, or location." + "description": "Display system and environment information for debugging." }, - "language": { - "argument": "The programming language of the template to update." + "header": { + "cli": "Scaffolder CLI", + "runtime": "Runtime Environment", + "os_details": "Operating System Details", + "config_files": "Configuration Files" }, - "template": { - "argument": "The name or alias of the template to update." + "cli": { + "version": "Version" }, - "option": { - "new_name": "A new name for the template.", - "description": "A new brief description for the template.", - "alias": "A new alias for the template.", - "location": "A new location for the template.", - "cache_strategy": "A new cache strategy for the template.", - "package_manager": "A new package manager for the template.", - "global": "Update the template in the global configuration instead of the local one." + "runtime": { + "runtime_name": "Runtime", + "runtime_version": "Runtime Version", + "package_manager": "Package Manager" }, - "updating": "Updating template(s) '{templateName}' in configuration...", - "success": "✔ Template '{templateName}' updated successfully!", - "success_name": "✔ Template '{oldName}' updated to '{newName}' successfully!", - "single_fail": "❌ Failed to update '{templateName}': {error}", - "success_summary": "Successfully updated {count} ({templateName}) template(s) from {language}!" - }, - "cache": { - "command": { - "description": "Update the cache strategy for a specific template.", - "start": "Updating cache strategy for template '{template}'...", - "fail": "❌ Failed to update cache strategy for template '{template}'.", - "success": "✨ Cache strategy for template '{template}' updated to '{strategy}'." + "os": { + "type_version": "OS Type/Release", + "architecture": "Architecture", + "shell": "Active Shell", + "home_dir": "Home Directory" }, - "template": { - "argument": "The name of the template to update" + "shell": { + "unknown": "Unknown" }, - "strategy": { - "argument": "The new cache strategy" - }, - "start": "Updating cache strategy for template '{template}'...", - "success": "Cache strategy for template '{template}' updated to '{strategy}'", - "fail": "Failed to update cache strategy for template '{template}'", - "updating": "Updating cache..." - }, - "check": { - "local": "Checking for local or monorepo configuration...", - "global": "Local config not found. Checking for global configuration..." - }, - "found": { - "local": "Found and using local configuration at {path}.", - "global": "No local config found. Using global configuration at {path}." - }, - "not": { - "found": { - "default": "No configuration file found. Using default settings." + "config": { + "global_path": "Global Config Path", + "local_path": "Local Config Path", + "found": "[FOUND]", + "not_found": "[NOT FOUND]", + "global_expected_location": "in your home directory", + "local_expected_location": "in the current working directory" } + } + }, + "messages": { + "success": { + "program_initialized": "CLI initialized successfully.", + "new_project": "✔ Project '{projectName}' created successfully!", + "template_cloned_or_cache": "Template cloned and cached successfully.", + "template_added": "Template '{templateName}' added successfully!", + "template_removed": "Successfully removed {count} template(s) ({templateName}) from {language}.", + "template_updated": "✔ Template '{templateName}' updated successfully!", + "template_name_updated": "✔ Template '{oldName}' updated to '{newName}' successfully!", + "template_summary_updated": "Successfully updated {count} ({templateName}) template(s) from {language}!", + "config_updated": "Configuration updated successfully!", + "config_initialized": "Configuration file created successfully!", + "info_collected": "System information collected.", + "scaffolding_complete": "\n🚀 Project created successfully! 🎉", + "next_steps": "\n| Next steps:\n" }, - "template": { - "description": "Update a template directly. Use with specific options below." + "status": { + "scaffolding_project": "Scaffolding project '{projectName}' with the '{template}' template...", + "config_loading": "Loading configuration...", + "config_updating": "Updating configuration...", + "config_init_start": "Initializing config...", + "template_adding": "Adding template '{templateName}' to configuration...", + "template_removing": "Removing template(s) from configuration...", + "template_updating": "Updating template(s) '{templateName}' in configuration...", + "cache_updating": "Updating cache...", + "cache_clone_start": "✨ Cloning new template from {url}...", + "cache_refresh_start": "🔄 Refreshing cached template...", + "cache_use_info": "🚀 Using cached template for {repoName}.", + "cache_copy_start": "📂 Copying cached template to project directory...", + "info_loading": "Collecting system and environment details...", + "config_source_local_and_global": "Using local and global configurations.", + "config_source_local": "Using local configuration.", + "config_source_global": "Using global configuration.", + "templates_using_global_fallback": "No local configuration found. Using templates from global configuration." + }, + "scaffolding": { + "start": "Scaffolding {language} project: {project}", + "copy_start": "📂 Copying local template files...", + "copy_success": "✔ Local template copied successfully!", + "run_start": "📦 Running official CLI command: {command}...", + "run_success": "✔ Official CLI command ran successfully!", + "install_start": "📦 Installing dependencies with {pm}...", + "install_success": "✔ Dependencies installed successfully!" + }, + "config_source": { + "local_and_global": "Using local and global configurations.", + "local": "Using local configuration.", + "global": "Using global configuration.", + "local_only": "Using templates from local configuration only, as no global configuration was found.", + "global_only": "Using templates from global configuration only, as no local configuration was found.", + "global_fallback": "No local configuration found. Using templates from global configuration.", + "using_local_and_global": "Using templates from both local and global configurations." + }, + "config_check": { + "check_local": "Checking for local or monorepo configuration...", + "check_global": "Local config not found. Checking for global configuration...", + "found_local": "Found and using local configuration at {path}.", + "found_global": "No local config found. Using global configuration at {path}.", + "found_existing": "Configuration file already exists at {path}. Skipping initialization." } }, - "error": { - "invalid": { - "key": "Invalid key: '{key}'. Valid keys are: {keys}", - "value": "Invalid value for {key}. Valid options are: {options}", - "command": "Invalid command in configuration: '{command}'", - "cache_strategy": "Invalid cache strategy: '{value}'. Valid options are: {options}", - "package_manager": "Invalid package manager: '{value}'. Valid options are: {options}", + "errors": { + "generic": { + "unexpected": "An unexpected error occurred", + "unknown": "An unknown error occurred", + "devkit_specific": "Devkit encountered an unexpected internal issue", + "aborted": "Operation aborted. No changes were made." + }, + "validation": { + "invalid_key": "Invalid key: '{key}'. Valid keys are: {keys}", + "invalid_value": "Invalid value for {key}. Valid options are: {options}", + "invalid_command": "Invalid command in configuration: '{command}'", + "invalid_cache_strategy": "Invalid cache strategy: '{value}'. Valid options are: {options}", + "invalid_package_manager": "Invalid package manager: '{value}'. Valid options are: {options}", "remove_required": "Cannot remove a required property: '{key}'.", - "github-repo": "Invalid or inaccessible GitHub repository URL: {url}", - "local-path": "Local path not found: {path}", + "alias_exists": "Alias '{alias}' already exists for another template in this language. Please choose a different alias.", + "alias_empty": "Template alias cannot be empty.", + "alias_too_short": "Template alias must be at least 10 letters long.", + "description_empty": "Template description cannot be empty.", + "description_too_short": "Template description must be at least 6 words long.", + "github_repo": "Invalid or inaccessible GitHub repository URL: {url}", + "local_path": "Local path not found: {path}", "location": "Invalid template location. Please provide a valid GitHub URL or local path.", - "alias": { - "exists": "Alias '{alias}' already exists for another template in this language. Please choose a different alias.", - "empty": "Template alias cannot be empty.", - "too-short": "Template alias must be at least 10 letters long." - }, - "description": { - "empty": "Template description cannot be empty.", - "too-short": "Template description must be at least 6 words long." - } + "language_required": "Language is required.", + "template_name_required": "Template name is required.", + "location_required": "Location is required.", + "description_required": "Description is required." }, "config": { - "parse": "Failed to parse {file}. Using default configuration.", - "save": "❌ Failed to save configuration file: {file}", + "parse_fail": "Failed to parse {file}. Using default configuration.", + "save_fail": "❌ Failed to save configuration file: {file}", "exists": "File already exists at {path}. Use 'config set' to update it.", - "not": { - "found": "Configuration file not found." - }, - "global": { - "not": { - "found": "Global configuration file not found. Run 'devkit config init --global' to create one." - } - }, - "local": { - "not": { - "found": "No local configuration file found. Run 'devkit config init --local' to create one." - } - }, - "generic": "Configuration error", - "init": { - "fail": "Failed to initialize configuration.", - "local_and_global": "Cannot use both --local and --global flags at the same time." - }, - "read": "Failed to read configuration at {path}.", + "not_found": "Configuration file not found.", + "read_fail": "Failed to read configuration at {path}.", "no_file_found": "No configuration file found. Run 'devkit config init' to create a global one, or 'devkit config init --local' to create a local one.", - "no_file_found_local": "No local configuration file found. Run 'devkit config init --local' to create one." - }, - "file": { - "not_found": "Could not find file '{fileName}'." - }, - "package": { - "root": { - "not_found": "Could not find project root containing package.json." - }, - "file_not_found": "{file} not found at {path}", - "failed_to_update_project_name": "Failed to update project name:" - }, - "version": { - "read_fail": "Failed to read package.json version." + "no_file_found_local": "No local configuration file found. Run 'devkit config init --local' to create one.", + "init_fail": "Failed to initialize configuration.", + "init_local_and_global": "Cannot use both --local and --global flags at the same time.", + "global_not_found": "Global configuration file not found. Run 'devkit config init --global' to create one.", + "local_not_found": "No local configuration file found. Run 'devkit config init --local' to create one.", + "get_key_not_found": "Configuration key '{key}' not found." }, "template": { "not_found": "Template '{template}' not found in configuration.", "language_not_found": "Template not found for the '{language}' language.", - "exists": "Template '{template}' already exists in the configuration. Use 'devkit config set' to update it." - }, - "alias": { - "exists": "Alias '{alias}' already exists for another template in this language. Please choose a different alias." - }, - "install": { - "message": "Installation error:" + "exists": "Template '{template}' already exists in the configuration. Use 'devkit config set' to update it.", + "single_fail": "❌ Failed to update '{templateName}': {error}" }, "scaffolding": { + "fail": "❌ Failed to scaffold project: {error}", + "copy_fail": "❌ Failed to copy local template.", + "run_fail": "❌ Failed to run official CLI command.", + "install_fail": "❌ Failed to install dependencies.", "unexpected": "An unexpected error occurred during scaffolding.", - "language": { - "not_found": "Scaffolding language not found in configuration: '{language}'" - } + "language_not_found": "Scaffolding language not found in configuration: '{language}'" }, "command": { - "config": { - "cache": { - "fail": "Failed to update cache strategy for template '{template}'" - } - }, - "set": { - "invalid_arguments_count": "Invalid number of arguments. Please provide key-value pairs.", - "description": "Set a configuration value directly using dot-notation.", - "invalid_format": "The values for the '--set' option must be a series of key-value pairs (e.g., --set key1 value1 key2 value2)." - }, - "update": { - "no_options": "No update options were provided. Please specify at least one option to update (e.g., --new-name, --description, etc.)." - }, - "mutually_exclusive_options": "You can't use the `{options}` options together. They are mutually exclusive." - }, - "save": { - "local": { - "config": { - "not_found": "Failed to save local configuration. File not found at '{path}'." - } - } + "set_invalid_arguments_count": "Invalid number of arguments. Please provide key-value pairs.", + "set_invalid_format": "The values for the '--set' option must be a series of key-value pairs (e.g., --set key1 value1 key2 value2).", + "update_no_options": "No update options were provided. Please specify at least one option to update (e.g., --new-name, --description, etc.).", + "mutually_exclusive_options": "You can't use the `{options}` options together. They are mutually exclusive.", + "missing_required_options": "Missing required options. Please provide all of the following: {fields}.\nAlternatively, run with the '-i' or '--interactive' flag for guided setup." + }, + "system": { + "file_not_found": "Could not find file '{fileName}'.", + "package_root_not_found": "Could not find project root containing package.json.", + "package_file_not_found": "{file} not found at {path}", + "package_name_update_fail": "Failed to update project name:", + "version_read_fail": "Failed to read package.json version.", + "git_generic": "Git error", + "info_package_manager_not_found": "{manager} version not found. Is it installed?" }, - "git": { - "generic": "Git error" - }, - "list": { - "global_and_all": "Cannot use both global and " - }, - "unexpected": "An unexpected error occurred", - "unknown": "An unknown error occurred", - "language_config_not_found": "Programming language '{language}' not found in configuration", - "language_required": "Language is required.", - "template_name_required": "Template name is required.", - "location_required": "Location is required.", - "description_required": "Description is required.", - "missing_required_options": { - "add_template": "Missing required options. Please provide all of the following: {fields}.\nAlternatively, run with the '-i' or '--interactive' flag for guided setup." - }, - "devkit_specific": "Devkit encountered an unexpected internal issue" - }, - "cache": { - "clone": { - "start": "✨ Cloning new template from {url}...", - "success": "✔ Template cloned successfully!", - "fail": "❌ Failed to clone repository." - }, - "refresh": { - "start": "🔄 Refreshing cached template...", - "success": "✔ Template refreshed successfully!", - "fail": "❌ Failed to refresh template." - }, - "use": { - "info": "🚀 Using cached template for {repoName}." - }, - "copy": { - "start": "📂 Copying cached template to project directory...", - "success": "✔ Files copied successfully!", - "fail": "❌ Failed to copy files from cache." - } - }, - "warning": { - "no": { - "local": { - "config": "No local project configuration found. Using global or default settings." - } - }, - "global": { - "config": { - "not": { - "initialized": "Global configuration not initialized. Run 'devkit config init' to create one." - } - } - }, - "no_config_found": "⚠️ No configuration file found. Using default settings.", - "no_command_or_option_provided": "Warning: No command or option provided. Use `dk config --help` to see available commands." - }, - "cli": { - "add_template": { - "description": "Add a new template to the configuration", - "options": { - "description": "A brief description of the template", - "alias": "A short alias for the template", - "cache": "The cache strategy for the template", - "package_manager": "The package manager to use for the template" - }, - "adding": "Adding template '{templateName}' to configuration...", - "success": "Template '{templateName}' added successfully!", - "prompts": { - "language": "Enter the programming language for the template", - "template_name": "Enter the template name", - "location": "Enter the template location", - "description": "Enter the template description", - "alias": "Enter a short alias for the template", - "cache_strategy": "Select a cache strategy for the template", - "package_manager": "Select a package manager for the template" - } + "cache": { + "clone_fail": "❌ Failed to clone repository.", + "refresh_fail": "❌ Failed to refresh template.", + "copy_fail": "❌ Failed to copy files from cache." } }, - "common": { - "yes": "Yes", - "no": "No", - "none": "None" - }, - "info": { - "command": { - "description": "Display system and environment information for debugging." - }, - "loading": "Collecting system and environment details...", - "success_message": "System information collected.", - "header": { - "cli": "Scaffolder CLI", - "runtime": "Runtime Environment", - "os_details": "Operating System Details", - "config_files": "Configuration Files" - }, - "cli": { - "version": "Version" - }, - "runtime": { - "runtime_name": "Runtime", - "runtime_version": "Runtime Version", - "package_manager": "Package Manager" - }, - "os": { - "type_version": "OS Type/Release", - "architecture": "Architecture", - "shell": "Active Shell", - "home_dir": "Home Directory" - }, - "shell": { - "unknown": "Unknown" - }, - "config": { - "global_path": "Global Config Path", - "local_path": "Local Config Path", - "found": "[FOUND]", - "not_found": "[NOT FOUND]", - "global_expected_location": "in your home directory", - "local_expected_location": "in the current working directory" - }, - "error": { - "package_manager_not_found": "{manager} version not found. Is it installed?" - } + "warnings": { + "not_found": "⚠️ No configuration file found. Using default settings.", + "no_local_config": "No local project configuration found. Using global or default settings.", + "global_not_initialized": "Global configuration not initialized. Run 'devkit config init' to create one.", + "template_not_found": "No templates found in the configuration file.", + "templates_not_found": "The following templates were not found: {templates}.", + "no_command_provided": "Warning: No command or option provided. Use `dk config --help` to see available commands.", + "no_config_found": "⚠️ No configuration file found. Using default configuration." } } diff --git a/packages/devkit/locales/fr.json b/packages/devkit/locales/fr.json index 43eb59a..643b556 100644 --- a/packages/devkit/locales/fr.json +++ b/packages/devkit/locales/fr.json @@ -1,472 +1,377 @@ { - "program": { - "description": "Une puissante boîte à outils pour l'échafaudage de nouveaux projets.", - "initialized": "CLI initialisée avec succès.", - "initializing": "Initialisation de la CLI...", - "verbose_option": "Activer la journalisation verbeuse pour une sortie détaillée" - }, - "new": { - "command": { - "description": "Créer un nouveau projet à partir d'un modèle" - }, - "project": { - "language": { - "argument": "Le langage de programmation du modèle (par exemple, 'react', 'node')" - }, - "name": { - "argument": "Le nom de votre nouveau projet" - }, - "template": { - "option": { - "description": "Le nom du modèle à utiliser (par exemple, 'ts', 'simple-api')" - } - }, - "scaffolding": "Échafaudage du projet '{projectName}' avec le modèle '{template}'...", - "success": "✔ Projet '{projectName}' créé avec succès !", - "fail": "❌ Échec de l'échafaudage du projet : {error}" - } - }, - "list": { - "command": { - "description": "Lister tous les modèles disponibles dans la configuration.", - "language": { - "argument": "Le langage pour filtrer les modèles" - }, - "global": { - "option": "Lister les modèles de la configuration globale." - }, - "local": { - "option": "Lister les modèles de la configuration locale." - }, - "all": { - "option": "Lister les modèles des configurations locale et globale." - }, - "filter": { - "option": "Filtrer les modèles par nom ou sous-chaîne." - } - }, - "templates": { - "loading": "Chargement des modèles...", - "not_found": "Aucun modèle trouvé dans le fichier de configuration.", - "no_local_config": "Aucune configuration locale trouvée.", - "no_global_config": "Aucune configuration globale trouvée.", - "using_local_and_global": "Utilisation des modèles des configurations locale et globale.", - "using_local_only": "Utilisation des modèles de la configuration locale uniquement, car aucune configuration globale n'a été trouvée.", - "using_global_only": "Utilisation des modèles de la configuration globale uniquement, car aucune configuration locale n'a été trouvée.", - "using_global": "Utilisation des modèles de la configuration globale.", - "using_local": "Utilisation des modèles de la configuration locale.", - "using_global_fallback": "Aucune configuration locale trouvée. Utilisation des modèles de la configuration globale.", - "header": "Modèles disponibles :" - }, - "config": { - "settings_header": "Paramètres de configuration :" - } - }, - "remove_template": { - "command": { - "description": "Supprimer un modèle de la configuration." - }, - "start": "Suppression du/des modèle(s) de la configuration...", - "success": "{count} modèle(s) ({templateName}) supprimé(s) avec succès pour le langage {language}.", - "language": { - "argument": "Le langage de programmation du modèle à supprimer." - }, - "name": { - "argument": "Le nom ou l'alias du modèle à supprimer." - }, - "option": { - "global": "Supprimer le modèle de la configuration globale au lieu de la configuration locale." - }, - "not_found_warning": "⚠️ Avertissement : Le(s) modèle(s) suivant(s) n'a/n'ont pas été trouvé(s) : {template}" - }, - "version": { - "description": "Afficher la version actuelle de la CLI." - }, - "help": { - "description": "Afficher l'aide pour une commande." + "common": { + "yes": "Oui", + "no": "Non", + "none": "Aucun" }, - "scaffolding": { - "project": { - "start": "Échafaudage du projet {language} : {project}", - "success": "✔ Projet {language} '{project}' créé avec succès !", - "fail": "❌ Échec de l'échafaudage du projet {language} : {error}" - }, - "copy": { - "start": "📂 Copie des fichiers du modèle local...", - "success": "✔ Modèle local copié avec succès !", - "fail": "❌ Échec de la copie du modèle local." + "program": { + "program": { + "description": "Une puissante boîte à outils de développement pour échafauder de nouveaux projets.", + "verbose_option": "Activer la journalisation verbeuse pour une sortie détaillée" }, - "run": { - "start": "📦 Exécution de la commande CLI officielle : {command}...", - "success": "✔ Commande CLI officielle exécutée avec succès !", - "fail": "❌ Échec de l'exécution de la commande CLI officielle." + "version": { + "description": "Afficher la version actuelle de l'interface de ligne de commande (CLI)." }, - "install": { - "start": "📦 Installation des dépendances avec {pm}...", - "success": "✔ Dépendances installées avec succès !", - "fail": "❌ Échec de l'installation des dépendances." + "help": { + "description": "Afficher l'aide pour une commande." }, - "complete": { - "success": "\n🚀 Projet créé avec succès ! 🎉", - "next_steps": "\n| Prochaines étapes :\n" + "status": { + "initializing": "Initialisation de la CLI..." } }, - "config": { - "command": { - "description": "Gérer les paramètres de DevKit" - }, - "interactive": { - "prompt_action": "Que souhaitez-vous configurer ?", - "action": { - "settings": "Paramètres", - "templates": "Modèles" - }, - "prompt_setting_key": "Choisissez un paramètre à modifier :", - "prompt_template_name": "Choisissez un modèle à mettre à jour :", - "prompt_language": "Choisissez un langage :", - "prompt_template_property": "Choisissez une propriété à mettre à jour :", - "prompt_new_value": "Entrez la nouvelle valeur pour {key} :", - "success": "Session de configuration terminée." + "commands": { + "new": { + "command": { + "description": "Créer un nouveau projet à partir d'un modèle" + }, + "project": { + "language": { + "argument": "Le langage de programmation du modèle (par ex., 'react', 'node')" + }, + "name": { + "argument": "Le nom de votre nouveau projet" + }, + "template": { + "option": { + "description": "Le nom du modèle à utiliser (par ex., 'ts', 'simple-api')" + } + } + } }, - "set": { + "list": { "command": { - "description": "Définir une ou plusieurs valeurs de configuration. \n\nClés disponibles :\n pm, packageManager - Définit le gestionnaire de paquets par défaut à utiliser pour les nouveaux projets.\n Valeurs possibles : {pmValues}\n cache, cacheStrategy - Définit le comportement global de mise en cache pour les modèles distants.\n Valeurs possibles : 'always-refresh', 'never-refresh', 'daily'\n language, lg - Définit la langue de la CLI. Valeurs possibles : 'en', 'fr'\n" - }, - "argument": { - "description": "Une liste de paires clé-valeur à définir (ex: 'pm npm language fr')" + "description": "Lister tous les modèles disponibles dans la configuration.", + "language": { + "argument": "Le langage pour filtrer les modèles" + }, + "filter": { + "option": "Filtrer les modèles par nom ou sous-chaîne." + } }, - "option": { - "global": "Mettre à jour la configuration globale au lieu de la locale.", - "bulk": "Définit plusieurs propriétés de configuration." + "options": { + "global": "Lister les modèles de la configuration globale.", + "local": "Lister les modèles de la configuration locale.", + "all": "Lister les modèles des configurations locale et globale." }, - "updating": "Mise à jour de la configuration...", - "success": "Configuration mise à jour avec succès !" + "output": { + "header": "Modèles disponibles :", + "settings_header": "Paramètres de configuration :" + } }, - "loading": "Chargement de la configuration...", - "get": { + "config": { "command": { - "description": "Obtenir une ou plusieurs valeurs de configuration." + "description": "Gérer les paramètres de DevKit" + }, + "interactive": { + "prompt_action": "Que souhaitez-vous configurer ?", + "action": { + "settings": "Paramètres", + "templates": "Modèles" + }, + "prompt_setting_key": "Choisissez un paramètre à modifier :", + "prompt_template_name": "Choisissez un modèle à mettre à jour :", + "prompt_language": "Choisissez un langage :", + "prompt_template_property": "Choisissez une propriété à mettre à jour :", + "prompt_new_value": "Entrez la nouvelle valeur pour {key} :", + "success": "Session de configuration terminée." }, - "argument": { - "description": "La clé de la valeur de configuration à obtenir (ex: 'pm', 'language')." + "set": { + "command": { + "description": "Définir une ou plusieurs valeurs de configuration. \n\nClés disponibles :\n pm, packageManager - Définit le gestionnaire de paquets par défaut à utiliser pour les nouveaux projets.\n Valeurs possibles : {pmValues}\n cache, cacheStrategy - Définit le comportement global de mise en cache pour les modèles distants.\n Valeurs possibles : 'always-refresh', 'never-refresh', 'daily'\n language, lg - Définit la langue de la CLI. Valeurs possibles : 'en', 'fr'\n" + }, + "argument": { + "description": "Une liste de paires clé-valeur à définir (par ex., 'pm npm language fr')" + }, + "option": { + "global": "Mettre à jour la configuration globale au lieu de la configuration locale.", + "bulk": "Définit plusieurs propriétés de configuration." + } }, - "option": { - "global": "Obtenir la configuration globale au lieu de la locale." + "get": { + "command": { + "description": "Obtenir une ou plusieurs valeurs de configuration." + }, + "argument": { + "description": "La clé de la valeur de configuration à obtenir (par ex., 'pm', 'language')." + }, + "option": { + "global": "Obtenir la configuration globale au lieu de la configuration locale." + } }, - "loading": "Chargement de la configuration...", - "success": "Configuration chargée avec succès !", - "not_found": "Clé de configuration '{key}' non trouvée.", - "fallback": { - "global": "Aucun fichier de configuration globale trouvé. Affichage des paramètres par défaut à la place.", - "local": "Aucun fichier de configuration locale trouvé. Affichage des paramètres par défaut à la place.", - "local_to_global": "Aucun fichier de configuration locale trouvé. Affichage des paramètres globaux à la place." + "cache": { + "command": { + "description": "Mettre à jour la stratégie de cache pour un modèle spécifique." + }, + "template": { + "argument": "Le nom du modèle à mettre à jour" + }, + "strategy": { + "argument": "La nouvelle stratégie de cache" + }, + "success": "Stratégie de cache pour le modèle '{template}' mise à jour à '{strategy}'" + }, + "update_template": { + "command": { + "description": "Mettre à jour les propriétés d'un modèle, telles que son alias, sa description ou son emplacement." + }, + "language": { + "argument": "Le langage de programmation du modèle à mettre à jour." + }, + "template": { + "argument": "Le nom ou l'alias du modèle à mettre à jour." + }, + "options": { + "new_name": "Un nouveau nom pour le modèle.", + "description": "Une nouvelle brève description pour le modèle.", + "alias": "Un nouvel alias pour le modèle.", + "location": "Un nouvel emplacement pour le modèle.", + "cache_strategy": "Une nouvelle stratégie de cache pour le modèle.", + "package_manager": "Un nouveau gestionnaire de paquets pour le modèle.", + "global": "Mettre à jour le modèle dans la configuration globale au lieu de la configuration locale." + } }, - "source": { - "local": "Utilisation de la configuration locale.", - "global": "Utilisation de la configuration globale.", - "local_and_global": "Utilisation des configurations locale et globale." + "init": { + "command": { + "description": "Initialise un fichier de configuration avec les paramètres par défaut." + }, + "option": { + "local": "Initialiser un fichier de configuration locale au lieu d'un fichier global.", + "global": "Initialiser un fichier de configuration globale au lieu d'un fichier local." + }, + "confirm_overwrite": "Le fichier de configuration existe déjà à {path}. Voulez-vous l'écraser ?", + "confirm_monorepo_overwrite": "Un fichier de configuration existe à la racine du monorepo à {path}. Voulez-vous en créer un nouveau dans le paquet actuel ?", + "monorepo_location": "Une racine de monorepo a été trouvée, mais aucun fichier de configuration n'y existe. Où voulez-vous créer le nouveau fichier de configuration ?", + "location_current": "Le créer dans le paquet actuel.", + "location_root": "Le créer à la racine du monorepo.", + "aborted": "Opération annulée. Aucune modification n'a été effectuée." + }, + "list": { + "command": { + "description": "Lister les paramètres et les modèles disponibles." + }, + "options": { + "all": "Lister les modèles des configurations locale et globale (fusionnées)." + }, + "settings_header": "Paramètres de configuration :", + "templates_header": "Modèles disponibles :" } }, - "init": { - "command": { - "description": "Initialise un fichier de configuration avec les paramètres par défaut." - }, - "start": "Initialisation de la configuration...", - "success": "Fichier de configuration créé avec succès !", - "fail": "Échec de l'initialisation de la configuration.", - "initializing": "Initialisation de la configuration...", - "option": { - "local": "Initialiser un fichier de configuration locale au lieu d'un fichier global.", - "global": "Initialiser un fichier de configuration globale au lieu d'un fichier local." + "template": { + "add": { + "description": "Ajouter un nouveau modèle à la configuration", + "options": { + "description": "Une brève description du modèle", + "alias": "Un alias court pour le modèle", + "cache": "La stratégie de cache pour le modèle", + "package_manager": "Le gestionnaire de paquets à utiliser pour le modèle" + }, + "prompts": { + "language": "Langage du modèle", + "template_name": "Entrez le nom du modèle", + "location": "Entrez l'emplacement du modèle", + "description": "Entrez la description du modèle", + "alias": "Entrez un alias court pour le modèle", + "cache_strategy": "Stratégie de cache", + "package_manager": "Gestionnaire de paquets" + } }, - "confirm_overwrite": "Le fichier de configuration existe déjà à {path}. Voulez-vous l'écraser ?", - "confirm_monorepo_overwrite": "Un fichier de configuration existe à la racine du monorepo à {path}. Voulez-vous en créer un nouveau dans le paquet actuel ?", - "found_existing": "Le fichier de configuration existe déjà à {path}. Initialisation ignorée.", - "monorepo_location": "Une racine de monorepo a été trouvée, mais aucun fichier de configuration n'y existe. Où voulez-vous créer le nouveau fichier de configuration ?", - "location_current": "Le créer dans le paquet actuel.", - "location_root": "Le créer à la racine du monorepo.", - "aborted": "Opération annulée. Aucun changement n'a été effectué." + "remove": { + "command": { + "description": "Supprimer un modèle de la configuration." + }, + "language": { + "argument": "Le langage de programmation du modèle à supprimer." + }, + "name": { + "argument": "Le nom ou l'alias du modèle à supprimer." + }, + "option": { + "global": "Supprimer le modèle de la configuration globale au lieu de la configuration locale." + } + } }, - "update": { + "info": { "command": { - "description": "Mettre à jour les propriétés d'un modèle, telles que son alias, sa description ou son emplacement." + "description": "Afficher les informations système et d'environnement pour le débogage." }, - "language": { - "argument": "Le langage de programmation du modèle à mettre à jour." + "header": { + "cli": "CLI de l'échafaudeur", + "runtime": "Environnement d'exécution", + "os_details": "Détails du système d'exploitation", + "config_files": "Fichiers de configuration" }, - "template": { - "argument": "Le nom ou l'alias du modèle à mettre à jour." + "cli": { + "version": "Version" }, - "option": { - "new_name": "Un nouveau nom pour le modèle.", - "description": "Une nouvelle brève description pour le modèle.", - "alias": "Un nouvel alias pour le modèle.", - "location": "Un nouvel emplacement pour le modèle.", - "cache_strategy": "Une nouvelle stratégie de cache pour le modèle.", - "package_manager": "Un nouveau gestionnaire de paquets pour le modèle.", - "global": "Mettre à jour le modèle dans la configuration globale au lieu de la locale." - }, - "updating": "Mise à jour du/des modèle(s) '{templateName}' dans la configuration...", - "success": "✔ Modèle '{templateName}' mis à jour avec succès !", - "success_name": "✔ Modèle '{oldName}' mis à jour vers '{newName}' avec succès !", - "single_fail": "❌ Échec de la mise à jour de '{templateName}' : {error}", - "success_summary": "{count} modèle(s) ({templateName}) du langage {language} mis à jour avec succès !" - }, - "cache": { - "command": { - "description": "Mettre à jour la stratégie de cache pour un modèle spécifique.", - "start": "Mise à jour de la stratégie de cache pour le modèle '{template}'...", - "fail": "❌ Échec de la mise à jour de la stratégie de cache pour le modèle '{template}'.", - "success": "✨ Stratégie de cache pour le modèle '{template}' mise à jour à '{strategy}'." + "runtime": { + "runtime_name": "Environnement d'exécution", + "runtime_version": "Version de l'environnement d'exécution", + "package_manager": "Gestionnaire de paquets" }, - "template": { - "argument": "Le nom du modèle à mettre à jour" + "os": { + "type_version": "Type/Version du SE", + "architecture": "Architecture", + "shell": "Shell actif", + "home_dir": "Répertoire personnel" }, - "strategy": { - "argument": "La nouvelle stratégie de cache" + "shell": { + "unknown": "Inconnu" }, - "start": "Mise à jour de la stratégie de cache pour le modèle '{template}'...", - "success": "Stratégie de cache pour le modèle '{template}' mise à jour à '{strategy}'", - "fail": "Échec de la mise à jour de la stratégie de cache pour le modèle '{template}'", - "updating": "Mise à jour du cache..." - }, - "check": { - "local": "Vérification de la configuration locale ou du monorepo...", - "global": "Configuration locale non trouvée. Vérification de la configuration globale..." - }, - "found": { - "local": "Configuration locale trouvée et utilisée à {path}.", - "global": "Aucune configuration locale trouvée. Utilisation de la configuration globale à {path}." - }, - "not": { - "found": { - "default": "Aucun fichier de configuration trouvé. Utilisation des paramètres par défaut." + "config": { + "global_path": "Chemin de la configuration globale", + "local_path": "Chemin de la configuration locale", + "found": "[TROUVÉ]", + "not_found": "[NON TROUVÉ]", + "global_expected_location": "dans votre répertoire personnel", + "local_expected_location": "dans le répertoire de travail actuel" } + } + }, + "messages": { + "success": { + "program_initialized": "CLI initialisée avec succès.", + "new_project": "✔ Projet '{projectName}' créé avec succès !", + "template_cloned_or_cache": "Modèle cloné et mis en cache avec succès.", + "template_added": "Modèle '{templateName}' ajouté avec succès !", + "template_removed": "Suppression réussie de {count} modèle(s) ({templateName}) de {language}.", + "template_updated": "✔ Modèle '{templateName}' mis à jour avec succès !", + "template_name_updated": "✔ Modèle '{oldName}' mis à jour vers '{newName}' avec succès !", + "template_summary_updated": "Mise à jour réussie de {count} modèle(s) ({templateName}) de {language} !", + "config_updated": "Configuration mise à jour avec succès !", + "config_initialized": "Fichier de configuration créé avec succès !", + "info_collected": "Informations système collectées.", + "scaffolding_complete": "\n🚀 Projet créé avec succès ! 🎉", + "next_steps": "\n| Prochaines étapes :\n" }, - "template": { - "description": "Mettre à jour un modèle directement. Utiliser avec les options spécifiques ci-dessous." + "status": { + "scaffolding_project": "Échafaudage du projet '{projectName}' avec le modèle '{template}'...", + "config_loading": "Chargement de la configuration...", + "config_updating": "Mise à jour de la configuration...", + "config_init_start": "Initialisation de la configuration...", + "template_adding": "Ajout du modèle '{templateName}' à la configuration...", + "template_removing": "Suppression du/des modèle(s) de la configuration...", + "template_updating": "Mise à jour du/des modèle(s) '{templateName}' dans la configuration...", + "cache_updating": "Mise à jour du cache...", + "cache_clone_start": "✨ Clonage du nouveau modèle depuis {url}...", + "cache_refresh_start": "🔄 Rafraîchissement du modèle mis en cache...", + "cache_use_info": "🚀 Utilisation du modèle mis en cache pour {repoName}.", + "cache_copy_start": "📂 Copie du modèle mis en cache dans le répertoire du projet...", + "info_loading": "Collecte des détails du système et de l'environnement...", + "config_source_local_and_global": "Utilisation des configurations locale et globale.", + "config_source_local": "Utilisation de la configuration locale.", + "config_source_global": "Utilisation de la configuration globale.", + "templates_using_global_fallback": "Aucune configuration locale trouvée. Utilisation des modèles de la configuration globale." + }, + "scaffolding": { + "start": "Échafaudage du projet {language} : {project}", + "copy_start": "📂 Copie des fichiers de modèle local...", + "copy_success": "✔ Modèle local copié avec succès !", + "run_start": "📦 Exécution de la commande CLI officielle : {command}...", + "run_success": "✔ Commande CLI officielle exécutée avec succès !", + "install_start": "📦 Installation des dépendances avec {pm}...", + "install_success": "✔ Dépendances installées avec succès !" + }, + "config_source": { + "local_and_global": "Utilisation des configurations locale et globale.", + "local": "Utilisation de la configuration locale.", + "global": "Utilisation de la configuration globale.", + "local_only": "Utilisation des modèles de la configuration locale uniquement, car aucune configuration globale n'a été trouvée.", + "global_only": "Utilisation des modèles de la configuration globale uniquement, car aucune configuration locale n'a été trouvée.", + "global_fallback": "Aucune configuration locale trouvée. Utilisation des modèles de la configuration globale.", + "using_local_and_global": "Utilisation des modèles des configurations locale et globale." + }, + "config_check": { + "check_local": "Vérification de la configuration locale ou du monorepo...", + "check_global": "Configuration locale non trouvée. Vérification de la configuration globale...", + "found_local": "Configuration locale trouvée et utilisée à {path}.", + "found_global": "Aucune configuration locale trouvée. Utilisation de la configuration globale à {path}.", + "found_existing": "Le fichier de configuration existe déjà à {path}. Saut de l'initialisation." } }, - "error": { - "invalid": { - "key": "Clé non valide : '{key}'. Les clés valides sont : {keys}", - "value": "Valeur non valide pour {key}. Les options valides sont : {options}", - "command": "Commande non valide dans la configuration : '{command}'", - "cache_strategy": "Stratégie de cache non valide : '{value}'. Les options valides sont : {options}", - "package_manager": "Gestionnaire de paquets non valide : '{value}'. Les options valides sont : {options}", + "errors": { + "generic": { + "unexpected": "Une erreur inattendue s'est produite", + "unknown": "Une erreur inconnue s'est produite", + "devkit_specific": "Devkit a rencontré un problème interne inattendu", + "aborted": "Opération annulée. Aucune modification n'a été effectuée." + }, + "validation": { + "invalid_key": "Clé invalide : '{key}'. Les clés valides sont : {keys}", + "invalid_value": "Valeur invalide pour {key}. Les options valides sont : {options}", + "invalid_command": "Commande invalide dans la configuration : '{command}'", + "invalid_cache_strategy": "Stratégie de cache invalide : '{value}'. Les options valides sont : {options}", + "invalid_package_manager": "Gestionnaire de paquets invalide : '{value}'. Les options valides sont : {options}", "remove_required": "Impossible de supprimer une propriété requise : '{key}'.", - "github-repo": "URL de référentiel GitHub non valide ou inaccessible : {url}", - "local-path": "Chemin local non trouvé : {path}", - "location": "Emplacement de modèle non valide. Veuillez fournir une URL GitHub ou un chemin local valide.", - "alias": { - "exists": "L'alias '{alias}' existe déjà pour un autre modèle dans ce langage. Veuillez choisir un alias différent.", - "empty": "L'alias de modèle ne peut pas être vide.", - "too-short": "L'alias de modèle doit comporter au moins 10 lettres." - }, - "description": { - "empty": "La description du modèle ne peut pas être vide.", - "too-short": "La description du modèle doit comporter au moins 6 mots." - } + "alias_exists": "L'alias '{alias}' existe déjà pour un autre modèle dans ce langage. Veuillez choisir un alias différent.", + "alias_empty": "L'alias du modèle ne peut pas être vide.", + "alias_too_short": "L'alias du modèle doit comporter au moins 10 lettres.", + "description_empty": "La description du modèle ne peut pas être vide.", + "description_too_short": "La description du modèle doit comporter au moins 6 mots.", + "github_repo": "URL du dépôt GitHub invalide ou inaccessible : {url}", + "local_path": "Chemin local non trouvé : {path}", + "location": "Emplacement du modèle invalide. Veuillez fournir une URL GitHub ou un chemin local valide.", + "language_required": "Le langage est requis.", + "template_name_required": "Le nom du modèle est requis.", + "location_required": "L'emplacement est requis.", + "description_required": "La description est requise." }, "config": { - "parse": "Échec de l'analyse de {file}. Utilisation de la configuration par défaut.", - "save": "❌ Échec de l'enregistrement du fichier de configuration : {file}", + "parse_fail": "Échec de l'analyse de {file}. Utilisation de la configuration par défaut.", + "save_fail": "❌ Échec de l'enregistrement du fichier de configuration : {file}", "exists": "Le fichier existe déjà à {path}. Utilisez 'config set' pour le mettre à jour.", - "not": { - "found": "Fichier de configuration non trouvé." - }, - "global": { - "not": { - "found": "Fichier de configuration globale non trouvé. Exécutez 'devkit config init --global' pour en créer un." - } - }, - "local": { - "not": { - "found": "Aucun fichier de configuration locale trouvé. Exécutez 'devkit config init --local' pour en créer un." - } - }, - "generic": "Erreur de configuration", - "init": { - "fail": "Échec de l'initialisation de la configuration.", - "local_and_global": "Impossible d'utiliser les drapeaux --local et --global en même temps." - }, - "read": "Échec de la lecture de la configuration à {path}.", + "not_found": "Fichier de configuration non trouvé.", + "read_fail": "Échec de la lecture de la configuration à {path}.", "no_file_found": "Aucun fichier de configuration trouvé. Exécutez 'devkit config init' pour en créer un global, ou 'devkit config init --local' pour en créer un local.", - "no_file_found_local": "Aucun fichier de configuration locale trouvé. Exécutez 'devkit config init --local' pour en créer un." - }, - "file": { - "not_found": "Impossible de trouver le fichier '{fileName}'." - }, - "package": { - "root": { - "not_found": "Impossible de trouver la racine du projet contenant package.json." - }, - "file_not_found": "{file} non trouvé à {path}", - "failed_to_update_project_name": "Échec de la mise à jour du nom du projet :" - }, - "version": { - "read_fail": "Échec de la lecture de la version de package.json." + "no_file_found_local": "Aucun fichier de configuration locale trouvé. Exécutez 'devkit config init --local' pour en créer un.", + "init_fail": "Échec de l'initialisation de la configuration.", + "init_local_and_global": "Impossible d'utiliser les deux drapeaux --local et --global en même temps.", + "global_not_found": "Fichier de configuration globale non trouvé. Exécutez 'devkit config init --global' pour en créer un.", + "local_not_found": "Aucun fichier de configuration locale trouvé. Exécutez 'devkit config init --local' pour en créer un.", + "get_key_not_found": "Clé de configuration '{key}' non trouvée." }, "template": { "not_found": "Modèle '{template}' non trouvé dans la configuration.", "language_not_found": "Modèle non trouvé pour le langage '{language}'.", - "exists": "Le modèle '{template}' existe déjà dans la configuration. Utilisez 'devkit config set' pour le mettre à jour." - }, - "alias": { - "exists": "L'alias '{alias}' existe déjà pour un autre modèle dans ce langage. Veuillez choisir un alias différent." - }, - "install": { - "message": "Erreur d'installation :" + "exists": "Le modèle '{template}' existe déjà dans la configuration. Utilisez 'devkit config set' pour le mettre à jour.", + "single_fail": "❌ Échec de la mise à jour de '{templateName}' : {error}" }, "scaffolding": { + "fail": "❌ Échec de l'échafaudage du projet : {error}", + "copy_fail": "❌ Échec de la copie du modèle local.", + "run_fail": "❌ Échec de l'exécution de la commande CLI officielle.", + "install_fail": "❌ Échec de l'installation des dépendances.", "unexpected": "Une erreur inattendue s'est produite lors de l'échafaudage.", - "language": { - "not_found": "Langage d'échafaudage non trouvé dans la configuration : '{language}'" - } + "language_not_found": "Langage d'échafaudage non trouvé dans la configuration : '{language}'" }, "command": { - "config": { - "cache": { - "fail": "Échec de la mise à jour de la stratégie de cache pour le modèle '{template}'" - } - }, - "set": { - "invalid_arguments_count": "Nombre d'arguments non valide. Veuillez fournir des paires clé-valeur.", - "description": "Définir une valeur de configuration directement en utilisant la notation par points.", - "invalid_format": "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (par ex. --set key1 value1 key2 value2)." - }, - "update": { - "no_options": "Aucune option de mise à jour n'a été fournie. Veuillez spécifier au moins une option à mettre à jour (ex: --new-name, --description, etc.)." - }, - "mutually_exclusive_options": "Vous ne pouvez pas utiliser les options `{options}` ensemble. Elles sont mutuellement exclusives." - }, - "save": { - "local": { - "config": { - "not_found": "Échec de l'enregistrement de la configuration locale. Fichier non trouvé à '{path}'." - } - } - }, - "git": { - "generic": "Erreur Git" + "set_invalid_arguments_count": "Nombre d'arguments invalide. Veuillez fournir des paires clé-valeur.", + "set_invalid_format": "Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (par ex., --set key1 value1 key2 value2).", + "update_no_options": "Aucune option de mise à jour n'a été fournie. Veuillez spécifier au moins une option à mettre à jour (par ex., --new-name, --description, etc.).", + "mutually_exclusive_options": "Vous ne pouvez pas utiliser les options `{options}` ensemble. Elles sont mutuellement exclusives.", + "missing_required_options": "Options requises manquantes. Veuillez fournir tous les champs suivants : {fields}.\nAlternativement, exécutez avec le drapeau '-i' ou '--interactive' pour une configuration guidée." + }, + "system": { + "file_not_found": "Impossible de trouver le fichier '{fileName}'.", + "package_root_not_found": "Impossible de trouver la racine du projet contenant package.json.", + "package_file_not_found": "{file} non trouvé à {path}", + "package_name_update_fail": "Échec de la mise à jour du nom du projet :", + "version_read_fail": "Échec de la lecture de la version de package.json.", + "git_generic": "Erreur Git", + "info_package_manager_not_found": "Version de {manager} non trouvée. Est-il installé ?" }, - "list": { - "global_and_all": "Impossible d'utiliser global et " - }, - "unexpected": "Une erreur inattendue s'est produite", - "unknown": "Une erreur inconnue s'est produite", - "language_config_not_found": "Langage de programmation '{language}' non trouvé dans la configuration", - "language_required": "Le langage est requis.", - "template_name_required": "Le nom du modèle est requis.", - "location_required": "L'emplacement est requis.", - "description_required": "La description est requise.", - "missing_required_options": { - "add_template": "Options requises manquantes. Veuillez fournir tous les champs suivants : {fields}.\nAlternativement, exécutez avec le drapeau '-i' ou '--interactive' pour une configuration guidée." - }, - "devkit_specific": "Problème interne inattendu rencontré par Devkit" - }, - "cache": { - "clone": { - "start": "✨ Clonage du nouveau modèle depuis {url}...", - "success": "✔ Modèle cloné avec succès !", - "fail": "❌ Échec du clonage du référentiel." - }, - "refresh": { - "start": "🔄 Rafraîchissement du modèle mis en cache...", - "success": "✔ Modèle rafraîchi avec succès !", - "fail": "❌ Échec du rafraîchissement du modèle." - }, - "use": { - "info": "🚀 Utilisation du modèle mis en cache pour {repoName}." - }, - "copy": { - "start": "📂 Copie du modèle mis en cache dans le répertoire du projet...", - "success": "✔ Fichiers copiés avec succès !", - "fail": "❌ Échec de la copie des fichiers depuis le cache." - } - }, - "warning": { - "no": { - "local": { - "config": "Aucune configuration de projet locale trouvée. Utilisation des paramètres globaux ou par défaut." - } - }, - "global": { - "config": { - "not": { - "initialized": "Configuration globale non initialisée. Exécutez 'devkit config init' pour en créer une." - } - } - }, - "no_config_found": "⚠️ Aucun fichier de configuration trouvé. Utilisation des paramètres par défaut.", - "no_command_or_option_provided": "Avertissement : Aucune commande ou option fournie. Utilisez `dk config --help` pour voir les commandes disponibles." - }, - "cli": { - "add_template": { - "description": "Ajouter un nouveau modèle à la configuration", - "options": { - "description": "Une brève description du modèle", - "alias": "Un alias court pour le modèle", - "cache": "La stratégie de cache pour le modèle", - "package_manager": "Le gestionnaire de paquets à utiliser pour le modèle" - }, - "adding": "Ajout du modèle '{templateName}' à la configuration...", - "success": "Modèle '{templateName}' ajouté avec succès !", - "prompts": { - "language": "Entrez le langage de programmation pour le modèle", - "template_name": "Entrez le nom du modèle", - "location": "Entrez l'emplacement du modèle", - "description": "Entrez la description du modèle", - "alias": "Entrez un alias court pour le modèle", - "cache_strategy": "Sélectionnez une stratégie de cache pour le modèle", - "package_manager": "Sélectionnez un gestionnaire de paquets pour le modèle" - } + "cache": { + "clone_fail": "❌ Échec du clonage du dépôt.", + "refresh_fail": "❌ Échec du rafraîchissement du modèle.", + "copy_fail": "❌ Échec de la copie des fichiers depuis le cache." } }, - "common": { - "yes": "Oui", - "no": "Non", - "none": "Aucun" - }, - "info": { - "command": { - "description": "Afficher les informations système et d'environnement pour le débogage." - }, - "loading": "Collecte des détails du système et de l'environnement...", - "success_message": "Informations système collectées.", - "header": { - "cli": "CLI de DevKit", - "runtime": "Environnement d'exécution", - "os_details": "Détails du système d'exploitation", - "config_files": "Fichiers de configuration" - }, - "cli": { - "version": "Version" - }, - "runtime": { - "runtime_name": "Nom du moteur d'exécution", - "runtime_version": "Version de Node.js", - "package_manager": "Gestionnaire de paquets" - }, - "os": { - "type_version": "Type/Version du SE", - "architecture": "Architecture", - "shell": "Shell actif", - "home_dir": "Répertoire personnel" - }, - "shell": { - "unknown": "Inconnu" - }, - "config": { - "global_path": "Chemin de configuration globale", - "local_path": "Chemin de configuration locale", - "found": "[TROUVÉ]", - "not_found": "[NON TROUVÉ]", - "global_expected_location": "dans votre répertoire personnel", - "local_expected_location": "dans le répertoire de travail actuel" - }, - "error": { - "package_manager_not_found": "Version de {manager} introuvable. Est-il installé ?" - } + "warnings": { + "not_found": "⚠️ Aucun fichier de configuration trouvé. Utilisation des paramètres par défaut.", + "no_local_config": "Aucune configuration de projet locale trouvée. Utilisation des paramètres globaux ou par défaut.", + "global_not_initialized": "Configuration globale non initialisée. Exécutez 'devkit config init' pour en créer une.", + "template_not_found": "Aucun modèle trouvé dans le fichier de configuration.", + "templates_not_found": "Les modèles suivants n'ont pas été trouvés : {templates}.", + "no_command_provided": "Avertissement : Aucune commande ou option fournie. Utilisez `dk config --help` pour voir les commandes disponibles.", + "no_config_found": "⚠️ Aucun fichier de configuration trouvé. Utilisation de la configuration par défaut." } } diff --git a/packages/devkit/src/commands/config/add.ts b/packages/devkit/src/commands/config/add.ts index f5a531e..da880b0 100644 --- a/packages/devkit/src/commands/config/add.ts +++ b/packages/devkit/src/commands/config/add.ts @@ -12,26 +12,30 @@ export function setupAddCommand(configCommand: Command): void { configCommand .command("add ") .alias("a") - .description(t("cli.add_template.description")) + .description(t("commands.template.add.description")) .option( "-d, --description ", - t("cli.add_template.options.description"), + t("commands.template.add.options.description"), "", ) .option( "-o, --location ", - t("new.project.template.option.description"), + t("commands.template.add.prompts.location"), + "", + ) + .option( + "-a, --alias ", + t("commands.template.add.options.alias"), "", ) - .option("-a, --alias ", t("cli.add_template.options.alias"), "") .option( "-c, --cache-strategy ", - t("cli.add_template.options.cache"), + t("commands.template.add.options.cache"), "", ) .option( "-p, --package-manager ", - t("cli.add_template.options.package_manager"), + t("commands.template.add.options.package_manager"), "", ) .action( @@ -48,14 +52,16 @@ export function setupAddCommand(configCommand: Command): void { const spinner: TSpinner = logger .spinner( - logger.colors.cyan(t("cli.add_template.adding", { templateName })), + logger.colors.cyan( + t("messages.status.template_adding", { templateName }), + ), ) .start(); try { if (!description || !location) { throw new DevkitError( - t("error.missing_required_options.add_template", { + t("errors.command.missing_required_options", { fields: "--description, --location", }), ); diff --git a/packages/devkit/src/commands/config/index.ts b/packages/devkit/src/commands/config/index.ts index 674636f..e4cfedc 100644 --- a/packages/devkit/src/commands/config/index.ts +++ b/packages/devkit/src/commands/config/index.ts @@ -28,7 +28,7 @@ async function handleConfigAction( if (bulkSetValues && bulkSetValues.length > 0) { if (bulkSetValues.length % 2 !== 0) { spinner.fail( - logger.colors.redBright(t("error.command.set.invalid_format")), + logger.colors.redBright(t("errors.command.set_invalid_format")), ); return; } @@ -37,7 +37,7 @@ async function handleConfigAction( const bulkValue = bulkSetValues[i + 1]; await handleNonInteractiveSettingsUpdate(bulkKey, bulkValue, !!isGlobal); } - spinner.succeed(logger.colors.green(t("config.set.success"))); + spinner.succeed(logger.colors.green(t("messages.success.config_updated"))); return; } @@ -47,27 +47,31 @@ async function handleConfigAction( if (configValue !== undefined) { logger.log(logger.colors.yellowBold(key) + ": " + configValue); } else { - logger.log(logger.colors.redBright(t("config.get.not_found", { key }))); + logger.log( + logger.colors.redBright( + t("errors.config.get_key_not_found", { key }), + ), + ); } }); - spinner.succeed(logger.colors.green(t("config.get.success"))); + spinner.succeed(logger.colors.green(t("messages.success.config_updated"))); return; } - spinner.warn(t("warning.no_command_or_option_provided")); + spinner.warn(t("warnings.no_command_provided")); } export function setupConfigCommand(program: Command): void { const configCommand = program .command("config [keys...]") .alias("conf") - .description(t("config.command.description")) - .option("-g, --global", t("config.update.option.global"), false) - .option("-s, --set ", t("config.set.option.bulk"), false) + .description(t("commands.config.command.description")) + .option("-g, --global", t("commands.config.set.option.global"), false) + .option("-s, --set ", t("commands.config.set.option.bulk"), false) .action(async (keys: string[], cmdOptions: ConfigOptions) => { const spinner: TSpinner = logger .spinner() - .start(logger.colors.cyan(t("config.get.loading"))); + .start(logger.colors.cyan(t("messages.status.config_loading"))); try { await handleConfigAction(keys, cmdOptions, spinner); } catch (error: unknown) { diff --git a/packages/devkit/src/commands/config/list.ts b/packages/devkit/src/commands/config/list.ts index 9fe45a7..4d74c27 100644 --- a/packages/devkit/src/commands/config/list.ts +++ b/packages/devkit/src/commands/config/list.ts @@ -17,51 +17,53 @@ const getStartMessageForConfig = ( ): Parameters[0] => { if (showAll) { if (source === "merged") { - return "config.get.source.local_and_global"; + return "messages.status.config_source_local_and_global"; } if (source === "global") { - return "config.get.source.global"; + return "messages.status.config_source_global"; } if (source === "local") { - return "config.get.source.local"; + return "messages.status.config_source_local"; } - return "warning.no_config_found"; + return "warnings.no_config_found"; } if (isGlobal) { if (source !== "global") { - return "error.config.global.not.found"; + return "errors.config.global_not_found"; } - return "config.get.source.global"; + return "messages.status.config_source_global"; } if (source === "local") { - return "config.get.source.local"; + return "messages.status.config_source_local"; } if (source === "global") { - return "list.templates.using_global_fallback"; + return "messages.status.templates_using_global_fallback"; } - return "warning.no_config_found"; + return "warnings.no_config_found"; }; export function setupListCommand(configCommand: Command): void { configCommand .command("list") .alias("ls") - .description(t("list.command.description")) - .option("-a, --all", t("list.command.all.option")) + .description(t("commands.config.list.command.description")) + .option("-a, --all", t("commands.config.list.options.all")) .action(async (cmdOptions: ListCommandOptions, childCommand: Command) => { const { all: showAll } = cmdOptions; const parentOpts = childCommand?.parent?.opts(); const isGlobal = !!parentOpts?.global; - const spinner: TSpinner = logger.spinner(t("config.loading")).start(); + const spinner: TSpinner = logger + .spinner(t("messages.status.config_loading")) + .start(); try { if (isGlobal && showAll) { throw new DevkitError( - t("error.command.mutually_exclusive_options", { + t("errors.command.mutually_exclusive_options", { options: "global, all", }), ); @@ -80,18 +82,22 @@ export function setupListCommand(configCommand: Command): void { source, ); - if (startMessageKey.startsWith("error.config")) { + if (startMessageKey.startsWith("errors.config")) { throw new DevkitError(t(startMessageKey)); } spinner.info(t(startMessageKey)).start(); - logger.log(logger.colors.bold("\n" + t("list.config.settings_header"))); + logger.log( + logger.colors.bold("\n" + t("commands.config.list.settings_header")), + ); printSettings(config?.settings || {}); - logger.log(logger.colors.bold("\n" + t("list.templates.header"))); + logger.log( + logger.colors.bold("\n" + t("commands.config.list.templates_header")), + ); if (Object.keys(config?.templates || {}).length === 0) { - logger.log(logger.colors.yellow(t("list.templates.not_found"))); + logger.log(logger.colors.yellow(t("warnings.template_not_found"))); } else { Object.entries(config?.templates || {}).forEach( ([lang, langTemplates]) => { diff --git a/packages/devkit/src/commands/config/logic.ts b/packages/devkit/src/commands/config/logic.ts index 34e70c4..6ded6c0 100644 --- a/packages/devkit/src/commands/config/logic.ts +++ b/packages/devkit/src/commands/config/logic.ts @@ -19,6 +19,7 @@ import { import { validatePackageManager, validateCacheStrategy, + validateProgrammingLanguage, } from "#utils/validations/config.js"; async function saveConfig( @@ -42,7 +43,7 @@ export async function handleNonInteractiveSettingsUpdate( }); if (source === "default" && !isGlobal) { - throw new DevkitError(t("error.config.local.not.found")); + throw new DevkitError(t("errors.config.local_not_found")); } const canonicalKey = ( @@ -74,12 +75,16 @@ export async function handleNonInteractiveTemplateUpdate( }); if (source === "default" && !isGlobal) { - throw new DevkitError(t("error.config.local.not.found")); + throw new DevkitError(t("errors.config.local_not_found")); } + validateProgrammingLanguage(language); + const languageTemplates = config.templates[language]; if (!languageTemplates) { - throw new DevkitError(t("error.language_config_not_found", { language })); + throw new DevkitError( + t("errors.template.language_not_found", { language }), + ); } const templateKey = Object.keys(languageTemplates.templates).find( @@ -90,7 +95,7 @@ export async function handleNonInteractiveTemplateUpdate( if (!templateKey) { throw new DevkitError( - t("error.template.not_found", { template: templateName }), + t("errors.template.not_found", { template: templateName }), ); } @@ -141,7 +146,7 @@ export async function handleNonInteractiveTemplateUpdate( if (cmdOptions.newName && cmdOptions.newName !== templateKey) { if (languageTemplates.templates[cmdOptions.newName]) { throw new DevkitError( - t("error.template.exists", { template: cmdOptions.newName }), + t("errors.template.exists", { template: cmdOptions.newName }), ); } languageTemplates.templates[cmdOptions.newName] = finalTemplate; diff --git a/packages/devkit/src/commands/config/prompts.ts b/packages/devkit/src/commands/config/prompts.ts index 440b6f1..ac8a15f 100644 --- a/packages/devkit/src/commands/config/prompts.ts +++ b/packages/devkit/src/commands/config/prompts.ts @@ -32,7 +32,7 @@ async function handleInteractiveSettings( isGlobal: boolean, ): Promise { const settingKey = await select({ - message: t("config.interactive.prompt_setting_key"), + message: t("commands.config.interactive.prompt_setting_key"), choices: SETTINGS_CHOICES, }); @@ -49,7 +49,7 @@ async function handleInteractiveSettings( break; default: newValue = await input({ - message: t("config.interactive.prompt_new_value", { + message: t("commands.config.interactive.prompt_new_value", { key: settingKey, }), }); @@ -57,7 +57,7 @@ async function handleInteractiveSettings( } await handleNonInteractiveSettingsUpdate(settingKey, newValue!, isGlobal); - logger.log(t("config.set.success")); + logger.log(t("messages.success.config_updated")); } async function handleInteractiveTemplates( @@ -68,12 +68,12 @@ async function handleInteractiveTemplates( const templates = Object.keys(config.templates[language]?.templates || {}); const templateName = await select({ - message: t("config.interactive.prompt_template_name"), + message: t("commands.config.interactive.prompt_template_name"), choices: templates.map((key) => ({ name: key, value: key })), }); const property = await select({ - message: t("config.interactive.prompt_template_property"), + message: t("commands.config.interactive.prompt_template_property"), choices: [ { name: "description", value: "description" }, { name: "location", value: "location" }, @@ -93,7 +93,7 @@ async function handleInteractiveTemplates( break; default: newValue = await input({ - message: t("config.interactive.prompt_new_value", { + message: t("commands.config.interactive.prompt_new_value", { key: property, }), }); @@ -107,7 +107,7 @@ async function handleInteractiveTemplates( updates, isGlobal, ); - logger.log(t("config.update.success", { templateName })); + logger.log(t("messages.success.template_updated", { templateName })); } export async function handleInteractiveConfig( @@ -115,16 +115,24 @@ export async function handleInteractiveConfig( isGlobal: boolean, ): Promise { const action = await select({ - message: t("config.interactive.prompt_action"), + message: t("commands.config.interactive.prompt_action"), choices: [ - { name: t("config.interactive.action.settings"), value: "settings" }, - { name: t("config.interactive.action.templates"), value: "templates" }, + { + name: t("commands.config.interactive.action.settings"), + value: "settings", + }, + { + name: t("commands.config.interactive.action.templates"), + value: "templates", + }, ], }); if (action === "settings") { await handleInteractiveSettings(config, isGlobal); + logger.log(t("commands.config.interactive.success")); } else if (action === "templates") { await handleInteractiveTemplates(config, isGlobal); + logger.log(t("commands.config.interactive.success")); } } diff --git a/packages/devkit/src/commands/config/remove.ts b/packages/devkit/src/commands/config/remove.ts index 2d7c560..1b09172 100644 --- a/packages/devkit/src/commands/config/remove.ts +++ b/packages/devkit/src/commands/config/remove.ts @@ -24,7 +24,7 @@ export function setupRemoveCommand(configCommand: Command): void { configCommand .command("remove ") .alias("rm") - .description(t("remove_template.command.description")) + .description(t("commands.template.remove.command.description")) .action( async ( language: string, @@ -37,7 +37,7 @@ export function setupRemoveCommand(configCommand: Command): void { const spinner: TSpinner = logger .spinner() - .start(logger.colors.cyan(t("remove_template.start"))); + .start(logger.colors.cyan(t("messages.status.template_removing"))); try { validateProgrammingLanguage(language); @@ -47,9 +47,9 @@ export function setupRemoveCommand(configCommand: Command): void { }); const languageTemplates = targetConfig?.templates?.[language]; - if (!languageTemplates.templates) { + if (!languageTemplates?.templates) { throw new DevkitError( - t("error.template.language_not_found", { language: language }), + t("errors.template.language_not_found", { language: language }), ); } @@ -80,7 +80,7 @@ export function setupRemoveCommand(configCommand: Command): void { if (templatesToRemove.length === 0) { throw new DevkitError( - t("error.template.not_found", { template: notFound.join(", ") }), + t("errors.template.not_found", { template: notFound.join(", ") }), ); } @@ -95,7 +95,7 @@ export function setupRemoveCommand(configCommand: Command): void { await saveConfig(targetConfig, !!isGlobal); spinner.succeed( - t("remove_template.success", { + t("messages.success.template_removed", { count: templatesToRemove.length.toString(), templateName: templatesToRemove.join(", "), language, @@ -103,10 +103,10 @@ export function setupRemoveCommand(configCommand: Command): void { ); if (notFound.length > 0) { - logger.log( + logger.warning( logger.colors.yellow( - t("remove_template.not_found_warning", { - template: notFound.join(", "), + t("warnings.templates_not_found", { + templates: notFound.join(", "), }), ), ); diff --git a/packages/devkit/src/commands/config/update.ts b/packages/devkit/src/commands/config/update.ts index 36a6288..88579b1 100644 --- a/packages/devkit/src/commands/config/update.ts +++ b/packages/devkit/src/commands/config/update.ts @@ -10,20 +10,36 @@ export function setupUpdateCommand(configCommand: Command): void { configCommand .command("update ") .alias("up") - .description(t("config.update.command.description")) - .option("-n, --new-name ", t("config.update.option.new_name")) - .option("-d, --description ", t("config.update.option.description")) - .option("-a, --alias ", t("config.update.option.alias")) - .option("-l, --location ", t("config.update.option.location")) + .description(t("commands.config.update_template.command.description")) + .option( + "-n, --new-name ", + t("commands.config.update_template.options.new_name"), + ) + .option( + "-d, --description ", + t("commands.config.update_template.options.description"), + ) + .option( + "-a, --alias ", + t("commands.config.update_template.options.alias"), + ) + .option( + "-l, --location ", + t("commands.config.update_template.options.location"), + ) .option( "--cache-strategy ", - t("config.update.option.cache_strategy"), + t("commands.config.update_template.options.cache_strategy"), ) .option( "--package-manager ", - t("config.update.option.package_manager"), + t("commands.config.update_template.options.package_manager"), + ) + .option( + "-g, --global", + t("commands.config.update_template.options.global"), + false, ) - .option("-g, --global", t("config.update.option.global"), false) .action( async ( language: string, @@ -33,7 +49,7 @@ export function setupUpdateCommand(configCommand: Command): void { ) => { const spinner: TSpinner = logger.spinner().start( logger.colors.cyan( - t("config.update.updating", { + t("messages.status.template_updating", { templateName: templateNames.join(", "), }), ), @@ -48,7 +64,9 @@ export function setupUpdateCommand(configCommand: Command): void { try { if (templateNames.length === 0) { - throw new DevkitError(t("error.template_name_required")); + throw new DevkitError( + t("errors.validation.template_name_required"), + ); } for (const templateName of templateNames) { @@ -66,13 +84,13 @@ export function setupUpdateCommand(configCommand: Command): void { if (error instanceof DevkitError) { logger.log( logger.colors.yellow( - `\n${t("config.update.single_fail", { templateName, error: error.message })}`, + `\n${t("errors.template.single_fail", { templateName, error: error.message })}`, ), ); } else { logger.log( logger.colors.yellow( - `\n${t("config.update.single_fail", { templateName, error: "unknown error" })}`, + `\n${t("errors.template.single_fail", { templateName, error: "unknown error" })}`, ), ); } @@ -84,7 +102,7 @@ export function setupUpdateCommand(configCommand: Command): void { if (successfullyUpdatedCount > 0) { logger.log( logger.colors.green( - `\n✔ ${t("config.update.success_summary", { + `\n✔ ${t("messages.success.template_summary_updated", { count: successfullyUpdatedCount.toString(), templateName: templateNames.join(", "), language, diff --git a/packages/devkit/src/commands/config/validate-and-save.ts b/packages/devkit/src/commands/config/validate-and-save.ts index a85230b..1aac237 100644 --- a/packages/devkit/src/commands/config/validate-and-save.ts +++ b/packages/devkit/src/commands/config/validate-and-save.ts @@ -51,7 +51,7 @@ export async function validateAndSaveTemplate( if (languageConfig.templates[templateName]) { throw new DevkitError( - t("error.template.exists", { template: templateName }), + t("errors.template.exists", { template: templateName }), ); } @@ -61,7 +61,9 @@ export async function validateAndSaveTemplate( (t) => t.alias === alias, ); if (aliasExists) { - throw new DevkitError(t("error.alias.exists", { alias: alias })); + throw new DevkitError( + t("errors.validation.alias_exists", { alias: alias }), + ); } } @@ -78,6 +80,6 @@ export async function validateAndSaveTemplate( await saveCliConfig(targetConfig, isGlobal); addSpinner.succeed( - logger.colors.green(t("cli.add_template.success", { templateName })), + logger.colors.green(t("messages.success.template_added", { templateName })), ); } diff --git a/packages/devkit/src/commands/index.ts b/packages/devkit/src/commands/index.ts index 88873a8..af48d99 100644 --- a/packages/devkit/src/commands/index.ts +++ b/packages/devkit/src/commands/index.ts @@ -43,25 +43,27 @@ export async function setupAndParse() { isVerbose && spinner.succeed( - logger.colors.green(logger.colors.bold(t("program.initialized"))), + logger.colors.green( + logger.colors.bold(t("messages.success.program_initialized")), + ), ); if (source === "default") { logger.warning( - `\n${logger.colors.yellowBold(logger.colors.italic(t("warning.no_config_found")))}\n`, + `\n${logger.colors.yellowBold(logger.colors.italic(t("warnings.not_found")))}\n`, ); } program .name("devkit") .alias("dk") - .description(t("program.description")) + .description(t("program.program.description")) .version( await getProjectVersion(), "-V, --version", - t("version.description"), + t("program.version.description"), ) - .helpOption("-h, --help", t("help.description")); + .helpOption("-h, --help", t("program.help.description")); setupInitCommand({ program, config }); setupNewCommand({ program, config }); diff --git a/packages/devkit/src/commands/info.ts b/packages/devkit/src/commands/info.ts index 9e66cc4..d78f0c7 100644 --- a/packages/devkit/src/commands/info.ts +++ b/packages/devkit/src/commands/info.ts @@ -10,31 +10,34 @@ const printInfo = (info: SystemInfo): void => { items: [string, string | { path: string; exists: boolean }][]; }[] = [ { - titleKey: "info.header.cli", - items: [[t("info.cli.version"), info.cliVersion]], + titleKey: "commands.info.header.cli", + items: [[t("commands.info.cli.version"), info.cliVersion]], }, { - titleKey: "info.header.runtime", + titleKey: "commands.info.header.runtime", items: [ - [t("info.runtime.runtime_name"), info.runtimeName], - [t("info.runtime.runtime_version"), info.runtimeVersion], - [t("info.runtime.package_manager"), info.packageManagerVersion], + [t("commands.info.runtime.runtime_name"), info.runtimeName], + [t("commands.info.runtime.runtime_version"), info.runtimeVersion], + [ + t("commands.info.runtime.package_manager"), + info.packageManagerVersion, + ], ], }, { - titleKey: "info.header.os_details", + titleKey: "commands.info.header.os_details", items: [ - [t("info.os.type_version"), info.os], - [t("info.os.architecture"), info.arch], - [t("info.os.shell"), info.shell], - [t("info.os.home_dir"), info.homeDir], + [t("commands.info.os.type_version"), info.os], + [t("commands.info.os.architecture"), info.arch], + [t("commands.info.os.shell"), info.shell], + [t("commands.info.os.home_dir"), info.homeDir], ], }, { - titleKey: "info.header.config_files", + titleKey: "commands.info.header.config_files", items: [ - [t("info.config.global_path"), info.globalConfig], - [t("info.config.local_path"), info.localConfig], + [t("commands.info.config.global_path"), info.globalConfig], + [t("commands.info.config.local_path"), info.localConfig], ], }, ]; @@ -54,8 +57,8 @@ const printInfo = (info: SystemInfo): void => { displayValue = value; } else { const status = value.exists - ? logger.colors.green(t("info.config.found")) - : logger.colors.red(t("info.config.not_found")); + ? logger.colors.green(t("commands.info.config.found")) + : logger.colors.red(t("commands.info.config.not_found")); displayValue = `${value.path} ${status}`; } @@ -72,14 +75,16 @@ export function setupInfoCommand(options: SetupCommandOptions): void { program .command("info") .alias("in") - .description(t("info.command.description")) + .description(t("commands.info.command.description")) .action(async () => { - const spinner: TSpinner = logger.spinner(t("info.loading")).start(); + const spinner: TSpinner = logger + .spinner(t("messages.status.info_loading")) + .start(); try { const info = await collectSystemInfo(cliVersion); spinner.stop(); - spinner.succeed(t("info.success_message")); + spinner.succeed(t("messages.success.info_collected")); printInfo(info); } catch (error: unknown) { diff --git a/packages/devkit/src/commands/init.ts b/packages/devkit/src/commands/init.ts index c4c1f1d..39dfc5b 100644 --- a/packages/devkit/src/commands/init.ts +++ b/packages/devkit/src/commands/init.ts @@ -21,7 +21,7 @@ import { getPackageManager } from "#utils/package-manager/index.js"; async function promptForStandardOverwrite(filePath: string): Promise { const response = await select({ message: logger.colors.yellow( - t("config.init.confirm_overwrite", { path: filePath }), + t("commands.config.init.confirm_overwrite", { path: filePath }), ), choices: [ { name: t("common.yes"), value: true }, @@ -58,12 +58,16 @@ async function handleGlobalInit(spinner: TSpinner): Promise { if (shouldOverwrite) { const configToSave = await getUpdatedConfig(); spinner.start( - logger.colors.cyan(t("config.init.initializing", { path: finalPath })), + logger.colors.cyan( + t("messages.status.config_init_start", { path: finalPath }), + ), ); await saveConfig(configToSave, finalPath); - spinner.succeed(logger.colors.green(t("config.init.success"))); + spinner.succeed( + logger.colors.green(t("messages.success.config_initialized")), + ); } else { - spinner.info(logger.colors.yellow(t("config.init.aborted"))); + spinner.info(logger.colors.yellow(t("commands.config.init.aborted"))); } } @@ -93,12 +97,16 @@ async function handleLocalInit(spinner: TSpinner): Promise { if (shouldOverwrite && finalPath) { const configToSave = await getUpdatedConfig(); spinner.start( - logger.colors.cyan(t("config.init.initializing", { path: finalPath })), + logger.colors.cyan( + t("messages.status.config_init_start", { path: finalPath }), + ), ); await saveConfig(configToSave, finalPath); - spinner.succeed(logger.colors.green(t("config.init.success"))); + spinner.succeed( + logger.colors.green(t("messages.success.config_initialized")), + ); } else { - spinner.info(logger.colors.yellow(t("config.init.aborted"))); + spinner.info(logger.colors.yellow(t("commands.config.init.aborted"))); } } @@ -107,9 +115,9 @@ export function setupInitCommand(options: SetupCommandOptions): void { program .command("init") .alias("i") - .description(t("config.init.command.description")) - .option("-l, --local", t("config.init.option.local"), false) - .option("-g, --global", t("config.init.option.global"), false) + .description(t("commands.config.init.command.description")) + .option("-l, --local", t("commands.config.init.option.local"), false) + .option("-g, --global", t("commands.config.init.option.global"), false) .action(async (cmdOptions: { local: boolean; global: boolean }) => { const isLocal: boolean = cmdOptions.local; const isGlobal: boolean = cmdOptions.global; @@ -117,7 +125,7 @@ export function setupInitCommand(options: SetupCommandOptions): void { try { if (isLocal && isGlobal) { - throw new ConfigError(t("error.config.init.local_and_global")); + throw new ConfigError(t("errors.config.init_local_and_global")); } if (isGlobal) { diff --git a/packages/devkit/src/commands/list.ts b/packages/devkit/src/commands/list.ts index 13120d4..767ef1d 100644 --- a/packages/devkit/src/commands/list.ts +++ b/packages/devkit/src/commands/list.ts @@ -18,35 +18,38 @@ const getStartMessage = ( showAll: boolean, source: string, ): Parameters[0] => { + const TEMPLATE_NOT_FOUND_KEY = "errors.template.not_found"; + const GLOBAL_NOT_FOUND_KEY = "errors.config.global_not_found"; + if (showAll) { if (source === "merged") { - return "list.templates.using_local_and_global"; + return "messages.config_source.using_local_and_global"; } if (source === "global") { - return "list.templates.using_global_only"; + return "messages.config_source.global_only"; } if (source === "local") { - return "list.templates.using_local_only"; + return "messages.config_source.local_only"; } - return "list.templates.not_found"; + return TEMPLATE_NOT_FOUND_KEY; } if (isGlobal) { if (source !== "global") { - return "list.templates.no_global_config"; + return GLOBAL_NOT_FOUND_KEY; } - return "list.templates.using_global"; + return "messages.config_source.global"; } if (source === "local") { - return "list.templates.using_local"; + return "messages.config_source.local"; } if (source === "global") { - return "list.templates.using_global_fallback"; + return "messages.config_source.global_fallback"; } - return "list.templates.not_found"; + return TEMPLATE_NOT_FOUND_KEY; }; export function setupListCommand(options: SetupCommandOptions): void { @@ -55,22 +58,22 @@ export function setupListCommand(options: SetupCommandOptions): void { program .command("list") .alias("ls") - .description(t("list.command.description")) - .argument("[language]", t("list.command.language.argument"), "") - .option("-g, --global", t("list.command.global.option")) - .option("-a, --all", t("list.command.all.option")) - .option("-f, --filter ", t("list.command.filter.option")) + .description(t("commands.list.command.description")) + .argument("[language]", t("commands.list.command.language.argument"), "") + .option("-g, --global", t("commands.list.options.global")) + .option("-a, --all", t("commands.list.options.all")) + .option("-f, --filter ", t("commands.list.command.filter.option")) .action(async (language, cmdOptions: ListCommandOptions) => { const { global: isGlobal, all: showAll, filter } = cmdOptions; const spinner: TSpinner = logger - .spinner(t("list.templates.loading")) + .spinner(t("messages.status.config_loading")) .start(); try { if (isGlobal && showAll) { throw new DevkitError( - t("error.command.mutually_exclusive_options", { + t("errors.command.mutually_exclusive_options", { options: "global, all", }), ); @@ -87,7 +90,7 @@ export function setupListCommand(options: SetupCommandOptions): void { const startMessageKey = getStartMessage(!!isGlobal, !!showAll, source); - if (startMessageKey.startsWith("list.templates.no_")) { + if (startMessageKey === "errors.config.global_not_found") { spinner.succeed(logger.colors.yellow(t(startMessageKey))); return; } @@ -97,7 +100,7 @@ export function setupListCommand(options: SetupCommandOptions): void { if (Object.keys(config?.templates || {}).length === 0) { spinner.succeed( logger.colors.yellow( - t("list.templates.not_found", { + t("warnings.template_not_found", { template: "", }), ), @@ -105,7 +108,7 @@ export function setupListCommand(options: SetupCommandOptions): void { return; } - logger.log(logger.colors.bold("\n" + t("list.templates.header"))); + logger.log(logger.colors.bold("\n" + t("commands.list.output.header"))); Object.entries(config?.templates || {}).forEach( ([lang, langTemplates]) => { diff --git a/packages/devkit/src/commands/new.ts b/packages/devkit/src/commands/new.ts index bf79da2..bd17c38 100644 --- a/packages/devkit/src/commands/new.ts +++ b/packages/devkit/src/commands/new.ts @@ -10,7 +10,9 @@ const getScaffolder = async (language: string) => { const { scaffoldProject } = await import("#scaffolding/javascript.js"); return scaffoldProject; } - throw new DevkitError(t("error.language_config_not_found", { language })); + throw new DevkitError( + t("errors.scaffolding.language_not_found", { language }), + ); }; export function setupNewCommand(options: SetupCommandOptions) { @@ -18,12 +20,12 @@ export function setupNewCommand(options: SetupCommandOptions) { program .command("new") .alias("nw") - .description(t("new.command.description")) - .argument("", t("new.project.language.argument")) - .argument("", t("new.project.name.argument")) + .description(t("commands.new.command.description")) + .argument("", t("commands.new.project.language.argument")) + .argument("", t("commands.new.project.name.argument")) .requiredOption( "-t, --template ", - t("new.project.template.option.description"), + t("commands.new.project.template.option.description"), ) .action(async (language, projectName, cmdOptions) => { const { template } = cmdOptions; @@ -31,7 +33,7 @@ export function setupNewCommand(options: SetupCommandOptions) { const scaffoldSpinner: TSpinner = logger .spinner( logger.colors.cyan( - t("new.project.scaffolding", { + t("messages.status.scaffolding_project", { projectName, template: template, }), @@ -45,7 +47,7 @@ export function setupNewCommand(options: SetupCommandOptions) { const languageTemplates = config.templates[language]; if (!languageTemplates) { throw new DevkitError( - t("error.language_config_not_found", { language }), + t("errors.scaffolding.language_not_found", { language }), ); } @@ -56,7 +58,7 @@ export function setupNewCommand(options: SetupCommandOptions) { ); if (!templateConfig) { - throw new DevkitError(t("error.template.not_found", { template })); + throw new DevkitError(t("errors.template.not_found", { template })); } const scaffoldAppropriateProject = await getScaffolder(language); @@ -75,7 +77,9 @@ export function setupNewCommand(options: SetupCommandOptions) { }); scaffoldSpinner.succeed( - logger.colors.green(t("new.project.success", { projectName })), + logger.colors.green( + t("messages.success.new_project", { projectName }), + ), ); } catch (error) { handleErrorAndExit(error, scaffoldSpinner); diff --git a/packages/devkit/src/core/cache/git.ts b/packages/devkit/src/core/cache/git.ts index 6a70732..6557461 100644 --- a/packages/devkit/src/core/cache/git.ts +++ b/packages/devkit/src/core/cache/git.ts @@ -20,16 +20,18 @@ export async function cloneRepo(url: string, repoPath: string) { cwd: repoPath, stdio: "ignore", }); - } catch (error) { - throw new GitError(t("cache.clone.fail"), url, { cause: error }); + } catch (error: unknown) { + throw new GitError(t("errors.cache.clone_fail"), url, { cause: error }); } } export async function pullRepo(repoPath: string) { try { await execute("git", ["pull"], { cwd: repoPath, stdio: "ignore" }); - } catch (error) { - throw new GitError(t("cache.refresh.fail"), undefined, { cause: error }); + } catch (error: unknown) { + throw new GitError(t("errors.cache.refresh_fail"), undefined, { + cause: error, + }); } } diff --git a/packages/devkit/src/core/cache/index.ts b/packages/devkit/src/core/cache/index.ts index 250d993..44264fa 100644 --- a/packages/devkit/src/core/cache/index.ts +++ b/packages/devkit/src/core/cache/index.ts @@ -40,35 +40,47 @@ export async function getTemplateFromCache( if (!repoExists) { spinner.text = logger.colors.cyan( - logger.colors.italic(t("cache.clone.start", { url })), + logger.colors.italic(t("messages.status.cache_clone_start", { url })), ); await cloneRepo(url, repoPath); spinner.succeed( - logger.colors.green(logger.colors.bold(t("cache.clone.success"))), + logger.colors.green( + logger.colors.bold(t("messages.success.template_added")), + ), ); } else { const fresh = await isRepoFresh(repoPath, strategy); if (!fresh) { - spinner.text = logger.colors.cyan(t("cache.refresh.start")); + spinner.text = logger.colors.cyan( + t("messages.status.cache_refresh_start"), + ); await pullRepo(repoPath); - spinner.succeed(logger.colors.green(t("cache.refresh.success"))); + spinner.succeed( + logger.colors.green(t("messages.success.template_updated")), + ); } else { - spinner.info(logger.colors.yellow(t("cache.use.info", { repoName }))); + spinner.info( + logger.colors.yellow( + t("messages.status.cache_use_info", { repoName }), + ), + ); } } - spinner.text = logger.colors.cyan(t("cache.copy.start")); + spinner.text = logger.colors.cyan(t("messages.status.cache_copy_start")); await copyJavascriptTemplate(repoPath, destination); await updateJavascriptProjectName(destination, projectName); spinner.succeed( - logger.colors.green(logger.colors.bold(t("cache.copy.success"))), + logger.colors.green( + logger.colors.bold(t("messages.success.new_project")), + ), ); - } catch (error: any) { - spinner.fail(logger.colors.red(t("cache.copy.fail"))); + } catch (error: unknown) { + spinner.fail(logger.colors.red(t("errors.cache.copy_fail"))); - const message = t("cache.copy.fail"); + const message = t("errors.cache.copy_fail"); if (error instanceof Error) { logger.error(`${message}: ${error.message}`, "CACHE"); } else { diff --git a/packages/devkit/src/core/config/writer.ts b/packages/devkit/src/core/config/writer.ts index 54abbab..fe991ad 100644 --- a/packages/devkit/src/core/config/writer.ts +++ b/packages/devkit/src/core/config/writer.ts @@ -14,7 +14,7 @@ export async function saveConfig(config: CliConfig, filePath: string) { ...config, }); } catch (error) { - throw new DevkitError(t("error.config.save", { file: filePath }), { + throw new DevkitError(t("errors.config.save_fail", { file: filePath }), { cause: error, }); } @@ -42,7 +42,7 @@ export async function updateTemplateCacheStrategy( ): Promise { const targetPath = await getConfigFilepath(); if (!targetPath) { - throw new ConfigError(t("error.config.not.found"), ""); + throw new ConfigError(t("errors.config.not_found"), ""); } let foundTemplate = false; @@ -57,7 +57,7 @@ export async function updateTemplateCacheStrategy( if (!foundTemplate) { throw new ConfigError( - t("error.template.not_found", { template: templateName }), + t("errors.template.not_found", { template: templateName }), "", ); } diff --git a/packages/devkit/src/core/info/info.ts b/packages/devkit/src/core/info/info.ts index 048c414..dd242bd 100644 --- a/packages/devkit/src/core/info/info.ts +++ b/packages/devkit/src/core/info/info.ts @@ -28,7 +28,7 @@ const getPackageManagerVersion = async ( return `${managerToQuery} v${(stdout as string)?.trim?.()}`; // oxlint-disable-next-line no-unused-vars } catch (error) { - return t("info.error.package_manager_not_found", { + return t("errors.system.info_package_manager_not_found", { manager: managerToQuery, }); } @@ -64,11 +64,11 @@ export const collectSystemInfo = async ( const globalConfigPath = foundGlobalPath ? foundGlobalPath - : t("info.config.global_expected_location"); + : t("commands.info.config.global_expected_location"); const localConfigPath = foundLocalPath ? foundLocalPath - : t("info.config.local_expected_location"); + : t("commands.info.config.local_expected_location"); let runtimeName: string; let runtimeVersion: string; @@ -86,7 +86,10 @@ export const collectSystemInfo = async ( cliVersion, os: `${os.type()} ${os.release()}`, arch: os.arch(), - shell: process.env.SHELL || process.env.COMSPEC || t("info.shell.unknown"), + shell: + process.env.SHELL || + process.env.COMSPEC || + t("commands.info.shell.unknown"), runtimeName, runtimeVersion, packageManagerVersion: packageManagerVersion, diff --git a/packages/devkit/src/core/info/project.ts b/packages/devkit/src/core/info/project.ts index 86786f8..d0fd6ad 100644 --- a/packages/devkit/src/core/info/project.ts +++ b/packages/devkit/src/core/info/project.ts @@ -9,7 +9,7 @@ export async function getProjectVersion(): Promise { try { const packageRoot = await findPackageRoot(); if (!packageRoot) { - throw new Error(t("error.package.root.not_found")); + throw new Error(t("errors.system.package_root_not_found")); } const packageJsonPath = path.join(packageRoot, FILE_NAMES.packageJson); @@ -17,7 +17,7 @@ export async function getProjectVersion(): Promise { return packageJson.version; } catch (error) { - const errorMessage = t("error.version.read_fail"); + const errorMessage = t("errors.system.version_read_fail"); if (error instanceof Error) { logger.error(`${errorMessage}: ${error.message}`, "INFO"); diff --git a/packages/devkit/src/core/prompts/prompts.ts b/packages/devkit/src/core/prompts/prompts.ts index be6456e..4dbce63 100644 --- a/packages/devkit/src/core/prompts/prompts.ts +++ b/packages/devkit/src/core/prompts/prompts.ts @@ -19,7 +19,7 @@ export async function promptForLanguage( required = true, defaultValue?: SupportedProgrammingLanguageValues, ): Promise { - const message = `${t("cli.add_template.prompts.language")} ${ + const message = `${t("commands.template.add.prompts.language")} ${ required ? logger.colors.red("(required)") : logger.colors.dim("(optional)") }`; const choices = Object.values(ProgrammingLanguage).map((lang) => ({ @@ -43,7 +43,7 @@ export async function promptForPackageManager( defaultValue?: SupportedPackageManager, ): Promise { const message = `${t( - "cli.add_template.prompts.package_manager", + "commands.template.add.prompts.package_manager", )} ${required ? logger.colors.red("(required)") : logger.colors.dim("(optional)")}`; return (await select({ message, @@ -65,7 +65,7 @@ export async function promptForCacheStrategy( defaultValue?: CacheStrategy, ): Promise { const message = `${t( - "cli.add_template.prompts.cache_strategy", + "commands.template.add.prompts.cache_strategy", )} ${required ? logger.colors.red("(required)") : logger.colors.dim("(optional)")}`; return (await select({ message, diff --git a/packages/devkit/src/core/template/printer.ts b/packages/devkit/src/core/template/printer.ts index 46d5c98..2efdd90 100644 --- a/packages/devkit/src/core/template/printer.ts +++ b/packages/devkit/src/core/template/printer.ts @@ -33,21 +33,21 @@ export function printTemplates( const alias = templateConfig?.alias ? cyanDim( - `(${t("cli.add_template.options.alias")}: ${templateConfig.alias})`, + `(${t("commands.template.add.options.alias")}: ${templateConfig.alias})`, ) : ""; const description = templateConfig?.description - ? `\n ${dim(t("cli.add_template.options.description"))}: ${templateConfig.description}` + ? `\n ${dim(t("commands.template.add.options.description"))}: ${templateConfig.description}` : ""; const location = templateConfig?.location ? `\n ${dim("Location")}: ${templateConfig.location}` : ""; const cacheStrategy = templateConfig?.cacheStrategy - ? `\n ${dim(t("cli.add_template.options.cache"))}: ${templateConfig.cacheStrategy}` + ? `\n ${dim(t("commands.template.add.options.cache"))}: ${templateConfig.cacheStrategy}` : ""; const packageManager = templateConfig?.packageManager - ? `\n ${dim(t("cli.add_template.options.package_manager"))}: ${templateConfig.packageManager}` + ? `\n ${dim(t("commands.template.add.options.package_manager"))}: ${templateConfig.packageManager}` : ""; const coloredName = logger.colors.green(templateName); diff --git a/packages/devkit/src/core/template/update-project-name.ts b/packages/devkit/src/core/template/update-project-name.ts index 6d719ce..bf44238 100644 --- a/packages/devkit/src/core/template/update-project-name.ts +++ b/packages/devkit/src/core/template/update-project-name.ts @@ -11,7 +11,7 @@ export async function updateJavascriptProjectName( const packageJsonPath = path.join(projectPath, FILE_NAMES.packageJson); if (!fs.existsSync(packageJsonPath)) { - logger.error(t("error.package.file_not_found"), "TEMPL"); + logger.error(t("errors.system.package_file_not_found"), "TEMPL"); return; } @@ -21,7 +21,7 @@ export async function updateJavascriptProjectName( await fs.writeJson(packageJsonPath, packageJson); } catch (error) { - const errorMessage = t("error.package.failed_to_update_project_name"); + const errorMessage = t("errors.system.package_name_update_fail"); if (error instanceof Error) { logger.error(`${errorMessage}: ${error.message}`, "TEMPL"); diff --git a/packages/devkit/src/scaffolding/cli-runner.ts b/packages/devkit/src/scaffolding/cli-runner.ts index 49fbc29..f8b6681 100644 --- a/packages/devkit/src/scaffolding/cli-runner.ts +++ b/packages/devkit/src/scaffolding/cli-runner.ts @@ -18,7 +18,7 @@ export async function runCliCommand(options: RunCliCommandOptions) { try { if (!finalCommand.trim()) { throw new DevkitError( - t("error.invalid.command", { command: finalCommand }), + t("errors.validation.invalid_command", { command: finalCommand }), ); } await executeCommand(`${finalCommand} ${projectName}`, { @@ -26,6 +26,6 @@ export async function runCliCommand(options: RunCliCommandOptions) { }); } catch (error: any) { const cause = error.stderr || error.message; - throw new DevkitError(t("scaffolding.run.fail"), { cause }); + throw new DevkitError(t("errors.scaffolding.run_fail"), { cause }); } } diff --git a/packages/devkit/src/scaffolding/dependencies.ts b/packages/devkit/src/scaffolding/dependencies.ts index 6f71bad..e4c9ad5 100644 --- a/packages/devkit/src/scaffolding/dependencies.ts +++ b/packages/devkit/src/scaffolding/dependencies.ts @@ -21,6 +21,8 @@ export async function installDependencies(options: InstallDependenciesOptions) { stdio: "inherit", }); } catch (error) { - throw new DevkitError(t("scaffolding.install.fail"), { cause: error }); + throw new DevkitError(t("errors.scaffolding.install_fail"), { + cause: error, + }); } } diff --git a/packages/devkit/src/scaffolding/javascript.ts b/packages/devkit/src/scaffolding/javascript.ts index 8298ea4..4c0b4af 100644 --- a/packages/devkit/src/scaffolding/javascript.ts +++ b/packages/devkit/src/scaffolding/javascript.ts @@ -22,7 +22,7 @@ export async function scaffoldProject( ): Promise { const { projectName, templateConfig, packageManager, cacheStrategy } = options; - const spinner = logger.spinner(t("scaffolding.run.start")); + const spinner = logger.spinner(t("messages.status.scaffolding_project")); let isOfficialCli = false; try { @@ -30,7 +30,9 @@ export async function scaffoldProject( isOfficialCli = true; spinner.text = logger.colors.cyan( logger.colors.bold( - t("scaffolding.run.start", { command: templateConfig.location }), + t("messages.scaffolding.run_start", { + command: templateConfig.location, + }), ), ); spinner.stop(); @@ -51,20 +53,22 @@ export async function scaffoldProject( strategy: cacheStrategy, }); } else { - spinner.text = logger.colors.cyan(t("scaffolding.copy.start")); + spinner.text = logger.colors.cyan(t("messages.scaffolding.copy_start")); spinner.start(); await copyLocalTemplate({ sourcePath: templateConfig.location, projectName, spinner, }); - spinner.succeed(logger.colors.green(t("scaffolding.copy.success"))); + spinner.succeed( + logger.colors.green(t("messages.scaffolding.copy_success")), + ); } if (!isOfficialCli) { spinner.text = logger.colors.cyan( logger.colors.bold( - `${t("scaffolding.install.start", { pm: packageManager })}\n`, + `${t("messages.scaffolding.install_start", { pm: packageManager })}\n`, ), ); spinner.stop(); @@ -74,13 +78,13 @@ export async function scaffoldProject( if (!isOfficialCli) { logger.log( logger.colors.green( - logger.colors.bold(t("scaffolding.complete.success")), + logger.colors.bold(t("messages.success.scaffolding_complete")), ), ); logger.log( logger.colors.white( logger.colors.bold( - logger.colors.italic(t("scaffolding.complete.next_steps")), + logger.colors.italic(t("messages.success.next_steps")), ), ), ); @@ -92,10 +96,10 @@ export async function scaffoldProject( ), ); } - } catch (err: any) { - spinner.fail(logger.colors.red(t("error.scaffolding.unexpected"))); + } catch (err: unknown) { + spinner.fail(logger.colors.red(t("errors.scaffolding.unexpected"))); - const message = t("error.scaffolding.unexpected"); + const message = t("errors.scaffolding.unexpected"); if (err instanceof Error) { logger.error(`${message}: ${err.message}`, "UNKNOWN"); } else { diff --git a/packages/devkit/src/scaffolding/local-template.ts b/packages/devkit/src/scaffolding/local-template.ts index abdb83c..459cf59 100644 --- a/packages/devkit/src/scaffolding/local-template.ts +++ b/packages/devkit/src/scaffolding/local-template.ts @@ -29,6 +29,6 @@ export async function copyLocalTemplate(options: CopyLocalTemplateOptions) { await copyJavascriptTemplate(finalSourcePath, projectPath); await updateJavascriptProjectName(projectPath, projectName); } catch (error) { - throw new DevkitError(t("scaffolding.copy.fail"), { cause: error }); + throw new DevkitError(t("errors.scaffolding.copy_fail"), { cause: error }); } } diff --git a/packages/devkit/src/utils/errors/handler.ts b/packages/devkit/src/utils/errors/handler.ts index 375b66f..2e395c9 100644 --- a/packages/devkit/src/utils/errors/handler.ts +++ b/packages/devkit/src/utils/errors/handler.ts @@ -6,22 +6,25 @@ export function handleErrorAndExit(error: unknown, spinner?: TSpinner): void { spinner?.stop(); if (error instanceof ConfigError) { - logger.error(`${t("error.config.generic")}: ${error.message}`, "CONFIG"); + logger.error(`${t("errors.config.read_fail")}: ${error.message}`, "CONFIG"); if (error.filePath) { logger.dimmed(`File path: ${error.filePath}`); } } else if (error instanceof GitError) { - logger.error(`${t("error.git.generic")}: ${error.message}`, "GIT"); + logger.error(`${t("errors.system.git_generic")}: ${error.message}`, "GIT"); if (error.url) { logger.dimmed(`Repository URL: ${error.url}`); } } else if (error instanceof DevkitError) { - logger.error(`${t("error.devkit_specific")}: ${error.message}`, "DEV"); + logger.error( + `${t("errors.generic.devkit_specific")}: ${error.message}`, + "DEV", + ); } else if (error instanceof Error) { - logger.error(`${t("error.unexpected")}: ${error.message}`, "ERR"); + logger.error(`${t("errors.generic.unexpected")}: ${error.message}`, "ERR"); } else { - logger.error(t("error.unknown"), "UNKNOWN"); + logger.error(t("errors.generic.unknown"), "UNKNOWN"); } const cause = error instanceof Error ? error.cause : undefined; diff --git a/packages/devkit/src/utils/logger.ts b/packages/devkit/src/utils/logger.ts index 4e00639..1634445 100644 --- a/packages/devkit/src/utils/logger.ts +++ b/packages/devkit/src/utils/logger.ts @@ -9,10 +9,10 @@ function getTimestamp(): string { function formatError(message: string, errorType: ErrorType): string { const timestamp = getTimestamp(); - const typeTag = chalk.bold.red(`[${errorType}]`); - const coloredMessage = chalk.redBright(`❌ ${message}`); + const typeTag = chalk.bold.red(`❌${timestamp}::[${errorType}]`); + const coloredMessage = chalk.redBright(`${message}`); - return `${timestamp} ${typeTag} ${coloredMessage}`; + return `${typeTag}>> ${coloredMessage}`; } export type ErrorType = diff --git a/packages/devkit/src/utils/validations/config.ts b/packages/devkit/src/utils/validations/config.ts index 033a55f..f5068c8 100644 --- a/packages/devkit/src/utils/validations/config.ts +++ b/packages/devkit/src/utils/validations/config.ts @@ -18,7 +18,7 @@ export function validatePackageManager( const validPackageManagers = Object.values(PackageManagers); if (!validPackageManagers.includes(value as PackageManager)) { throw new DevkitError( - t("error.invalid.value", { + t("errors.validation.invalid_value", { key: "defaultPackageManager", options: validPackageManagers.join(", "), }), @@ -32,7 +32,7 @@ export function validateCacheStrategy( const validStrategies = VALID_CACHE_STRATEGIES; if (!validStrategies.includes(value as CacheStrategy)) { throw new DevkitError( - t("error.invalid.value", { + t("errors.validation.invalid_value", { key: "cacheStrategy", options: validStrategies.join(", "), }), @@ -46,7 +46,7 @@ export function validateLanguage( const validLanguages = Object.values(TextLanguages); if (!validLanguages.includes(value as TextLanguageValues)) { throw new DevkitError( - t("error.invalid.value", { + t("errors.validation.invalid_value", { key: "language", options: validLanguages.join(", "), }), @@ -62,8 +62,8 @@ export function validateProgrammingLanguage( ); if (!validLanguages.includes(value as SupportedProgrammingLanguageValues)) { throw new DevkitError( - t("error.invalid.value", { - key: "language", + t("errors.validation.invalid_value", { + key: "Programming Language", options: validLanguages.join(", "), }), ); diff --git a/packages/devkit/src/utils/validations/templates.ts b/packages/devkit/src/utils/validations/templates.ts index f203da2..0b504ac 100644 --- a/packages/devkit/src/utils/validations/templates.ts +++ b/packages/devkit/src/utils/validations/templates.ts @@ -30,12 +30,12 @@ export const validateLocation = async (location: string, spinner?: Ora) => { const isGithubUrl = isFromGitHub(location); if (isGithubUrl) { - spinner?.start(t("cli.add_template.adding")); + spinner?.start(t("messages.status.template_adding")); const isValid = await checkGitHubRepoExists(location); if (!isValid) { spinner?.fail(); handleErrorAndExit( - new DevkitError(t("error.invalid.github-repo", { url: location })), + new DevkitError(t("errors.validation.github_repo", { url: location })), spinner, ); return; @@ -45,7 +45,7 @@ export const validateLocation = async (location: string, spinner?: Ora) => { const filePath = normalizePath(location); if (!fs.existsSync(filePath)) { handleErrorAndExit( - new DevkitError(t("error.invalid.local-path", { path: filePath })), + new DevkitError(t("errors.validation.local_path", { path: filePath })), spinner, ); } @@ -54,19 +54,19 @@ export const validateLocation = async (location: string, spinner?: Ora) => { export function validateAlias(alias: string): void { if (!alias.trim()) { - throw new DevkitError(t("error.invalid.alias.empty")); + throw new DevkitError(t("errors.validation.alias_empty")); } if (alias.trim().length < 2) { - throw new DevkitError(t("error.invalid.alias.too-short")); + throw new DevkitError(t("errors.validation.alias_too_short")); } } export function validateDescription(description: string): void { if (!description.trim()) { - throw new DevkitError(t("error.invalid.description.empty")); + throw new DevkitError(t("errors.validation.description_empty")); } const wordCount = description.replaceAll(" ", "").length; if (wordCount < 10) { - throw new DevkitError(t("error.invalid.description.too-short")); + throw new DevkitError(t("errors.validation.description_too_short")); } } diff --git a/packages/devkit/src/utils/validations/validateConfigValue.ts b/packages/devkit/src/utils/validations/validateConfigValue.ts index 0abb469..af2fe2b 100644 --- a/packages/devkit/src/utils/validations/validateConfigValue.ts +++ b/packages/devkit/src/utils/validations/validateConfigValue.ts @@ -12,7 +12,7 @@ export function validateConfigValue(key: string, value: string): void { if (!resolvedKey) { throw new DevkitError( - t("error.invalid.key", { + t("errors.validation.invalid_key", { key, keys: Object.keys(configAliases).join(", "), }),