ui-thing 0.2.9 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/.claude/settings.local.json +5 -0
  2. package/.github/workflows/main.yml +7 -4
  3. package/.github/workflows/test.yml +7 -4
  4. package/.prettierrc +1 -1
  5. package/CHANGELOG.md +52 -0
  6. package/README.md +208 -21
  7. package/bun.lock +171 -134
  8. package/dist/index.js +28 -25
  9. package/dist/index.js.map +1 -1
  10. package/package.json +15 -14
  11. package/src/commands/add.ts +41 -12
  12. package/src/commands/block.ts +5 -3
  13. package/src/commands/list.ts +48 -0
  14. package/src/commands/prose.ts +7 -3
  15. package/src/commands/remove.ts +109 -0
  16. package/src/commands/theme.ts +5 -14
  17. package/src/commands/update.ts +81 -0
  18. package/src/index.ts +6 -0
  19. package/src/types.ts +12 -0
  20. package/src/utils/config.ts +9 -16
  21. package/src/utils/constants.ts +1 -0
  22. package/src/utils/fetchBlockCategories.ts +13 -11
  23. package/src/utils/fetchBlocks.ts +13 -11
  24. package/src/utils/fetchComponents.ts +13 -11
  25. package/src/utils/fetchProseComponents.ts +13 -11
  26. package/src/utils/installPackages.ts +30 -4
  27. package/src/utils/installValidator.ts +3 -3
  28. package/src/utils/logger.ts +9 -0
  29. package/src/utils/uiConfigPrompt.ts +3 -6
  30. package/tests/commands/add.test.ts +136 -0
  31. package/tests/commands/list.test.ts +111 -0
  32. package/tests/commands/remove.test.ts +151 -0
  33. package/tests/commands/update.test.ts +127 -0
  34. package/tests/utils/fetchBlockCategories.test.ts +14 -2
  35. package/tests/utils/fetchBlocks.test.ts +14 -2
  36. package/tests/utils/fetchComponents.test.ts +10 -4
  37. package/tests/utils/fetchProseComponents.test.ts +14 -2
  38. package/tests/utils/installPackages.test.ts +36 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-thing",
3
- "version": "0.2.9",
3
+ "version": "0.3.1",
4
4
  "description": "CLI used to add Nuxt components to a project",
5
5
  "keywords": [
6
6
  "cli",
@@ -51,20 +51,20 @@
51
51
  ]
52
52
  },
53
53
  "dependencies": {
54
- "axios": "^1.17.0",
54
+ "axios": "^1.18.1",
55
55
  "boxen": "^8.0.1",
56
56
  "c12": "^3.3.4",
57
57
  "commander": "^15.0.0",
58
58
  "consola": "^3.4.2",
59
59
  "dotenv": "^17.4.2",
60
- "es-toolkit": "^1.47.0",
60
+ "es-toolkit": "^1.48.1",
61
61
  "execa": "^9.6.1",
62
62
  "figlet": "^1.11.0",
63
63
  "fs-extra": "^11.3.5",
64
64
  "kleur": "^4.1.5",
65
65
  "lodash": "^4.18.1",
66
66
  "magicast": "^0.5.3",
67
- "ora": "^9.4.0",
67
+ "ora": "^9.4.1",
68
68
  "prompts": "^2.4.2"
69
69
  },
70
70
  "devDependencies": {
@@ -72,24 +72,25 @@
72
72
  "@ianvs/prettier-plugin-sort-imports": "^4.7.1",
73
73
  "@types/fs-extra": "^11.0.4",
74
74
  "@types/lodash": "^4.17.24",
75
- "@types/node": "^25.9.2",
75
+ "@types/node": "^26.0.0",
76
76
  "@types/prompts": "^2.4.9",
77
- "@vitest/coverage-v8": "^4.1.8",
78
- "@vitest/ui": "^4.1.8",
77
+ "@vitest/coverage-v8": "^4.1.9",
78
+ "@vitest/ui": "^4.1.9",
79
79
  "axios-mock-adapter": "^2.1.0",
80
- "eslint": "^10.4.1",
81
- "globals": "^17.6.0",
80
+ "eslint": "^10.5.0",
81
+ "globals": "^17.7.0",
82
82
  "husky": "^9.1.7",
83
83
  "jiti": "^2.7.0",
84
- "knip": "^5.88.1",
85
- "lint-staged": "^17.0.7",
84
+ "knip": "^6.18.0",
85
+ "lint-staged": "^17.0.8",
86
86
  "prettier": "^3.8.4",
87
+ "prettier-plugin-packagejson": "^3.0.2",
87
88
  "tsup": "^8.5.1",
88
89
  "typescript": "^6.0.3",
89
- "typescript-eslint": "^8.61.0",
90
- "vitest": "^4.1.8"
90
+ "typescript-eslint": "^8.62.0",
91
+ "vitest": "^4.1.9"
91
92
  },
