ui-thing 0.2.8 → 0.3.0
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/.prettierrc +1 -1
- package/CHANGELOG.md +58 -0
- package/bun.lock +978 -0
- package/package.json +29 -27
- package/src/commands/add.ts +9 -5
- 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/templates/css.ts +5 -0
- package/src/templates/vs-code.ts +0 -8
- package/src/utils/config.ts +11 -17
- 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/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/tsup.config.ts +5 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1343
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-thing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI used to add Nuxt components to a project",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -30,65 +30,67 @@
|
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsup",
|
|
33
|
-
"clean": "rm -rf dist coverage node_modules package-lock.json &&
|
|
33
|
+
"clean": "rm -rf dist coverage node_modules bun.lock package-lock.json && bun install",
|
|
34
34
|
"coverage": "vitest run --coverage",
|
|
35
35
|
"dev": "tsup --watch",
|
|
36
36
|
"format": "npx prettier --write .",
|
|
37
37
|
"knip": "knip",
|
|
38
38
|
"knip:fix": "knip --fix",
|
|
39
39
|
"lint": "eslint src",
|
|
40
|
-
"lint-staged": "lint-staged",
|
|
41
40
|
"lint:fix": "eslint src --fix",
|
|
41
|
+
"lint-staged": "lint-staged",
|
|
42
42
|
"prepare": "husky",
|
|
43
|
-
"release": "
|
|
43
|
+
"release": "bun run build && npx changelogen@latest --release && npm publish && git push --follow-tags",
|
|
44
44
|
"start": "node dist/index.js",
|
|
45
45
|
"test": "vitest"
|
|
46
46
|
},
|
|
47
47
|
"lint-staged": {
|
|
48
|
-
"
|
|
48
|
+
"**/*.{ts,js}": [
|
|
49
49
|
"prettier --write",
|
|
50
50
|
"eslint --fix"
|
|
51
51
|
]
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"axios": "^1.
|
|
54
|
+
"axios": "^1.18.1",
|
|
55
55
|
"boxen": "^8.0.1",
|
|
56
|
-
"c12": "^3.3.
|
|
57
|
-
"commander": "^
|
|
56
|
+
"c12": "^3.3.4",
|
|
57
|
+
"commander": "^15.0.0",
|
|
58
58
|
"consola": "^3.4.2",
|
|
59
|
-
"dotenv": "^17.
|
|
60
|
-
"es-toolkit": "^1.
|
|
59
|
+
"dotenv": "^17.4.2",
|
|
60
|
+
"es-toolkit": "^1.48.1",
|
|
61
61
|
"execa": "^9.6.1",
|
|
62
62
|
"figlet": "^1.11.0",
|
|
63
|
-
"fs-extra": "^11.3.
|
|
63
|
+
"fs-extra": "^11.3.5",
|
|
64
64
|
"kleur": "^4.1.5",
|
|
65
|
-
"lodash": "^4.
|
|
66
|
-
"magicast": "^0.5.
|
|
67
|
-
"ora": "^9.
|
|
65
|
+
"lodash": "^4.18.1",
|
|
66
|
+
"magicast": "^0.5.3",
|
|
67
|
+
"ora": "^9.4.1",
|
|
68
68
|
"prompts": "^2.4.2"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@eslint/js": "^
|
|
71
|
+
"@eslint/js": "^10.0.1",
|
|
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": "^
|
|
81
|
-
"globals": "^17.
|
|
80
|
+
"eslint": "^10.5.0",
|
|
81
|
+
"globals": "^17.7.0",
|
|
82
82
|
"husky": "^9.1.7",
|
|
83
|
-
"jiti": "^2.
|
|
84
|
-
"knip": "^
|
|
85
|
-
"lint-staged": "^
|
|
86
|
-
"prettier": "^3.8.
|
|
83
|
+
"jiti": "^2.7.0",
|
|
84
|
+
"knip": "^6.18.0",
|
|
85
|
+
"lint-staged": "^17.0.8",
|
|
86
|
+
"prettier": "^3.8.4",
|
|
87
|
+
"prettier-plugin-packagejson": "^3.0.2",
|
|
87
88
|
"tsup": "^8.5.1",
|
|
88
|
-
"typescript": "^
|
|
89
|
-
"typescript-eslint": "^8.
|
|
90
|
-
"vitest": "^4.1.
|
|
89
|
+
"typescript": "^6.0.3",
|
|
90
|
+
"typescript-eslint": "^8.62.0",
|
|
91
|
+
"vitest": "^4.1.9"
|
|
91
92
|
},
|
|
93
|
+
"packageManager": "bun@1.3.14",
|
|
92
94
|
"publishConfig": {
|
|
93
95
|
"access": "public"
|
|
94
96
|
}
|
package/src/commands/add.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { fetchComponents } from "../utils/fetchComponents";
|
|
|
13
13
|
import { fileExists } from "../utils/fileExists";
|
|
14
14
|
import { installPackages } from "../utils/installPackages";
|
|
15
15
|
import { installValidator } from "../utils/installValidator";
|
|
16
|
+
import { logger } from "../utils/logger";
|
|
16
17
|
import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
|
|
17
18
|
import { promptUserForComponents } from "../utils/promptForComponents";
|
|
18
19
|
import { writeFile } from "../utils/writeFile";
|
|
@@ -84,8 +85,8 @@ const runAddCommand = async (components: string[], options: AddCommand) => {
|
|
|
84
85
|
uiConfig = await getUIConfig({ force: true });
|
|
85
86
|
}
|
|
86
87
|
if (_.isEmpty(uiConfig)) {
|
|
87
|
-
consola.
|
|
88
|
-
process.exit(
|
|
88
|
+
consola.error("Config file not set. Exiting...");
|
|
89
|
+
process.exit(1);
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
// Step 2 — Fetch all available components
|
|
@@ -206,11 +207,14 @@ const runAddCommand = async (components: string[], options: AddCommand) => {
|
|
|
206
207
|
{ box: { title: "Components Added" } }
|
|
207
208
|
);
|
|
208
209
|
|
|
210
|
+
found
|
|
211
|
+
.filter((c) => c.docsPath)
|
|
212
|
+
.forEach((c) => consola.info(`Docs: https://uithing.com${c.docsPath}`));
|
|
213
|
+
|
|
209
214
|
const instructions = _.compact(found.flatMap((c) => c.instructions));
|
|
210
215
|
if (instructions.length > 0) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
instructions.forEach((i) => console.log(`${kleur.cyan("-")} ${i}`));
|
|
216
|
+
logger.info("Instructions:");
|
|
217
|
+
instructions.forEach((i) => logger.info(`- ${i}`));
|
|
214
218
|
}
|
|
215
219
|
};
|
|
216
220
|
|
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/templates/css.ts
CHANGED
package/src/templates/vs-code.ts
CHANGED
|
@@ -8,7 +8,6 @@ export const VS_CODE_RECOMMENDATIONS = {
|
|
|
8
8
|
"antfu.iconify",
|
|
9
9
|
"formulahendry.auto-close-tag",
|
|
10
10
|
"formulahendry.auto-rename-tag",
|
|
11
|
-
"prettier.prettier-vscode",
|
|
12
11
|
],
|
|
13
12
|
};
|
|
14
13
|
|
|
@@ -21,13 +20,6 @@ export const VS_CODE_SETTINGS = {
|
|
|
21
20
|
"files.associations": { "*.css": "tailwindcss" },
|
|
22
21
|
"tailwindCSS.classFunctions": ["tw", "clsx", "tw\\.[a-z-]+"],
|
|
23
22
|
"tailwindCSS.experimental.classRegex": [
|
|
24
|
-
[
|
|
25
|
-
"tv\\(([^)(]*(?:\\([^)(]*(?:\\([^)(]*(?:\\([^)(]*\\)[^)(]*)*\\)[^)(]*)*\\)[^)(]*)*)\\)",
|
|
26
|
-
'"(.*?)"',
|
|
27
|
-
],
|
|
28
|
-
"tw`(.*?)`",
|
|
29
|
-
"tw\\('(.*?)'\\)",
|
|
30
|
-
"tw\\(\\s*('(.*?)'|\"(.*?)\")\\s*\\)",
|
|
31
23
|
[
|
|
32
24
|
"\\btv\\(\\s*((?:\"(?:\\\\.|[^\"\\\\])*\"|'(?:\\\\.|[^'\\\\])*'|`(?:\\\\.|[^`\\\\])*`|[^)]*)*)\\)",
|
|
33
25
|
"[\"']([^\"']*)[\"']",
|
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";
|
|
@@ -18,7 +17,8 @@ const currentDir = process.cwd();
|
|
|
18
17
|
*/
|
|
19
18
|
export const getUIConfig = async (options?: InitOptions): Promise<UIConfig> => {
|
|
20
19
|
const configExists = fse.existsSync(UI_CONFIG_FILENAME);
|
|
21
|
-
|
|
20
|
+
// eslint-disable-next-line no-useless-assignment
|
|
21
|
+
let uiConfig = {} as UIConfig;
|
|
22
22
|
const nuxtVersion = Number(options?.nuxtVersion) || detectNuxtVersion();
|
|
23
23
|
|
|
24
24
|
// Force creation or first-time setup
|
|
@@ -31,23 +31,17 @@ export const getUIConfig = async (options?: InitOptions): Promise<UIConfig> => {
|
|
|
31
31
|
|
|
32
32
|
await fse.writeFile(UI_CONFIG_FILENAME, `export default ${JSON.stringify(uiConfig, null, 2)}`);
|
|
33
33
|
|
|
34
|
-
// Handle pnpm special case
|
|
34
|
+
// Handle pnpm special case: append required lines without destroying existing content
|
|
35
35
|
if (uiConfig.packageManager === "pnpm") {
|
|
36
|
-
const
|
|
37
|
-
let
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const { confirmCreateNpmrc } = await prompts({
|
|
41
|
-
type: "confirm",
|
|
42
|
-
name: "confirmCreateNpmrc",
|
|
43
|
-
message: "A .npmrc file already exists. Overwrite it?",
|
|
44
|
-
initial: false,
|
|
45
|
-
});
|
|
46
|
-
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");
|
|
47
40
|
}
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
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");
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
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
|
};
|