ui-thing 0.2.6 → 0.2.7
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/.github/workflows/test.yml +4 -4
- package/CHANGELOG.md +35 -0
- package/dist/index.js +24 -28
- package/dist/index.js.map +1 -1
- package/package.json +22 -21
- package/src/commands/block.ts +165 -0
- package/src/commands/prose.ts +200 -0
- package/src/index.ts +4 -0
- package/src/templates/prettier.ts +1 -1
- package/src/templates/shortcuts.ts +1 -7
- package/src/templates/vs-code.ts +10 -2
- package/src/types.ts +52 -7
- package/src/utils/fetchBlockCategories.ts +19 -0
- package/src/utils/fetchBlocks.ts +21 -0
- package/src/utils/fetchProseComponents.ts +21 -0
- package/src/utils/promptForComponents.ts +7 -4
- package/tests/utils/addPrettierConfig.test.ts +10 -17
- package/tests/utils/compareUIConfig.test.ts +3 -3
- package/tests/utils/constants.test.ts +136 -0
- package/tests/utils/detectNuxtVersion.test.ts +97 -0
- package/tests/utils/fetchBlockCategories.test.ts +59 -0
- package/tests/utils/fetchBlocks.test.ts +59 -0
- package/tests/utils/fetchComponents.test.ts +92 -0
- package/tests/utils/fetchProseComponents.test.ts +62 -0
- package/tests/utils/installPackages.test.ts +114 -0
- package/tests/utils/printFancyBoxMessage.test.ts +66 -0
- package/tests/utils/promptForComponents.test.ts +94 -0
- package/tests/utils/writeFile.test.ts +56 -0
- package/vite.config.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ui-thing",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "CLI used to add Nuxt components to a project",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -50,42 +50,43 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"axios": "^1.13.
|
|
53
|
+
"axios": "^1.13.2",
|
|
54
54
|
"boxen": "^8.0.1",
|
|
55
|
-
"c12": "^3.3.
|
|
55
|
+
"c12": "^3.3.3",
|
|
56
56
|
"commander": "^14.0.2",
|
|
57
57
|
"consola": "^3.4.2",
|
|
58
58
|
"dotenv": "^17.2.3",
|
|
59
|
-
"es-toolkit": "^1.
|
|
60
|
-
"execa": "^9.6.
|
|
61
|
-
"figlet": "^1.9.
|
|
62
|
-
"fs-extra": "^11.3.
|
|
59
|
+
"es-toolkit": "^1.43.0",
|
|
60
|
+
"execa": "^9.6.1",
|
|
61
|
+
"figlet": "^1.9.4",
|
|
62
|
+
"fs-extra": "^11.3.3",
|
|
63
63
|
"kleur": "^4.1.5",
|
|
64
64
|
"lodash": "^4.17.21",
|
|
65
|
-
"magicast": "^0.
|
|
65
|
+
"magicast": "^0.5.1",
|
|
66
66
|
"ora": "^9.0.0",
|
|
67
67
|
"prompts": "^2.4.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@eslint/js": "^9.
|
|
70
|
+
"@eslint/js": "^9.39.2",
|
|
71
71
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
72
|
-
"@types/figlet": "^1.7.0",
|
|
73
72
|
"@types/fs-extra": "^11.0.4",
|
|
74
|
-
"@types/lodash": "^4.17.
|
|
75
|
-
"@types/node": "^24.
|
|
73
|
+
"@types/lodash": "^4.17.21",
|
|
74
|
+
"@types/node": "^24.10.4",
|
|
76
75
|
"@types/prompts": "^2.4.9",
|
|
77
|
-
"@vitest/coverage-v8": "^4.0.
|
|
78
|
-
"
|
|
79
|
-
"
|
|
76
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
77
|
+
"@vitest/ui": "^4.0.16",
|
|
78
|
+
"axios-mock-adapter": "^2.1.0",
|
|
79
|
+
"eslint": "^9.39.2",
|
|
80
|
+
"globals": "^16.5.0",
|
|
80
81
|
"husky": "^9.1.7",
|
|
81
82
|
"jiti": "^2.6.1",
|
|
82
|
-
"knip": "^5.
|
|
83
|
-
"lint-staged": "^16.2.
|
|
84
|
-
"prettier": "^3.
|
|
85
|
-
"tsup": "^8.5.
|
|
83
|
+
"knip": "^5.78.0",
|
|
84
|
+
"lint-staged": "^16.2.7",
|
|
85
|
+
"prettier": "^3.7.4",
|
|
86
|
+
"tsup": "^8.5.1",
|
|
86
87
|
"typescript": "^5.9.3",
|
|
87
|
-
"typescript-eslint": "^8.
|
|
88
|
-
"vitest": "^4.0.
|
|
88
|
+
"typescript-eslint": "^8.51.0",
|
|
89
|
+
"vitest": "^4.0.16"
|
|
89
90
|
},
|
|
90
91
|
"publishConfig": {
|
|
91
92
|
"access": "public"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
|
|
9
|
+
import { AddCommand, BlockComponent } from "../types";
|
|
10
|
+
import { compareUIConfig } from "../utils/compareUIConfig";
|
|
11
|
+
import { getUIConfig } from "../utils/config";
|
|
12
|
+
import { fetchBlockCategories } from "../utils/fetchBlockCategories";
|
|
13
|
+
import { fetchBlocks } from "../utils/fetchBlocks";
|
|
14
|
+
import { fileExists } from "../utils/fileExists";
|
|
15
|
+
import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
|
|
16
|
+
import { writeFile } from "../utils/writeFile";
|
|
17
|
+
|
|
18
|
+
type BlockOptions = AddCommand & { category?: string };
|
|
19
|
+
|
|
20
|
+
let allBlocks: BlockComponent[] = [];
|
|
21
|
+
const currentDirectory = process.cwd();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the default blocks directory based on Nuxt version from config
|
|
25
|
+
*/
|
|
26
|
+
function getDefaultBlocksDir(uiConfig: any): string {
|
|
27
|
+
const isNuxt4 = uiConfig.componentsLocation?.startsWith("app/");
|
|
28
|
+
return isNuxt4 ? "app/components/Blocks" : "components/Blocks";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function safeWriteFile(
|
|
32
|
+
targetPath: string,
|
|
33
|
+
content: string,
|
|
34
|
+
forceOverwrite: boolean,
|
|
35
|
+
promptMessage: string
|
|
36
|
+
) {
|
|
37
|
+
const exists = await fileExists(targetPath);
|
|
38
|
+
|
|
39
|
+
if (exists && !forceOverwrite) {
|
|
40
|
+
const { value: overwrite } = await prompts({
|
|
41
|
+
type: "confirm",
|
|
42
|
+
name: "value",
|
|
43
|
+
message: promptMessage,
|
|
44
|
+
initial: false,
|
|
45
|
+
});
|
|
46
|
+
if (!overwrite) {
|
|
47
|
+
consola.info(`Skipped: ${kleur.cyan(path.basename(targetPath))}`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await writeFile(targetPath, content);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const runBlockCommand = async (components: string[], options: BlockOptions) => {
|
|
57
|
+
let uiConfig = await getUIConfig();
|
|
58
|
+
if (!(await compareUIConfig())) {
|
|
59
|
+
uiConfig = await getUIConfig({ force: true });
|
|
60
|
+
}
|
|
61
|
+
if (_.isEmpty(uiConfig)) {
|
|
62
|
+
consola.info("Config file not set. Exiting...");
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step 1: Fetch categories
|
|
67
|
+
const categories = await fetchBlockCategories();
|
|
68
|
+
|
|
69
|
+
// Step 2: Ask user to select category
|
|
70
|
+
let selectedCategory = options.category;
|
|
71
|
+
if (!selectedCategory && categories?.length) {
|
|
72
|
+
const { category } = await prompts({
|
|
73
|
+
type: "select",
|
|
74
|
+
name: "category",
|
|
75
|
+
message: "Choose a block category",
|
|
76
|
+
choices: [{ title: "All", value: "all" }, ...categories.map((c) => ({ title: c, value: c }))],
|
|
77
|
+
initial: 0,
|
|
78
|
+
});
|
|
79
|
+
selectedCategory = category;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Step 3: Fetch all blocks
|
|
83
|
+
allBlocks = await fetchBlocks();
|
|
84
|
+
|
|
85
|
+
// Step 4: Filter blocks by category if not "all"
|
|
86
|
+
let blocksToSelect = allBlocks;
|
|
87
|
+
if (selectedCategory && selectedCategory !== "all") {
|
|
88
|
+
blocksToSelect = allBlocks.filter((b) => b.category === selectedCategory);
|
|
89
|
+
if (blocksToSelect.length === 0) {
|
|
90
|
+
consola.warn(
|
|
91
|
+
`No blocks found for category ${kleur.cyan(selectedCategory)}. Falling back to all.`
|
|
92
|
+
);
|
|
93
|
+
blocksToSelect = allBlocks;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Step 5: Let user select blocks via autocomplete multi-select
|
|
98
|
+
const { selectedBlocks } = await prompts({
|
|
99
|
+
type: "autocompleteMultiselect",
|
|
100
|
+
name: "selectedBlocks",
|
|
101
|
+
message: "Select the blocks you want to add",
|
|
102
|
+
choices: blocksToSelect.map((b) => ({ title: b.name, value: b })),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!selectedBlocks || selectedBlocks.length === 0) {
|
|
106
|
+
consola.info("No blocks selected. Exiting...");
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const found: BlockComponent[] = selectedBlocks;
|
|
111
|
+
|
|
112
|
+
// Step 6: Ask user where to add blocks (with default based on Nuxt version)
|
|
113
|
+
const defaultBlocksDir = getDefaultBlocksDir(uiConfig);
|
|
114
|
+
const { blocksDir } = await prompts({
|
|
115
|
+
type: "text",
|
|
116
|
+
name: "blocksDir",
|
|
117
|
+
message: "Where should we add the blocks?",
|
|
118
|
+
initial: defaultBlocksDir,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const blocksDirPath = blocksDir || defaultBlocksDir;
|
|
122
|
+
|
|
123
|
+
// Step 7: Write block files
|
|
124
|
+
for (const block of found) {
|
|
125
|
+
// Create full path: blocksDirPath + block.path + block.fileName
|
|
126
|
+
const fullPath = path.join(blocksDirPath, block.path);
|
|
127
|
+
const targetPath = path.join(currentDirectory, fullPath);
|
|
128
|
+
|
|
129
|
+
const fileWritten = await safeWriteFile(
|
|
130
|
+
targetPath,
|
|
131
|
+
block.file,
|
|
132
|
+
uiConfig.force,
|
|
133
|
+
`The block file ${kleur.bold(block.fileName)} already exists. Overwrite?`
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (!fileWritten) continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Step 8: Handle components if present
|
|
140
|
+
const componentsToAdd = _.uniq(found.flatMap((b) => b.components || []));
|
|
141
|
+
if (componentsToAdd.length > 0) {
|
|
142
|
+
consola.info(`Adding ${componentsToAdd.length} component(s) required by blocks...`);
|
|
143
|
+
const result = spawnSync("npx", ["ui-thing@latest", "add", ...componentsToAdd], {
|
|
144
|
+
cwd: currentDirectory,
|
|
145
|
+
stdio: "inherit",
|
|
146
|
+
});
|
|
147
|
+
if (result.error) {
|
|
148
|
+
consola.error("Failed to add components:", result.error.message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Step 9: Success message
|
|
153
|
+
printFancyBoxMessage(
|
|
154
|
+
"Blocks added!",
|
|
155
|
+
`Run the ${kleur.cyan("ui-thing@latest --help")} command to learn more.\n`,
|
|
156
|
+
{ box: { title: "Blocks Added" } }
|
|
157
|
+
);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const block = new Command()
|
|
161
|
+
.name("block")
|
|
162
|
+
.command("block")
|
|
163
|
+
.description("Add UI Thing blocks to your project.")
|
|
164
|
+
.option("-c --category <category>", "Filter blocks by category before selection")
|
|
165
|
+
.action(runBlockCommand);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { consola } from "consola";
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
import _ from "lodash";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
|
|
9
|
+
import { AddCommand, ProseComponent, TemplateFile } from "../types";
|
|
10
|
+
import { compareUIConfig } from "../utils/compareUIConfig";
|
|
11
|
+
import { addModuleToConfig, getUIConfig } from "../utils/config";
|
|
12
|
+
import { fetchProseComponents } from "../utils/fetchProseComponents";
|
|
13
|
+
import { fileExists } from "../utils/fileExists";
|
|
14
|
+
import { installPackages } from "../utils/installPackages";
|
|
15
|
+
import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
|
|
16
|
+
import { promptUserForComponents } from "../utils/promptForComponents";
|
|
17
|
+
import { writeFile } from "../utils/writeFile";
|
|
18
|
+
|
|
19
|
+
let allProse: ProseComponent[] = [];
|
|
20
|
+
const currentDirectory = process.cwd();
|
|
21
|
+
|
|
22
|
+
const findProse = (name: string) =>
|
|
23
|
+
allProse.find((c) => c.value.toLowerCase() === name.toLowerCase());
|
|
24
|
+
|
|
25
|
+
async function safeWriteFile(
|
|
26
|
+
targetPath: string,
|
|
27
|
+
content: string,
|
|
28
|
+
forceOverwrite: boolean,
|
|
29
|
+
promptMessage: string
|
|
30
|
+
) {
|
|
31
|
+
const exists = await fileExists(targetPath);
|
|
32
|
+
|
|
33
|
+
if (exists && !forceOverwrite) {
|
|
34
|
+
const { value: overwrite } = await prompts({
|
|
35
|
+
type: "confirm",
|
|
36
|
+
name: "value",
|
|
37
|
+
message: promptMessage,
|
|
38
|
+
initial: false,
|
|
39
|
+
});
|
|
40
|
+
if (!overwrite) {
|
|
41
|
+
consola.info(`Skipped: ${kleur.cyan(path.basename(targetPath))}`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
await writeFile(targetPath, content);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function writeCategoryFiles(
|
|
51
|
+
category: string,
|
|
52
|
+
items: TemplateFile[] | undefined,
|
|
53
|
+
baseDir: string,
|
|
54
|
+
forceOverwrite: boolean
|
|
55
|
+
) {
|
|
56
|
+
if (!items || items.length === 0) return;
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const targetPath = path.join(currentDirectory, baseDir, item.fileName);
|
|
59
|
+
await safeWriteFile(
|
|
60
|
+
targetPath,
|
|
61
|
+
item.fileContent,
|
|
62
|
+
forceOverwrite,
|
|
63
|
+
`The ${category} file ${kleur.bold(item.fileName)} already exists. Overwrite?`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const runProseCommand = async (components: string[], options: AddCommand) => {
|
|
69
|
+
let uiConfig = await getUIConfig();
|
|
70
|
+
if (!(await compareUIConfig())) {
|
|
71
|
+
uiConfig = await getUIConfig({ force: true });
|
|
72
|
+
}
|
|
73
|
+
if (_.isEmpty(uiConfig)) {
|
|
74
|
+
consola.info("Config file not set. Exiting...");
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
allProse = await fetchProseComponents();
|
|
79
|
+
|
|
80
|
+
let componentNames = components;
|
|
81
|
+
if (componentNames.length === 0) {
|
|
82
|
+
const response = await promptUserForComponents(options.all, allProse);
|
|
83
|
+
if (!response || response.length === 0) {
|
|
84
|
+
consola.info("No components selected. Exiting...");
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
componentNames = response;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const notFound = componentNames.filter((name) => !findProse(name));
|
|
91
|
+
if (notFound.length > 0) {
|
|
92
|
+
consola.error(`Not found: ${kleur.bgRed(notFound.join(", "))}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const found: ProseComponent[] = componentNames
|
|
96
|
+
.map((name) => findProse(name))
|
|
97
|
+
.filter(Boolean) as ProseComponent[];
|
|
98
|
+
|
|
99
|
+
// Handle prose dependencies
|
|
100
|
+
for (const comp of [...found]) {
|
|
101
|
+
if (comp.prose && comp.prose.length > 0) {
|
|
102
|
+
comp.prose.forEach((proseName) => {
|
|
103
|
+
if (!found.find((c) => c.value === proseName)) {
|
|
104
|
+
const proseComp = findProse(proseName);
|
|
105
|
+
if (proseComp) {
|
|
106
|
+
found.push(proseComp);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const component of found) {
|
|
114
|
+
const componentFile = component.file;
|
|
115
|
+
let dirPath = uiConfig.componentsLocation;
|
|
116
|
+
let filePath = path.join(currentDirectory, dirPath, componentFile.fileName);
|
|
117
|
+
|
|
118
|
+
if (!uiConfig.useDefaultFilename) {
|
|
119
|
+
const { value: newDir } = await prompts({
|
|
120
|
+
type: "text",
|
|
121
|
+
name: "value",
|
|
122
|
+
message: `Where should we add the file ${kleur.cyan(componentFile.fileName)}?`,
|
|
123
|
+
initial: dirPath,
|
|
124
|
+
});
|
|
125
|
+
if (newDir) {
|
|
126
|
+
dirPath = newDir;
|
|
127
|
+
filePath = path.join(currentDirectory, dirPath, componentFile.fileName);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const fileWritten = await safeWriteFile(
|
|
132
|
+
filePath,
|
|
133
|
+
componentFile.fileContent,
|
|
134
|
+
uiConfig.force,
|
|
135
|
+
`The file ${kleur.bold(componentFile.fileName)} already exists. Overwrite?`
|
|
136
|
+
);
|
|
137
|
+
if (!fileWritten) continue;
|
|
138
|
+
|
|
139
|
+
await writeCategoryFiles("utils", component.utils, uiConfig.utilsLocation, uiConfig.force);
|
|
140
|
+
await writeCategoryFiles(
|
|
141
|
+
"composables",
|
|
142
|
+
component.composables,
|
|
143
|
+
uiConfig.composablesLocation,
|
|
144
|
+
uiConfig.force
|
|
145
|
+
);
|
|
146
|
+
await writeCategoryFiles(
|
|
147
|
+
"plugins",
|
|
148
|
+
component.plugins,
|
|
149
|
+
uiConfig.pluginsLocation ?? "",
|
|
150
|
+
uiConfig.force
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await addModuleToConfig(_.uniq(found.flatMap((c) => c.modules || [])));
|
|
155
|
+
|
|
156
|
+
const deps = _.uniq(found.flatMap((c) => c.deps || []));
|
|
157
|
+
const devDeps = _.uniq(found.flatMap((c) => c.devDeps || []));
|
|
158
|
+
if (deps.length > 0 || devDeps.length > 0) {
|
|
159
|
+
if (options.all) {
|
|
160
|
+
await installPackages(uiConfig.packageManager, deps, devDeps);
|
|
161
|
+
} else {
|
|
162
|
+
const { confirmInstall } = await prompts({
|
|
163
|
+
type: "confirm",
|
|
164
|
+
name: "confirmInstall",
|
|
165
|
+
message: `Install packages: ${kleur.cyan([...deps, ...devDeps].join(", "))}?`,
|
|
166
|
+
initial: true,
|
|
167
|
+
});
|
|
168
|
+
if (confirmInstall) {
|
|
169
|
+
await installPackages(uiConfig.packageManager, deps, devDeps);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Collect and add components via the add command
|
|
175
|
+
const componentsToAdd = _.uniq(found.flatMap((c) => c.components || []));
|
|
176
|
+
if (componentsToAdd.length > 0) {
|
|
177
|
+
consola.info(`Adding ${componentsToAdd.length} component(s) required by prose...`);
|
|
178
|
+
const result = spawnSync("npx", ["ui-thing@latest", "add", ...componentsToAdd], {
|
|
179
|
+
cwd: currentDirectory,
|
|
180
|
+
stdio: "inherit",
|
|
181
|
+
});
|
|
182
|
+
if (result.error) {
|
|
183
|
+
consola.error("Failed to add components:", result.error.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
printFancyBoxMessage(
|
|
188
|
+
"Prose added!",
|
|
189
|
+
`Run the ${kleur.cyan("ui-thing@latest --help")} command to learn more.\n`,
|
|
190
|
+
{ box: { title: "Prose Components Added" } }
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export const prose = new Command()
|
|
195
|
+
.name("prose")
|
|
196
|
+
.command("prose")
|
|
197
|
+
.description("Add prose components to your project.")
|
|
198
|
+
.option("-a --all", "Add all prose components to your project.", false)
|
|
199
|
+
.argument("[componentNames...]")
|
|
200
|
+
.action(runProseCommand);
|
package/src/index.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { Command } from "commander";
|
|
|
3
3
|
|
|
4
4
|
import { version } from "../package.json";
|
|
5
5
|
import { add } from "./commands/add";
|
|
6
|
+
import { block } from "./commands/block";
|
|
6
7
|
import { init } from "./commands/init";
|
|
7
8
|
import { addPrettier } from "./commands/prettier";
|
|
9
|
+
import { prose } from "./commands/prose";
|
|
8
10
|
import { addShortcuts } from "./commands/shortcuts";
|
|
9
11
|
import { theme } from "./commands/theme";
|
|
10
12
|
import { printFancyBoxMessage } from "./utils/printFancyBoxMessage";
|
|
@@ -26,6 +28,8 @@ program
|
|
|
26
28
|
.version(version)
|
|
27
29
|
.addCommand(init)
|
|
28
30
|
.addCommand(add)
|
|
31
|
+
.addCommand(prose)
|
|
32
|
+
.addCommand(block)
|
|
29
33
|
.addCommand(theme)
|
|
30
34
|
.addCommand(addShortcuts)
|
|
31
35
|
.addCommand(addPrettier);
|
|
@@ -9,6 +9,6 @@ export const PRETTIER_CONFIG = {
|
|
|
9
9
|
trailingComma: "es5",
|
|
10
10
|
useTabs: false,
|
|
11
11
|
vueIndentScriptAndStyle: true,
|
|
12
|
-
tailwindFunctions: ["tv"],
|
|
12
|
+
tailwindFunctions: ["tv", "tw"],
|
|
13
13
|
importOrder: ["<BUILTIN_MODULES>", "<THIRD_PARTY_MODULES>", "<TYPES>", "", "^[.]"],
|
|
14
14
|
};
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
export const DEFINE_SHORTCUT = `import {
|
|
2
|
-
createSharedComposable,
|
|
3
|
-
useActiveElement,
|
|
4
|
-
useDebounceFn,
|
|
5
|
-
useEventListener,
|
|
6
|
-
} from "@vueuse/core";
|
|
7
|
-
import type { MaybeRef } from "vue";
|
|
1
|
+
export const DEFINE_SHORTCUT = `import type { MaybeRef } from "vue";
|
|
8
2
|
|
|
9
3
|
type KbdKeysSpecificMap = {
|
|
10
4
|
meta: string;
|
package/src/templates/vs-code.ts
CHANGED
|
@@ -5,10 +5,10 @@ export const VS_CODE_RECOMMENDATIONS = {
|
|
|
5
5
|
recommendations: [
|
|
6
6
|
"vue.volar",
|
|
7
7
|
"bradlc.vscode-tailwindcss",
|
|
8
|
-
"esbenp.prettier-vscode",
|
|
9
8
|
"antfu.iconify",
|
|
10
9
|
"formulahendry.auto-close-tag",
|
|
11
10
|
"formulahendry.auto-rename-tag",
|
|
11
|
+
"prettier.prettier-vscode",
|
|
12
12
|
],
|
|
13
13
|
};
|
|
14
14
|
|
|
@@ -20,5 +20,13 @@ export const VS_CODE_SETTINGS = {
|
|
|
20
20
|
"editor.quickSuggestions": { strings: "on" },
|
|
21
21
|
"files.associations": { "*.css": "tailwindcss" },
|
|
22
22
|
"tailwindCSS.classFunctions": ["tw", "clsx", "tw\\.[a-z-]+"],
|
|
23
|
-
"tailwindCSS.experimental.classRegex": [
|
|
23
|
+
"tailwindCSS.experimental.classRegex": [
|
|
24
|
+
[
|
|
25
|
+
"tv\\(([^)(]*(?:\\([^)(]*(?:\\([^)(]*(?:\\([^)(]*\\)[^)(]*)*\\)[^)(]*)*\\)[^)(]*)*)\\)",
|
|
26
|
+
'"(.*?)"',
|
|
27
|
+
],
|
|
28
|
+
"tw`(.*?)`",
|
|
29
|
+
"tw\\('(.*?)'\\)",
|
|
30
|
+
"tw\\(\\s*('(.*?)'|\"(.*?)\")\\s*\\)",
|
|
31
|
+
],
|
|
24
32
|
};
|
package/src/types.ts
CHANGED
|
@@ -85,6 +85,12 @@ export type AddCommand = {
|
|
|
85
85
|
all?: boolean;
|
|
86
86
|
};
|
|
87
87
|
|
|
88
|
+
export type TemplateFile = {
|
|
89
|
+
fileName: string;
|
|
90
|
+
dirPath: string;
|
|
91
|
+
fileContent: string;
|
|
92
|
+
};
|
|
93
|
+
|
|
88
94
|
export type Component = {
|
|
89
95
|
name: string;
|
|
90
96
|
value: string;
|
|
@@ -92,17 +98,56 @@ export type Component = {
|
|
|
92
98
|
devDeps?: string[];
|
|
93
99
|
nuxtModules?: string[];
|
|
94
100
|
instructions?: string[];
|
|
95
|
-
files:
|
|
96
|
-
utils:
|
|
97
|
-
composables:
|
|
98
|
-
plugins:
|
|
101
|
+
files: TemplateFile[];
|
|
102
|
+
utils: TemplateFile[];
|
|
103
|
+
composables: TemplateFile[];
|
|
104
|
+
plugins: TemplateFile[];
|
|
99
105
|
components?: string[];
|
|
100
106
|
askValidator?: boolean;
|
|
101
107
|
overrides?: Record<string, any>;
|
|
108
|
+
docsPath?: string;
|
|
102
109
|
};
|
|
103
110
|
|
|
104
|
-
type
|
|
111
|
+
export type ProseComponent = {
|
|
112
|
+
name: string;
|
|
113
|
+
value: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
filePath: string;
|
|
105
116
|
fileName: string;
|
|
106
|
-
|
|
107
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The other prose components that should be added alongside this one.
|
|
119
|
+
*/
|
|
120
|
+
prose?: string[];
|
|
121
|
+
docsUrl?: string;
|
|
122
|
+
file: TemplateFile;
|
|
123
|
+
/**
|
|
124
|
+
* The components that this prose component depends on.
|
|
125
|
+
*
|
|
126
|
+
* Should be added from the add command
|
|
127
|
+
*/
|
|
128
|
+
components?: string[];
|
|
129
|
+
composables?: TemplateFile[];
|
|
130
|
+
utils?: TemplateFile[];
|
|
131
|
+
plugins?: TemplateFile[];
|
|
132
|
+
/** The dependencies to add */
|
|
133
|
+
deps?: string[];
|
|
134
|
+
/** The development dependencies to add */
|
|
135
|
+
devDeps?: string[];
|
|
136
|
+
/** The nuxt modules to add */
|
|
137
|
+
modules?: string[];
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type BlockComponent = {
|
|
141
|
+
/** The display name of the block */
|
|
142
|
+
name: string;
|
|
143
|
+
/** The filename of the block */
|
|
144
|
+
fileName: string;
|
|
145
|
+
/** The actual file content of the block */
|
|
146
|
+
file: string;
|
|
147
|
+
/** The category of the block */
|
|
148
|
+
category: string;
|
|
149
|
+
/** The path to the block */
|
|
150
|
+
path: string;
|
|
151
|
+
/** The components used by the block */
|
|
152
|
+
components?: string[];
|
|
108
153
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
|
|
5
|
+
dotenv.config();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Fetch block categories from UI Thing API.
|
|
9
|
+
*/
|
|
10
|
+
export const fetchBlockCategories = async () => {
|
|
11
|
+
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;
|
|
19
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
|
|
5
|
+
import { BlockComponent } from "../types";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetch block components from UI Thing API.
|
|
11
|
+
*/
|
|
12
|
+
export const fetchBlocks = async () => {
|
|
13
|
+
const spinner = ora("Fetching blocks...").start();
|
|
14
|
+
|
|
15
|
+
const { data } = await axios.get<BlockComponent[]>(
|
|
16
|
+
process.env.BLOCKS_API || "https://uithing.com/api/blocks"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
spinner.succeed("Blocks fetched.");
|
|
20
|
+
return data;
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
|
|
5
|
+
import { ProseComponent } from "../types";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fetch prose components from UI Thing API.
|
|
11
|
+
*/
|
|
12
|
+
export const fetchProseComponents = async () => {
|
|
13
|
+
const spinner = ora("Fetching prose components...").start();
|
|
14
|
+
|
|
15
|
+
const { data } = await axios.get<ProseComponent[]>(
|
|
16
|
+
process.env.PROSE_COMPONENTS_API || "https://uithing.com/api/prose"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
spinner.succeed("Prose components fetched.");
|
|
20
|
+
return data;
|
|
21
|
+
};
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
2
|
|
|
3
|
-
import { Component } from "../types";
|
|
3
|
+
import { Component, ProseComponent } from "../types";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Prompts the user to select components to add.
|
|
7
7
|
*/
|
|
8
8
|
export const promptUserForComponents = async (
|
|
9
9
|
all?: boolean,
|
|
10
|
-
allComponents: Component[] = []
|
|
10
|
+
allComponents: Component[] | ProseComponent[] = []
|
|
11
11
|
): Promise<string[]> => {
|
|
12
12
|
// If all is true, return all components
|
|
13
|
-
if (all) return allComponents.map((c: Component) => c.value);
|
|
13
|
+
if (all) return allComponents.map((c: Component | ProseComponent) => c.value);
|
|
14
14
|
const { components } = await prompts({
|
|
15
15
|
type: "autocompleteMultiselect",
|
|
16
16
|
name: "components",
|
|
17
17
|
message: "Select the components you want to add",
|
|
18
|
-
choices: allComponents.map((c: Component) => ({
|
|
18
|
+
choices: allComponents.map((c: Component | ProseComponent) => ({
|
|
19
|
+
title: c.name,
|
|
20
|
+
value: c.value,
|
|
21
|
+
})),
|
|
19
22
|
});
|
|
20
23
|
return components;
|
|
21
24
|
};
|