92
- "packageManager": "bun@1.3.9",
93
+ "packageManager": "bun@1.3.14",
93
94
  "publishConfig": {
94
95
  "access": "public"
95
96
  }
@@ -6,13 +6,16 @@ import kleur from "kleur";
6
6
  import _ from "lodash";
7
7
  import prompts from "prompts";
8
8
 
9
- import { AddCommand, Component } from "../types";
9
+ import { AddCommand, Component, UIConfig } from "../types";
10
10
  import { compareUIConfig } from "../utils/compareUIConfig";
11
11
  import { addModuleToConfig, getUIConfig } from "../utils/config";
12
+ import { DEFAULT_CONFIG, DEFAULT_CONFIG_NUXT4, PACKAGE_MANAGER_CHOICES } from "../utils/constants";
13
+ import { detectNuxtVersion } from "../utils/detectNuxtVersion";
12
14
  import { fetchComponents } from "../utils/fetchComponents";
13
15
  import { fileExists } from "../utils/fileExists";
14
16
  import { installPackages } from "../utils/installPackages";
15
17
  import { installValidator } from "../utils/installValidator";
18
+ import { logger } from "../utils/logger";
16
19
  import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
17
20
  import { promptUserForComponents } from "../utils/promptForComponents";
18
21
  import { writeFile } from "../utils/writeFile";
