Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/short-deer-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"scaffolder-toolkit": minor
---

feat: refactoring configuration command logic into dedicated, reusable files and adding comprehensive unit tests for init, remove, and update commands, including new wildcard (\*) template resolution.
17 changes: 13 additions & 4 deletions packages/devkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,9 @@ dk config add javascript react-ts-template --description "My custom React TS tem
The `dk config update` command allows you to modify an existing template's properties.

- You must provide the language and the name of the template(s) you wish to update.
- **Important:** If you list multiple template names, the command will apply the **exact same property updates** to all of them. For instance, you cannot update the `description` of two different templates to different values in a single command.
- You can also update the template's name using the `--new-name` flag.
- **Wildcard Support:** Use the wildcard character `*` in place of a template name (e.g., `dk config update javascript *`) to apply the update to **all templates** registered under that language.
- **Multiple Templates:** You can list multiple template names to apply the **exact same property updates** to all of them.
- You can also update the template's name using the `--new-name` flag (this flag only works when updating a single template).
- Use the `--global` flag to update templates in your global (`~/.devkitrc`) file.

<!-- end list -->
Expand All @@ -284,18 +285,21 @@ The `dk config update` command allows you to modify an existing template's prope
# Update the description and alias for a single template
dk config update javascript my-template --description "A new and improved description" --alias "my-alias"

# Update a template's package manager and remove its alias
dk config update javascript my-template --package-manager bun --alias null
# Apply a new package manager to ALL javascript templates in the local config
dk config update javascript * --package-manager bun

# Change a template's name and its description in a single command
dk config update javascript my-template --new-name my-cool-template --description "A newly renamed template"
```

---

#### Remove an existing template from your configuration

The `dk config remove` command allows you to delete one or more templates from your configuration file.

- You must provide the language and the name(s) of the template(s) you wish to remove.
- **Wildcard Support:** Use the wildcard character `*` in place of a template name (e.g., `dk config remove javascript *`) to remove **all templates** registered under that language.
- **Multiple Templates:** You can list multiple template names to remove them all in one operation (e.g., `dk config remove javascript template1 template2`).
- **Global:** You can explicitly remove the template from your global (`~/.devkitrc`) file using the `--global` flag.
- **Local:** It removes the template from the `.devkit.json` file in the root of your current project.
Expand All @@ -306,13 +310,18 @@ The `dk config remove` command allows you to delete one or more templates from y
# Remove the 'react-ts-template' for 'javascript' from the local config
dk config remove javascript react-ts-template

# Remove ALL javascript templates from the local config
dk config remove javascript *

# Remove multiple templates at once from the local config
dk config remove javascript template1 template2

# Remove the 'node-api' template from the global config
dk config remove node node-api --global
```

---

#### Manage cache strategy for a template

Use the `dk config cache` command to update the cache strategy for a specific template.
Expand Down
2 changes: 1 addition & 1 deletion packages/devkit/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ This document tracks all planned and completed tasks for the Dev Kit project.
- [x] **Enhance `list` Command**: Add flag to also see default config `--include-defaults`.
- [x] Explain the usage of `{pm}` role inside the config in the documentation.
- [x] Add a `--settings, -s` to the `dk list` command to display the current configuration settings only
- [ ] Add wildcard support for template name in the `dk config update` and `dk config remove` commands.
- [x] Add wildcard support for template name in the `dk config update` and `dk config remove` commands.
- [ ] add short keys to get config using `dk config` like `dk config lang` for `dk config language`
- [ ] ** Enhance for organization Purpose **: Add new language `Typescript(ts)` with same code as javascript(js), also support for nodejs(node) template name for those who prefer it than the programming language name
- [ ] Add a configuration validation step when updating the config file to ensure all required fields are present and correctly formatted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 clé1 valeur1 clé2 valeur2).",
"Les valeurs pour l'option '--set' doivent être une série de paires clé-valeur (ex: --set clé1 valeur1 clé2 valeur2).",
);
});

