centoui-cli 0.1.1 → 0.2.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/dist/index.mjs +166 -3
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { defineCommand, runMain } from "citty";
|
|
3
3
|
import { cancel, confirm, group, intro, isCancel, log, note, outro, tasks, text } from "@clack/prompts";
|
|
4
|
-
import { join } from "pathe";
|
|
4
|
+
import { dirname, join } from "pathe";
|
|
5
5
|
import fsExtra from "fs-extra";
|
|
6
6
|
import { addDependency } from "nypm";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
//#endregion
|
|
8
9
|
//#region src/constants.ts
|
|
9
10
|
/** CentoUI current package version */
|
|
10
|
-
const VERSION = "0.
|
|
11
|
+
const VERSION = "0.2.1";
|
|
11
12
|
/** CentoUI config file name */
|
|
12
13
|
const CONFIG_FILE_NAME = "centoui.config.ts";
|
|
13
14
|
/** CentoUI registry file name */
|
|
@@ -64,6 +65,36 @@ function validatePath(path) {
|
|
|
64
65
|
//#endregion
|
|
65
66
|
//#region src/utils/file-system-utils.ts
|
|
66
67
|
/**
|
|
68
|
+
* Resolve the destination path for a component file inside the user's project.
|
|
69
|
+
* Strips the leading `src/components/` registry prefix.
|
|
70
|
+
*
|
|
71
|
+
* e.g. with `componentsDir = './components/ui'`:
|
|
72
|
+
* src/components/button/button.vue → ./components/ui/button/button.vue
|
|
73
|
+
*
|
|
74
|
+
* @param path - The path to the component file in the registry.
|
|
75
|
+
* @param config - The CentoUI configuration.
|
|
76
|
+
* @param cwd - The current working directory.
|
|
77
|
+
* @returns The destination path for the component file.
|
|
78
|
+
*/
|
|
79
|
+
function resolveComponentsDestinationPath(path, config, cwd) {
|
|
80
|
+
const normalizedPath = path.replace(/^src\/components\//, "");
|
|
81
|
+
return join(cwd, config.componentsDir, normalizedPath);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Write content to a destination path, creating parent directories as needed.
|
|
85
|
+
*
|
|
86
|
+
* @param path - The path to write the file to.
|
|
87
|
+
* @param content - The content to write to the file.
|
|
88
|
+
*/
|
|
89
|
+
async function writeFile(path, content) {
|
|
90
|
+
try {
|
|
91
|
+
await fsExtra.mkdir(dirname(path), { recursive: true });
|
|
92
|
+
await fsExtra.writeFile(path, content, "utf8");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error(`Failed to write file: ${path}: ${error}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
67
98
|
* Ask the user whether to overwrite a path that already exists.
|
|
68
99
|
* Returns `true` immediately (no prompt) if the file does not exist yet.
|
|
69
100
|
*
|
|
@@ -82,6 +113,22 @@ async function promptOverwrite(label, path) {
|
|
|
82
113
|
//#endregion
|
|
83
114
|
//#region src/utils/config-utils.ts
|
|
84
115
|
/**
|
|
116
|
+
* Loads the user's CentoUI configuration.
|
|
117
|
+
*
|
|
118
|
+
* @param cwd - The current working directory.
|
|
119
|
+
* @returns The user's CentoUI configuration.
|
|
120
|
+
* @throws If the config file is not found or cannot be loaded.
|
|
121
|
+
*/
|
|
122
|
+
async function loadUserConfig(cwd) {
|
|
123
|
+
try {
|
|
124
|
+
const configFilePath = join(cwd, CONFIG_FILE_NAME);
|
|
125
|
+
if (!await fsExtra.pathExists(configFilePath)) throw `No ${CONFIG_FILE_NAME} found. Run \`centoui init\` first.`;
|
|
126
|
+
return (await import(pathToFileURL(configFilePath).href)).default;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
throw new Error(`Failed to load user config: ${error}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
85
132
|
* Generates the default user-defined CentoUI config file template.
|
|
86
133
|
* @param themeFilePath - The relative path to the user's theme CSS file.
|
|
87
134
|
* @param componentsDir - The relative path to the user's components directory.
|
|
@@ -121,6 +168,36 @@ async function fetchRegistry() {
|
|
|
121
168
|
return registry;
|
|
122
169
|
}
|
|
123
170
|
/**
|
|
171
|
+
* Recursively resolves a component and all its transitive dependencies.
|
|
172
|
+
*
|
|
173
|
+
* @param name - Root component name to resolve
|
|
174
|
+
* @param registry - The pre-fetched registry index
|
|
175
|
+
* @param seen - Internal set used to prevent infinite loops on circular deps
|
|
176
|
+
* @returns Map of component name → registry entry for the full dependency tree
|
|
177
|
+
*/
|
|
178
|
+
function resolveComponentTree(name, registry, seen = /* @__PURE__ */ new Set()) {
|
|
179
|
+
const result = /* @__PURE__ */ new Map();
|
|
180
|
+
if (seen.has(name)) return result;
|
|
181
|
+
seen.add(name);
|
|
182
|
+
const entry = registry.components.find((component) => component.name === name);
|
|
183
|
+
if (!entry) throw new Error(`Component "${name}" not found in registry.`);
|
|
184
|
+
result.set(name, entry);
|
|
185
|
+
for (const dependency of entry.componentDeps) for (const [dependencyName, dependencyEntry] of resolveComponentTree(dependency, registry, seen)) result.set(dependencyName, dependencyEntry);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Fetches the raw source code of a component or utility file from the registry.
|
|
190
|
+
*
|
|
191
|
+
* @param registryPath - Path as listed in the component registry (e.g. 'src/components/button/button.vue')
|
|
192
|
+
* @returns The raw source code of the file
|
|
193
|
+
*/
|
|
194
|
+
async function fetchComponentFile(registryPath) {
|
|
195
|
+
const requestUrl = `${BASE_URL}/${registryPath}`;
|
|
196
|
+
const response = await fetch(requestUrl, { headers: FETCH_HEADERS });
|
|
197
|
+
if (!response.ok) throw new Error(`${response.status}: ${response.statusText}`);
|
|
198
|
+
return response.text();
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
124
201
|
* Fetches the theme file from the registry.
|
|
125
202
|
*
|
|
126
203
|
* @returns The raw source code of the theme file
|
|
@@ -220,13 +297,99 @@ function init() {
|
|
|
220
297
|
});
|
|
221
298
|
}
|
|
222
299
|
//#endregion
|
|
300
|
+
//#region src/utils/components-utils.ts
|
|
301
|
+
/**
|
|
302
|
+
* Resolves the absolute path to a specific component directory.
|
|
303
|
+
*
|
|
304
|
+
* @param name - The name of the component (kebab-case)
|
|
305
|
+
* @param config - CentoUI configuration
|
|
306
|
+
* @param cwd - Current working directory
|
|
307
|
+
* @returns The absolute path to the component directory
|
|
308
|
+
*/
|
|
309
|
+
function getComponentPath(name, config, cwd) {
|
|
310
|
+
return join(cwd, config.componentsDir, name);
|
|
311
|
+
}
|
|
312
|
+
//#endregion
|
|
313
|
+
//#region src/commands/add.ts
|
|
314
|
+
/**
|
|
315
|
+
* Command: add
|
|
316
|
+
*
|
|
317
|
+
* Installs one or more components (and their transitive dependencies) from
|
|
318
|
+
* the registry into the user's project.
|
|
319
|
+
*/
|
|
320
|
+
function add() {
|
|
321
|
+
return defineCommand({
|
|
322
|
+
meta: {
|
|
323
|
+
name: "add",
|
|
324
|
+
description: "Add one or more components to your project"
|
|
325
|
+
},
|
|
326
|
+
args: {},
|
|
327
|
+
async run({ args }) {
|
|
328
|
+
try {
|
|
329
|
+
const cwd = process.cwd();
|
|
330
|
+
const componentNames = args._;
|
|
331
|
+
intro("CentoUI — Add components");
|
|
332
|
+
if (componentNames.length === 0) {
|
|
333
|
+
log.error("No components specified. Usage: centoui add <component> [component...]");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
const config = await loadUserConfig(cwd);
|
|
337
|
+
const registry = await fetchRegistry();
|
|
338
|
+
const allComponents = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const name of componentNames) try {
|
|
340
|
+
const tree = resolveComponentTree(name, registry);
|
|
341
|
+
for (const [depName, depEntry] of tree) allComponents.set(depName, depEntry);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
log.error(`Failed to resolve "${name}": ${error}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
const installDecisions = /* @__PURE__ */ new Map();
|
|
347
|
+
for (const [name] of allComponents) {
|
|
348
|
+
const shouldWrite = await promptOverwrite(name, getComponentPath(name, config, cwd));
|
|
349
|
+
installDecisions.set(name, shouldWrite);
|
|
350
|
+
}
|
|
351
|
+
const allPackageDeps = {};
|
|
352
|
+
for (const [name, entry] of allComponents) if (installDecisions.get(name)) Object.assign(allPackageDeps, entry.packageDeps);
|
|
353
|
+
const componentsToInstall = Array.from(allComponents.entries()).filter(([name]) => installDecisions.get(name));
|
|
354
|
+
await tasks([...componentsToInstall.map(([name, entry]) => ({
|
|
355
|
+
title: `Installing ${name}`,
|
|
356
|
+
task: async () => {
|
|
357
|
+
for (const registryPath of entry.files) {
|
|
358
|
+
const content = await fetchComponentFile(registryPath);
|
|
359
|
+
await writeFile(resolveComponentsDestinationPath(registryPath, config, cwd), content);
|
|
360
|
+
}
|
|
361
|
+
return `${name} installed (${entry.files.length} file${entry.files.length !== 1 ? "s" : ""})`;
|
|
362
|
+
}
|
|
363
|
+
})), {
|
|
364
|
+
title: "Installing packages",
|
|
365
|
+
task: async (message) => installPackages(allPackageDeps, cwd, message)
|
|
366
|
+
}]);
|
|
367
|
+
const skipped = Array.from(installDecisions.entries()).filter(([, shouldWrite]) => !shouldWrite).map(([name]) => name);
|
|
368
|
+
note([
|
|
369
|
+
`Installed > ${componentsToInstall.map(([name]) => name).join(", ") || "none"}`,
|
|
370
|
+
skipped.length > 0 ? `Skipped > ${skipped.join(", ")}` : "",
|
|
371
|
+
"",
|
|
372
|
+
"Import components from your components directory to use them"
|
|
373
|
+
].filter(Boolean).join("\n"), "Components added");
|
|
374
|
+
outro("All set!");
|
|
375
|
+
} catch (error) {
|
|
376
|
+
log.error(`Failed to add component(s): ${error}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
223
383
|
//#region src/index.ts
|
|
224
384
|
runMain(defineCommand({
|
|
225
385
|
meta: {
|
|
226
386
|
name: "centoui",
|
|
227
387
|
version: VERSION
|
|
228
388
|
},
|
|
229
|
-
subCommands: {
|
|
389
|
+
subCommands: {
|
|
390
|
+
init,
|
|
391
|
+
add
|
|
392
|
+
}
|
|
230
393
|
}));
|
|
231
394
|
//#endregion
|
|
232
395
|
export {};
|