@@ -77,15 +80,33 @@ async function writeCategoryFiles(
77
80
  /**
78
81
  * Main command logic for adding components.
79
82
  */
80
- const runAddCommand = async (components: string[], options: AddCommand) => {
83
+ export const runAddCommand = async (components: string[], options: AddCommand) => {
81
84
  // Step 1 — Load and verify UI config
82
- let uiConfig = await getUIConfig();
83
- if (!(await compareUIConfig())) {
84
- uiConfig = await getUIConfig({ force: true });
85
- }
86
- if (_.isEmpty(uiConfig)) {
87
- consola.info("Config file not set. Exiting...");
88
- process.exit(0);
85
+ let uiConfig: UIConfig;
86
+
87
+ if (options.skipConfig) {
88
+ const nuxtVersion = detectNuxtVersion();
89
+ const base = nuxtVersion === 4 ? DEFAULT_CONFIG_NUXT4 : DEFAULT_CONFIG;
90
+ let pm = options.packageManager;
91
+ if (!pm) {
92
+ const { packageManager } = await prompts({
93
+ type: "select",
94
+ name: "packageManager",
95
+ message: "Which package manager are you using?",
96
+ choices: PACKAGE_MANAGER_CHOICES,
97
+ });
98
+ pm = packageManager ?? "npm";
99
+ }
100
+ uiConfig = { ...base, packageManager: pm };
101
+ } else {
102
+ uiConfig = await getUIConfig();
103
+ if (!(await compareUIConfig())) {
104
+ uiConfig = await getUIConfig({ force: true });
105
+ }
106
+ if (_.isEmpty(uiConfig)) {
107
+ consola.error("Config file not set. Exiting...");
108
+ process.exit(1);
109
+ }
89
110
  }
90
111
 
91
112
  // Step 2 — Fetch all available components
@@ -206,11 +227,14 @@ const runAddCommand = async (components: string[], options: AddCommand) => {
206
227
  { box: { title: "Components Added" } }
207
228
  );
208
229
 
230
+ found
231
+ .filter((c) => c.docsPath)
232
+ .forEach((c) => consola.info(`Docs: https://uithing.com${c.docsPath}`));
233
+
209
234
  const instructions = _.compact(found.flatMap((c) => c.instructions));
210
235
  if (instructions.length > 0) {
211
- console.log("");
212
- console.log(kleur.bgCyan(" Instructions "));
213
- instructions.forEach((i) => console.log(`${kleur.cyan("-")} ${i}`));
236
+ logger.info("Instructions:");
237
+ instructions.forEach((i) => logger.info(`- ${i}`));
214
238
  }
215
239
  };
216
240
 
@@ -222,6 +246,11 @@ export const add = new Command()
222
246
  .command("add")
223
247
  .description("Add a list of components to your project.")
224
248
  .option("-a --all", "Add all components to your project.", false)
249
+ .option(
250
+ "--skip-config",
251
+ "Add components without a ui-thing config file. Uses auto-detected Nuxt defaults."
252
+ )
253
+ .option("--package-manager <pm>", "Package manager to use when --skip-config is set.")
225
254
  .argument("[componentNames...]", "Components that you want to add.")
226
255
  .action(runAddCommand);
227
256
 
@@ -59,8 +59,8 @@ const runBlockCommand = async (components: string[], options: BlockOptions) => {
59
59
  uiConfig = await getUIConfig({ force: true });
60
60
  }
61
61
  if (_.isEmpty(uiConfig)) {
62
- consola.info("Config file not set. Exiting...");
63
- process.exit(0);
62
+ consola.error("Config file not set. Exiting...");
63
+ process.exit(1);
64
64
  }
65
65
 
66
66
  // Step 1: Fetch categories
@@ -145,7 +145,9 @@ const runBlockCommand = async (components: string[], options: BlockOptions) => {
145
145
  stdio: "inherit",
146
146
  });
147
147
  if (result.error) {
148
- consola.error("Failed to add components:", result.error.message);
148
+ consola.error("Failed to spawn component installer:", result.error.message);
149
+ } else if (result.status !== 0) {
150
+ consola.error(`Component installer exited with code ${result.status}`);
149
151
  }
150
152
  }
151
153
 
@@ -0,0 +1,48 @@
1
+ import path from "node:path";
2
+ import { Command } from "commander";
3
+
4
+ import { Component } from "../types";
5
+ import { getUIConfig } from "../utils/config";
6
+ import { fetchComponents } from "../utils/fetchComponents";
7
+ import { fileExists } from "../utils/fileExists";
8
+ import { logger } from "../utils/logger";
9
+
10
+ type ListOptions = { installed: boolean };
11
+
12
+ export const runListCommand = async (options: ListOptions) => {
13
+ const uiConfig = await getUIConfig();
14
+ const components = await fetchComponents();
15
+ const currentDirectory = process.cwd();
16
+
17
+ let display: Component[] = components;
18
+
19
+ if (options.installed) {
20
+ const results = await Promise.all(
21
+ components.map(async (c) => {
22
+ if (!c.files || c.files.length === 0) return false;
23
+ const targetPath = path.join(
24
+ currentDirectory,
25
+ uiConfig.componentsLocation,
26
+ c.files[0].fileName
27
+ );
28
+ return fileExists(targetPath);
29
+ })
30
+ );
31
+ display = components.filter((_, i) => results[i]);
32
+ }
33
+
34
+ if (display.length === 0) {
35
+ logger.info(options.installed ? "No components installed." : "No components available.");
36
+ return;
37
+ }
38
+
39
+ display.forEach((c) => logger.log(`${c.name} (${c.value})`));
40
+ logger.success(`${display.length} component(s) found.`);
41
+ };
42
+
43
+ export const list = new Command()
44
+ .command("list")
45
+ .name("list")
46
+ .description("List available or installed components.")
47
+ .option("-i --installed", "Only show installed components.", false)
48
+ .action(runListCommand);
@@ -71,8 +71,8 @@ const runProseCommand = async (components: string[], options: AddCommand) => {
71
71
  uiConfig = await getUIConfig({ force: true });
72
72
  }
73
73
  if (_.isEmpty(uiConfig)) {
74
- consola.info("Config file not set. Exiting...");
75
- process.exit(0);
74
+ consola.error("Config file not set. Exiting...");
75
+ process.exit(1);
76
76
  }
77
77
 
78
78
  allProse = await fetchProseComponents();
@@ -180,7 +180,9 @@ const runProseCommand = async (components: string[], options: AddCommand) => {
180
180
  stdio: "inherit",
181
181
  });