Expand Down
200 changes: 177 additions & 23 deletions packages/devkit/__tests__/integrations/config/remove.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const localConfig: CliConfig = {
location: "https://github.com/vuejs/vue",
alias: "vb",
},
"node-cli": {
description: "A Node.js CLI template",
location: "https://github.com/node-cli-template",
},
},
},
},
Expand All @@ -51,17 +55,21 @@ const globalConfig: CliConfig = {
templates: {
javascript: {
templates: {
"react-ts": {
description: "A React project with TypeScript",
location: "https://github.com/react-ts-template",
alias: "rt",
"global-react-ts": {
description: "A global React project with TypeScript",
location: "https://github.com/react-ts-template-global",
alias: "grt",
},
"global-lib": {
description: "A global library template",
location: "https://github.com/global-lib",
},
},
},
},
};

describe("dk config remove", () => {
describe("dk config remove - Basic and Alias", () => {
beforeAll(() => {
vi.unmock("#utils/shell.js");
});
Expand All @@ -84,7 +92,7 @@ describe("dk config remove", () => {
await fs.remove(globalConfigDir);
});

it("should remove a template from the local config", async () => {
it("should remove a single template from the local config by name", async () => {
await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig);
const { exitCode, all } = await execute(
"bun",
Expand All @@ -106,21 +114,48 @@ describe("dk config remove", () => {
expect(
updatedConfig.templates.javascript.templates["react-ts"],
).toBeDefined();
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(2);
});

it("should remove a template from the global config when --global is used", async () => {
await fs.writeJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
globalConfig,
it("should remove multiple templates at once by name", async () => {
await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "remove", "javascript", "vue-basic", "react-ts"],
{ all: true },
);

const updatedConfig = await fs.readJson(
path.join(tempDir, LOCAL_CONFIG_FILE_NAME),
);

expect(exitCode).toBe(0);
expect(all).toContain(
"Successfully removed 2 template(s) (vue-basic, react-ts) from javascript.",
);
expect(
updatedConfig.templates.javascript.templates["vue-basic"],
).toBeUndefined();
expect(
updatedConfig.templates.javascript.templates["react-ts"],
).toBeUndefined();
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(1);
});

it("should remove templates by alias", async () => {
await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "--global", "rm", "javascript", "react-ts"],
{ all: true, env: { HOME: globalConfigDir } },
[CLI_PATH, "config", "remove", "javascript", "rt"],
{ all: true },
);

const updatedConfig = await fs.readJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
path.join(tempDir, LOCAL_CONFIG_FILE_NAME),
);

expect(exitCode).toBe(0);
Expand All @@ -130,13 +165,36 @@ describe("dk config remove", () => {
expect(
updatedConfig.templates.javascript.templates["react-ts"],
).toBeUndefined();
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(2);
});
});

describe("dk config remove - Wildcard Support", () => {
beforeEach(async () => {
originalCwd = process.cwd();
tempDir = path.join(os.tmpdir(), `devkit-test-config-remove-${Date.now()}`);
globalConfigDir = path.join(
os.tmpdir(),
`devkit-global-config-dir-${Date.now()}`,
);
await fs.ensureDir(tempDir);
process.chdir(tempDir);
await fs.ensureDir(globalConfigDir);
});

afterEach(async () => {
process.chdir(originalCwd);
await fs.remove(tempDir);
await fs.remove(globalConfigDir);
});

it("should remove multiple templates at once", async () => {
it("should remove ALL local templates using the wildcard '*'", async () => {
await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "remove", "javascript", "vue-basic", "react-ts"],
[CLI_PATH, "config", "remove", "javascript", "*"],
{ all: true },
);

Expand All @@ -146,18 +204,50 @@ describe("dk config remove", () => {

expect(exitCode).toBe(0);
expect(all).toContain(
"Successfully removed 2 template(s) (vue-basic, react-ts) from javascript.",
"Successfully removed 3 template(s) (react-ts, vue-basic, node-cli) from javascript.",
);
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(0);
});

it("should remove templates by alias", async () => {
it("should remove ALL global templates using the wildcard '*' with --global", async () => {
await fs.writeJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
globalConfig,
);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "--global", "remove", "javascript", "*"],
{ all: true, env: { HOME: globalConfigDir } },
);

const updatedConfig = await fs.readJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
);

expect(exitCode).toBe(0);
expect(all).toContain(
"Successfully removed 2 template(s) (global-react-ts, global-lib) from javascript.",
);
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(0);
});

it("should remove all templates and warn for explicitly listed not-found names when using wildcard", async () => {
await fs.writeJson(path.join(tempDir, LOCAL_CONFIG_FILE_NAME), localConfig);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "remove", "javascript", "rt"],
[
CLI_PATH,
"config",
"remove",
"javascript",
"*",
"non-existent-A",
"non-existent-B",
],
{ all: true },
);

Expand All @@ -167,11 +257,61 @@ describe("dk config remove", () => {

expect(exitCode).toBe(0);
expect(all).toContain(
"Successfully removed 1 template(s) (react-ts) from javascript.",
"Successfully removed 3 template(s) (react-ts, vue-basic, node-cli) from javascript.",
);
expect(all).toContain(
"⚠️ The following templates were not found: non-existent-A, non-existent-B",
);
expect(
updatedConfig.templates.javascript.templates["react-ts"],
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(0);
});
});

describe("dk config remove - Global and Edge Cases", () => {
beforeEach(async () => {
originalCwd = process.cwd();
tempDir = path.join(os.tmpdir(), `devkit-test-config-remove-${Date.now()}`);
globalConfigDir = path.join(
os.tmpdir(),
`devkit-global-config-dir-${Date.now()}`,
);
await fs.ensureDir(tempDir);
process.chdir(tempDir);
await fs.ensureDir(globalConfigDir);
});

afterEach(async () => {
process.chdir(originalCwd);
await fs.remove(tempDir);
await fs.remove(globalConfigDir);
});

it("should remove a template from the global config when --global is used", async () => {
await fs.writeJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
globalConfig,
);
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "--global", "rm", "javascript", "global-lib"],
{ all: true, env: { HOME: globalConfigDir } },
);

const updatedConfig = await fs.readJson(
path.join(globalConfigDir, GLOBAL_CONFIG_FILE_NAME),
);

expect(exitCode).toBe(0);
expect(all).toContain(
"Successfully removed 1 template(s) (global-lib) from javascript.",
);
expect(
updatedConfig.templates.javascript.templates["global-lib"],
).toBeUndefined();
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(1);
});

it("should show a warning for templates not found while removing others", async () => {
Expand All @@ -196,6 +336,9 @@ describe("dk config remove", () => {
expect(
updatedConfig.templates.javascript.templates["vue-basic"],
).toBeUndefined();
expect(
Object.keys(updatedConfig.templates.javascript.templates).length,
).toBe(2);
});

it("should show an error if no templates are found to remove", async () => {
Expand All @@ -215,7 +358,7 @@ describe("dk config remove", () => {

expect(exitCode).toBe(1);
expect(all).toContain(
"::[DEV]>> Devkit encountered an unexpected internal issue: Template 'not-found-1, not-found-2' not found in configuration.",
"Template 'not-found-1, not-found-2' not found in configuration.",
);
});

Expand All @@ -229,11 +372,11 @@ describe("dk config remove", () => {

expect(exitCode).toBe(1);
expect(all).toContain(
"::[DEV]>> Devkit encountered an unexpected internal issue: Invalid value for Programming Language. Valid options are: javascript",
"Invalid value for Programming Language. Valid options are: javascript",
);
});

it("should throw an error if no config file exists to remove from", async () => {
it("should throw an error if no config file exists to remove from (local)", async () => {
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "remove", "javascript", "vue-basic"],
Expand All @@ -243,4 +386,15 @@ describe("dk config remove", () => {
expect(exitCode).toBe(1);
expect(all).toContain("No configuration file found.");
});

it("should throw an error if no config file exists to remove from (global)", async () => {
const { exitCode, all } = await execute(
"bun",
[CLI_PATH, "config", "--global", "remove", "javascript", "vue-basic"],
{ all: true, reject: false, env: { HOME: globalConfigDir } },
);

expect(exitCode).toBe(1);
expect(all).toContain("Global configuration file not found.");
});
});
Loading