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.
- package/.claude/settings.local.json +5 -0
- package/.github/workflows/main.yml +7 -4
- package/.github/workflows/test.yml +7 -4
- package/.prettierrc +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +208 -21
- package/bun.lock +171 -134
- package/dist/index.js +28 -25
- package/dist/index.js.map +1 -1
- package/package.json +15 -14
- package/src/commands/add.ts +41 -12
- package/src/commands/block.ts +5 -3
- package/src/commands/list.ts +48 -0
- package/src/commands/prose.ts +7 -3
- package/src/commands/remove.ts +109 -0
- package/src/commands/theme.ts +5 -14
- package/src/commands/update.ts +81 -0
- package/src/index.ts +6 -0
- package/src/types.ts +12 -0
- package/src/utils/config.ts +9 -16
- package/src/utils/constants.ts +1 -0
- package/src/utils/fetchBlockCategories.ts +13 -11
- package/src/utils/fetchBlocks.ts +13 -11
- package/src/utils/fetchComponents.ts +13 -11
- package/src/utils/fetchProseComponents.ts +13 -11
- package/src/utils/installPackages.ts +30 -4
- package/src/utils/installValidator.ts +3 -3
- package/src/utils/logger.ts +9 -0
- package/src/utils/uiConfigPrompt.ts +3 -6
- package/tests/commands/add.test.ts +136 -0
- package/tests/commands/list.test.ts +111 -0
- package/tests/commands/remove.test.ts +151 -0
- package/tests/commands/update.test.ts +127 -0
- package/tests/utils/fetchBlockCategories.test.ts +14 -2
- package/tests/utils/fetchBlocks.test.ts +14 -2
- package/tests/utils/fetchComponents.test.ts +10 -4
- package/tests/utils/fetchProseComponents.test.ts +14 -2
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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": "^
|
|
75
|
+
"@types/node": "^26.0.0",
|
|
76
76
|
"@types/prompts": "^2.4.9",
|
|
77
|
-
"@vitest/coverage-v8": "^4.1.
|
|
78
|
-
"@vitest/ui": "^4.1.
|
|
77
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
78
|
+
"@vitest/ui": "^4.1.9",
|
|
79
79
|
"axios-mock-adapter": "^2.1.0",
|
|
80
|
-
"eslint": "^10.
|
|
81
|
-
"globals": "^17.
|
|
80
|
+
"eslint": "^10.5.0",
|
|
81
|
+
"globals": "^17.7.0",
|
|
82
82
|
"husky": "^9.1.7",
|
|
83
83
|
"jiti": "^2.7.0",
|
|
84
|
-
"knip": "^
|
|
85
|
-
"lint-staged": "^17.0.
|
|
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.
|
|
90
|
-
"vitest": "^4.1.
|
|
90
|
+
"typescript-eslint": "^8.62.0",
|
|
91
|
+
"vitest": "^4.1.9"
|
|
91
92
|
},
|
|
92
|
-
"packageManager": "bun@1.3.
|
|
93
|
+
"packageManager": "bun@1.3.14",
|
|
93
94
|
"publishConfig": {
|
|
94
95
|
"access": "public"
|
|
95
96
|
}
|
package/src/commands/add.ts
CHANGED
|
@@ -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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
|
package/src/commands/block.ts
CHANGED
|
@@ -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.
|
|
63
|
-
process.exit(
|
|
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
|
|
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);
|
package/src/commands/prose.ts
CHANGED
|
@@ -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.
|
|
75
|
-
process.exit(
|
|
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
|
|
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);
|
package/src/commands/theme.ts
CHANGED
|
@@ -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
|
-
|
|
37
|
-
process.exit(
|
|
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
|
-
|
|
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
|
-
|
|
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 = {
|
package/src/utils/config.ts
CHANGED
|
@@ -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
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
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 (
|
|
51
|
-
|
|
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 {
|
package/src/utils/constants.ts
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
};
|