182
182
  if (result.error) {
183
- consola.error("Failed to add components:", result.error.message);
183
+ consola.error("Failed to spawn component installer:", result.error.message);
184
+ } else if (result.status !== 0) {
185
+ consola.error(`Component installer exited with code ${result.status}`);
184
186
  }
185
187
  }
186
188
 
@@ -189,6 +191,8 @@ const runProseCommand = async (components: string[], options: AddCommand) => {
189
191
  `Run the ${kleur.cyan("ui-thing@latest --help")} command to learn more.\n`,
190
192
  { box: { title: "Prose Components Added" } }
191
193
  );
194
+
195
+ found.filter((c) => c.docsUrl).forEach((c) => consola.info(`Docs: ${c.docsUrl}`));
192
196
  };
193
197
 
194
198
  export const prose = new Command()
@@ -0,0 +1,109 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { Command } from "commander";
4
+ import prompts from "prompts";
5
+
6
+ import { Component } from "../types";
7
+ import { getUIConfig } from "../utils/config";
8
+ import { fetchComponents } from "../utils/fetchComponents";
9
+ import { fileExists } from "../utils/fileExists";
10
+ import { logger } from "../utils/logger";
11
+ import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
12
+
13
+ export const runRemoveCommand = async (componentNames: string[]) => {
14
+ const uiConfig = await getUIConfig();
15
+ const allComponents = await fetchComponents();
16
+ const currentDirectory = process.cwd();
17
+
18
+ let toRemove = componentNames;
19
+
20
+ if (toRemove.length === 0) {
21
+ const installed = (
22
+ await Promise.all(
23
+ allComponents.map(async (c) => {
24
+ if (!c.files || c.files.length === 0) return null;
25
+ const targetPath = path.join(
26
+ currentDirectory,
27
+ uiConfig.componentsLocation,
28
+ c.files[0].fileName
29
+ );
30
+ return (await fileExists(targetPath)) ? c : null;
31
+ })
32
+ )
33
+ ).filter(Boolean) as Component[];
34
+
35
+ if (installed.length === 0) {
36
+ logger.info("No installed components found.");
37
+ return;
38
+ }
39
+
40
+ const { selected } = await prompts({
41
+ type: "autocompleteMultiselect",
42
+ name: "selected",
43
+ message: "Select components to remove",
44
+ choices: installed.map((c) => ({ title: c.name, value: c.value })),
45
+ });
46
+
47
+ if (!selected || selected.length === 0) {
48
+ logger.info("No components selected. Exiting...");
49
+ process.exit(0);
50
+ }
51
+ toRemove = selected;
52
+ }
53
+
54
+ const found = toRemove
55
+ .map((name) => allComponents.find((c) => c.value.toLowerCase() === name.toLowerCase()))
56
+ .filter(Boolean) as Component[];
57
+
58
+ if (found.length === 0) {
59
+ logger.error("None of the specified components were found in the registry.");
60
+ process.exit(1);
61
+ }
62
+
63
+ const result = await prompts({
64
+ type: "confirm",
65
+ name: "confirmed",
66
+ message: `Remove ${found.map((c) => c.name).join(", ")}?`,
67
+ initial: false,
68
+ });
69
+
70
+ if (!result?.confirmed) {
71
+ logger.info("Cancelled.");
72
+ return;
73
+ }
74
+
75
+ for (const component of found) {
76
+ const allFiles = [
77
+ ...component.files.map((f) =>
78
+ path.join(currentDirectory, uiConfig.componentsLocation, f.fileName)
79
+ ),
80
+ ...component.utils.map((f) =>
81
+ path.join(currentDirectory, uiConfig.utilsLocation, f.fileName)
82
+ ),
83
+ ...(component.composables ?? []).map((f) =>
84
+ path.join(currentDirectory, uiConfig.composablesLocation, f.fileName)
85
+ ),
86
+ ];
87
+
88
+ for (const filePath of allFiles) {
89
+ const exists = await fileExists(filePath);
90
+ if (!exists) {
91
+ logger.warn(`Skipped (not found): ${path.basename(filePath)}`);
92
+ continue;
93
+ }
94
+ await fs.unlink(filePath);
95
+ logger.success(`Removed: ${path.basename(filePath)}`);
96
+ }
97
+ }
98
+
99
+ printFancyBoxMessage("Removed!", undefined, { box: { title: "Components Removed" } });
100
+ };
101
+
102
+ export const remove = new Command()
103
+ .command("remove")
104
+ .name("remove")
105
+ .description(
106
+ "Remove installed components from your project.\nNote: shared utility files used by multiple components are deleted — other components depending on them may break."
107
+ )
108
+ .argument("[componentNames...]", "Components to remove.")
109
+ .action(runRemoveCommand);
@@ -1,6 +1,5 @@
1
1
  import { Command } from "commander";
2
2
  import fse from "fs-extra";
3
- import kleur from "kleur";
4
3
  import _ from "lodash";
5
4
  import prompts from "prompts";
6
5
 
@@ -8,40 +7,33 @@ import { createCSS } from "../templates/css";
8
7
  import { compareUIConfig } from "../utils/compareUIConfig";
9
8
  import { getUIConfig } from "../utils/config";
10
9
  import { CSS_THEME_OPTIONS } from "../utils/constants";
10
+ import { logger } from "../utils/logger";
11
11
  import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
12
12
 
13
- /**
14
- * Validates if a theme name exists in the predefined options.
15
- */
16
13
  const validateThemeName = (name: string) => {
17
14
  return CSS_THEME_OPTIONS.some((option) => option.value === name?.toLowerCase());
18
15
  };
19
16
 
20
- /**
21
- * Adds a new theme to the project.
22
- */
23
17
  export const theme = new Command()
24
18
  .command("theme")
25
19
  .name("theme")
26
20
  .description("Add a new theme to your project.")
27
21
  .argument("[themeName]", "The name of the theme you would like to add")
28
22
  .action(async (themeName?: string) => {
29
- // Get ui config
30
23
  let uiConfig = await getUIConfig();
31
24
  const uiConfigIsCorrect = await compareUIConfig();
32
25
  if (!uiConfigIsCorrect) {
33
26
  uiConfig = await getUIConfig({ force: true });
34
27
  }
35
28
  if (_.isEmpty(uiConfig)) {
36
- console.log(kleur.red("Config file not set. Exiting..."));
37
- process.exit(0);
29
+ logger.error("Config file not set. Exiting...");
30
+ process.exit(1);
38
31
  }
39
32
 
40
33
  let selectedTheme =
41
34
  themeName && validateThemeName(themeName) ? themeName.toLowerCase() : undefined;
42
35
 
43
36
  if (!selectedTheme) {
44
- // Prompt for theme if not provided or invalid
45
37
  const { theme } = await prompts([
46
38
  {
47
39
  name: "theme",
@@ -51,13 +43,12 @@ export const theme = new Command()
51
43
  },
52
44
  ]);
53
45
  if (!theme) {
54
- console.log(kleur.red("No theme selected. Exiting..."));
46
+ logger.warn("No theme selected. Exiting...");
55
47
  process.exit(0);
56
48
  }
57
49
  selectedTheme = theme;
58
50
  }
59
51
 
60
- // Check if the file exists
61
52
  if (fse.existsSync(uiConfig.tailwindCSSLocation)) {
62
53
  const { force } = await prompts([
63
54
  {
@@ -68,7 +59,7 @@ export const theme = new Command()
68
59
  },
69
60
  ]);
70
61
  if (!force) {
71
- console.log("Exiting...");
62
+ logger.info("Exiting...");
72
63
  return process.exit(0);
73
64
  }
74
65
  }
@@ -0,0 +1,81 @@
1
+ import path from "node:path";
2
+ import { Command } from "commander";
3
+ import prompts from "prompts";
4
+
5
+ import { Component } from "../types";
6
+ import { getUIConfig } from "../utils/config";
7
+ import { fetchComponents } from "../utils/fetchComponents";
8
+ import { fileExists } from "../utils/fileExists";
9
+ import { logger } from "../utils/logger";
10
+ import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
11
+ import { writeFile } from "../utils/writeFile";
12
+
13
+ export const runUpdateCommand = async (componentNames: string[]) => {
14
+ const uiConfig = await getUIConfig();
15
+ const allComponents = await fetchComponents();
16
+ const currentDirectory = process.cwd();
17
+
18
+ let toUpdate = componentNames;
19
+
20
+ if (toUpdate.length === 0) {
21
+ const installed = (
22
+ await Promise.all(
23
+ allComponents.map(async (c) => {
24
+ if (!c.files || c.files.length === 0) return null;
25
+ const targetPath = path.join(
26
+ currentDirectory,
27
+ uiConfig.componentsLocation,
28
+ c.files[0].fileName
29
+ );
30
+ return (await fileExists(targetPath)) ? c : null;
31
+ })
32
+ )
33
+ ).filter(Boolean) as Component[];
34
+
35
+ if (installed.length === 0) {
36
+ logger.info("No installed components found.");
37
+ return;
38
+ }
39
+
40
+ const { selected } = await prompts({
41
+ type: "autocompleteMultiselect",
42
+ name: "selected",
43
+ message: "Select components to update",
44
+ choices: installed.map((c) => ({ title: c.name, value: c.value })),
45
+ });
46
+
47
+ if (!selected || selected.length === 0) {
48
+ logger.info("No components selected. Exiting...");
49
+ process.exit(0);
50
+ }
51
+ toUpdate = selected;
52
+ }
53
+
54
+ const notFound = toUpdate.filter(
55
+ (name) => !allComponents.find((c) => c.value.toLowerCase() === name.toLowerCase())
56
+ );
57
+ if (notFound.length > 0) {
58
+ logger.warn(`Component(s) not found in registry: ${notFound.join(", ")}`);
59
+ }
60
+
61
+ const found = toUpdate
62
+ .map((name) => allComponents.find((c) => c.value.toLowerCase() === name.toLowerCase()))
63
+ .filter(Boolean) as Component[];
64
+
65
+ for (const component of found) {
66
+ for (const file of component.files) {
67
+ const targetPath = path.join(currentDirectory, uiConfig.componentsLocation, file.fileName);
68
+ await writeFile(targetPath, file.fileContent);
69
+ logger.success(`Updated: ${file.fileName}`);
70
+ }
71
+ }
72
+
73
+ printFancyBoxMessage("Updated!", undefined, { box: { title: "Components Updated" } });
74
+ };
75
+
76
+ export const update = new Command()
77
+ .command("update")
78
+ .name("update")
79
+ .description("Re-fetch and overwrite installed components with the latest version.")
80
+ .argument("[componentNames...]", "Components to update.")
81
+ .action(runUpdateCommand);
package/src/index.ts CHANGED
@@ -5,10 +5,13 @@ import { version } from "../package.json";
5
5
  import { add } from "./commands/add";
6
6
  import { block } from "./commands/block";
7
7
  import { init } from "./commands/init";
8
+ import { list } from "./commands/list";
8
9
  import { addPrettier } from "./commands/prettier";
9
10
  import { prose } from "./commands/prose";
11
+ import { remove } from "./commands/remove";
10
12
  import { addShortcuts } from "./commands/shortcuts";
11
13
  import { theme } from "./commands/theme";
14
+ import { update } from "./commands/update";
12
15
  import { printFancyBoxMessage } from "./utils/printFancyBoxMessage";
13
16
 
14
17
  process.on("SIGINT", () => process.exit(0));
@@ -31,6 +34,9 @@ program
31
34
  .addCommand(prose)
32
35
  .addCommand(block)
33
36
  .addCommand(theme)
37
+ .addCommand(list)
38
+ .addCommand(update)
39
+ .addCommand(remove)
34
40
  .addCommand(addShortcuts)
35
41
  .addCommand(addPrettier);
36
42
 
package/src/types.ts CHANGED
@@ -83,6 +83,18 @@ export type AddCommand = {
83
83
  * @default false
84
84
  */
85
85
  all?: boolean;
86
+ /**
87
+ * Skip loading the ui-thing config file and use auto-detected defaults instead.
88
+ * Useful for adding components to a project that has no ui-thing.config.ts.
89
+ *
90
+ * @default false
91
+ */
92
+ skipConfig?: boolean;
93
+ /**
94
+ * Override the package manager when using --skip-config.
95
+ * If not set and --skip-config is active, the user will be prompted.
96
+ */
97
+ packageManager?: string;
86
98
  };
87
99
 
88
100
  export type TemplateFile = {
@@ -4,7 +4,6 @@ import fse from "fs-extra";
4
4
  import _ from "lodash";
5
5
  import { loadFile, writeFile } from "magicast";
6
6
  import { addNuxtModule } from "magicast/helpers";
7
- import prompts from "prompts";
8
7
 
9
8
  import { InitOptions, UIConfig } from "../types";
10
9
  import { DEFAULT_CONFIG, DEFAULT_CONFIG_NUXT4, UI_CONFIG_FILENAME } from "./constants";
@@ -32,23 +31,17 @@ export const getUIConfig = async (options?: InitOptions): Promise<UIConfig> => {
32
31
 
33
32
  await fse.writeFile(UI_CONFIG_FILENAME, `export default ${JSON.stringify(uiConfig, null, 2)}`);
34
33
 
35
- // Handle pnpm special case
34
+ // Handle pnpm special case: append required lines without destroying existing content
36
35
  if (uiConfig.packageManager === "pnpm") {
37
- const npmrcExists = fse.existsSync(".npmrc");
38
- let shouldWrite = true;
39
-
40
- if (npmrcExists) {
41
- const { confirmCreateNpmrc } = await prompts({
42
- type: "confirm",
43
- name: "confirmCreateNpmrc",
44
- message: "A .npmrc file already exists. Overwrite it?",
45
- initial: false,
46
- });
47
- shouldWrite = confirmCreateNpmrc;
36
+ const requiredLines = ["shamefully-hoist=true", "strict-peer-dependencies=false"];
37
+ let existing = "";
38
+ if (fse.existsSync(".npmrc")) {
39
+ existing = await fse.readFile(".npmrc", "utf-8");
48
40
  }
49
-
50
- if (shouldWrite) {
51
- await fse.writeFile(".npmrc", "shamefully-hoist=true\nstrict-peer-dependencies=false\n");
41
+ const linesToAdd = requiredLines.filter((line) => !existing.includes(line));
42
+ if (linesToAdd.length > 0) {
43
+ const separator = existing && !existing.endsWith("\n") ? "\n" : "";
44
+ await fse.writeFile(".npmrc", existing + separator + linesToAdd.join("\n") + "\n");
52
45
  }
53
46
  }
54
47
  } else {
@@ -87,6 +87,7 @@ export const PACKAGE_MANAGER_CHOICES = [
87
87
  { title: "Yarn", value: "yarn" },
88
88
  { title: "Pnpm", value: "pnpm" },
89
89
  { title: "Bun", value: "bun" },
90
+ { title: "Deno", value: "deno" },
90
91
  ];
91
92
 
92
93
  /**
@@ -1,19 +1,21 @@
1
1
  import axios from "axios";
2
+ import { consola } from "consola";
2
3
  import dotenv from "dotenv";
3
4
  import ora from "ora";
4
5
 
5
6
  dotenv.config();
6
7
 
7
- /**
8
- * Fetch block categories from UI Thing API.
9
- */
10
- export const fetchBlockCategories = async () => {
8
+ export const fetchBlockCategories = async (): Promise<string[]> => {
11
9
  const spinner = ora("Fetching block categories...").start();
12
-
13
- const { data } = await axios.get<string[]>(
14
- process.env.BLOCK_CATEGORIES_API || "https://uithing.com/api/blocks/categories"
15
- );
16
-
17
- spinner.succeed("Block categories fetched.");
18
- return data;
10
+ try {
11
+ const { data } = await axios.get<string[]>(
12
+ process.env.BLOCK_CATEGORIES_API || "https://uithing.com/api/blocks/categories"
13
+ );
14
+ spinner.succeed("Block categories fetched.");
15
+ return data;
16
+ } catch {
17
+ spinner.fail("Failed to fetch block categories.");
18
+ consola.error("Could not reach the UI Thing API. Check your network connection.");
19
+ process.exit(1);
20
+ }
19
21
  };