centoui-cli 1.0.0-alpha.37 → 1.0.0-alpha.38
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.d.mts +8 -3
- package/dist/index.mjs +69 -47
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -16,7 +16,12 @@ type GlobalsRegistry = {
|
|
|
16
16
|
*/
|
|
17
17
|
type ComponentRegistry = {
|
|
18
18
|
/** Unique identifier for the component. Matches its registry filename (e.g. `"button"`). */name: string; /** Short human-readable description shown in CLI output. */
|
|
19
|
-
description
|
|
19
|
+
description: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this component requires the global utils file (e.g., isSlotEmpty).
|
|
22
|
+
* If true, the utils file will be written when this component is installed.
|
|
23
|
+
*/
|
|
24
|
+
needsUtils?: boolean;
|
|
20
25
|
/**
|
|
21
26
|
* Source file paths relative to `packages/core/src/`.
|
|
22
27
|
* All paths start with `components/` by convention (e.g. `"components/button/button.vue"`).
|
|
@@ -27,13 +32,13 @@ type ComponentRegistry = {
|
|
|
27
32
|
* Names of other CentoUI components that must be installed alongside this one.
|
|
28
33
|
* The CLI resolves the full dependency tree automatically.
|
|
29
34
|
*/
|
|
30
|
-
componentDeps
|
|
35
|
+
componentDeps?: string[];
|
|
31
36
|
/**
|
|
32
37
|
* NPM packages required specifically by this component,
|
|
33
38
|
* in addition to the global dependencies defined in `GlobalsRegistry`.
|
|
34
39
|
* Keys are package names; values are semver version ranges.
|
|
35
40
|
*/
|
|
36
|
-
packageDeps
|
|
41
|
+
packageDeps?: Record<string, string>;
|
|
37
42
|
};
|
|
38
43
|
/**
|
|
39
44
|
* The complete CentoUI registry — the `index.json` file fetched from GitHub.
|
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { loadConfig } from "c12";
|
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/constants.ts
|
|
10
10
|
/** CentoUI current package version, sourced directly from package.json. */
|
|
11
|
-
const VERSION = "1.0.0-alpha.
|
|
11
|
+
const VERSION = "1.0.0-alpha.38";
|
|
12
12
|
/** File name for the user-side CentoUI config (created by `centoui init`). */
|
|
13
13
|
const CONFIG_FILE_NAME = "centoui.config.ts";
|
|
14
14
|
/**
|
|
@@ -68,7 +68,7 @@ async function installMissingPackages(requiredPackages, cwd, onProgress) {
|
|
|
68
68
|
...packageJson.dependencies,
|
|
69
69
|
...packageJson.devDependencies
|
|
70
70
|
};
|
|
71
|
-
const packagesToInstall = Object.entries(requiredPackages).filter(([name
|
|
71
|
+
const packagesToInstall = Object.entries(requiredPackages).filter(([name]) => !(name in alreadyInstalled)).map(([name, version]) => `${name}@${version}`);
|
|
72
72
|
if (packagesToInstall.length === 0) return "All packages already up to date";
|
|
73
73
|
try {
|
|
74
74
|
for (const [index, pkg] of packagesToInstall.entries()) {
|
|
@@ -128,16 +128,16 @@ function validateNonEmptyPath(value) {
|
|
|
128
128
|
//#endregion
|
|
129
129
|
//#region src/utils/file-system-utils.ts
|
|
130
130
|
/**
|
|
131
|
-
* Converts a registry-relative file path into the absolute destination path
|
|
131
|
+
* Converts a registry-relative component file path into the absolute destination path
|
|
132
132
|
* inside the user's project.
|
|
133
133
|
*
|
|
134
|
-
* Registry file paths always begin with `components/` (e.g.
|
|
134
|
+
* Registry component file paths always begin with `components/` (e.g.
|
|
135
135
|
* `"components/button/button.vue"`). This function strips that leading segment
|
|
136
136
|
* and joins the remainder with the user's configured components directory so
|
|
137
137
|
* that `"components/button/button.vue"` becomes, for example,
|
|
138
138
|
* `"/home/user/my-app/src/components/centoui/button/button.vue"`.
|
|
139
139
|
*
|
|
140
|
-
* @param
|
|
140
|
+
* @param registryComponentFilePath - Path as it appears in the component's registry entry
|
|
141
141
|
* (always starts with `"components/"`).
|
|
142
142
|
* @param config - The loaded CentoUI project configuration.
|
|
143
143
|
* @param cwd - Absolute path to the project root.
|
|
@@ -145,11 +145,11 @@ function validateNonEmptyPath(value) {
|
|
|
145
145
|
*
|
|
146
146
|
* @example
|
|
147
147
|
* // config.componentsDir = 'src/components/centoui', cwd = '/home/user/my-app'
|
|
148
|
-
*
|
|
148
|
+
* mapComponentsRegistryPathToProjectDest('components/button/button.vue', config, cwd)
|
|
149
149
|
* // → '/home/user/my-app/src/components/centoui/button/button.vue'
|
|
150
150
|
*/
|
|
151
|
-
function
|
|
152
|
-
const pathWithoutRegistryPrefix =
|
|
151
|
+
function mapComponentsRegistryPathToProjectDest(registryComponentFilePath, config, cwd) {
|
|
152
|
+
const pathWithoutRegistryPrefix = registryComponentFilePath.replace(/^components\//, "");
|
|
153
153
|
return join(cwd, config.componentsDir, pathWithoutRegistryPrefix);
|
|
154
154
|
}
|
|
155
155
|
/**
|
|
@@ -185,7 +185,10 @@ async function writeFileWithDirs(filePath, content) {
|
|
|
185
185
|
*/
|
|
186
186
|
async function confirmOverwriteIfExists(label, path) {
|
|
187
187
|
if (!await fsExtra.pathExists(path)) return true;
|
|
188
|
-
const answer = await confirm({
|
|
188
|
+
const answer = await confirm({
|
|
189
|
+
message: `"${label}" already exists. Overwrite?`,
|
|
190
|
+
initialValue: false
|
|
191
|
+
});
|
|
189
192
|
if (isCancel(answer)) {
|
|
190
193
|
cancel("Operation cancelled.");
|
|
191
194
|
process.exit(0);
|
|
@@ -337,7 +340,7 @@ function resolveComponentWithDependencies(componentName, registry, visited = /*
|
|
|
337
340
|
const entry = registry.components.find((component) => component.name === componentName);
|
|
338
341
|
if (!entry) throw new Error(`[resolveComponentWithDependencies] Component "${componentName}" not found in registry.`);
|
|
339
342
|
result.set(componentName, entry);
|
|
340
|
-
for (const dep of entry
|
|
343
|
+
for (const dep of entry?.componentDeps || []) for (const [depName, depEntry] of resolveComponentWithDependencies(dep, registry, visited)) result.set(depName, depEntry);
|
|
341
344
|
return result;
|
|
342
345
|
}
|
|
343
346
|
/**
|
|
@@ -410,9 +413,8 @@ async function fetchUtilsFileContent() {
|
|
|
410
413
|
* Bootstraps a new CentoUI project in the current working directory.
|
|
411
414
|
*
|
|
412
415
|
* Flow:
|
|
413
|
-
* 1. Prompt the user for the components directory
|
|
414
|
-
* 2. Ask upfront whether to overwrite
|
|
415
|
-
* (config file, theme CSS, components directory) if they already exist.
|
|
416
|
+
* 1. Prompt the user for the components directory, theme CSS file path, and utils file path.
|
|
417
|
+
* 2. Ask upfront whether to overwrite the config file, theme CSS, components directory paths if they already exist.
|
|
416
418
|
* 3. Write the config file, fetch and write the theme CSS, prepare the
|
|
417
419
|
* components directory, and install global npm dependencies.
|
|
418
420
|
*/
|
|
@@ -438,7 +440,7 @@ function init() {
|
|
|
438
440
|
validate: validateNonEmptyPath
|
|
439
441
|
}),
|
|
440
442
|
utilsFilePath: () => text({
|
|
441
|
-
message: "Path for the utils file",
|
|
443
|
+
message: "Path for the utils file (written on demand)",
|
|
442
444
|
initialValue: "src/utils/centoui-utils.ts",
|
|
443
445
|
validate: validateNonEmptyPath
|
|
444
446
|
})
|
|
@@ -449,19 +451,16 @@ function init() {
|
|
|
449
451
|
const configPath = join(cwd, CONFIG_FILE_NAME);
|
|
450
452
|
const themePath = join(cwd, directories.themeFilePath);
|
|
451
453
|
const componentsPath = join(cwd, directories.componentDir);
|
|
452
|
-
const utilsPath = join(cwd, directories.utilsFilePath);
|
|
453
454
|
const shouldWriteConfig = await confirmOverwriteIfExists(CONFIG_FILE_NAME, configPath);
|
|
454
455
|
const shouldWriteTheme = await confirmOverwriteIfExists(directories.themeFilePath, themePath);
|
|
455
456
|
const shouldWriteComponentsDir = await confirmOverwriteIfExists(directories.componentDir, componentsPath);
|
|
456
|
-
const shouldWriteUtils = await confirmOverwriteIfExists(directories.utilsFilePath, utilsPath);
|
|
457
457
|
let registry;
|
|
458
458
|
await tasks([
|
|
459
459
|
{
|
|
460
460
|
title: "Fetching config defaults",
|
|
461
461
|
task: async () => {
|
|
462
462
|
if (!shouldWriteConfig) return `Skipped — "${CONFIG_FILE_NAME}" already exists`;
|
|
463
|
-
|
|
464
|
-
await fsExtra.outputFile(configPath, userConfigContent, "utf-8");
|
|
463
|
+
await writeFileWithDirs(configPath, await buildUserDefaultConfigFileContent(directories.themeFilePath, directories.componentDir, directories.utilsFilePath));
|
|
465
464
|
return `${CONFIG_FILE_NAME} written`;
|
|
466
465
|
}
|
|
467
466
|
},
|
|
@@ -469,20 +468,10 @@ function init() {
|
|
|
469
468
|
title: "Fetching theme CSS",
|
|
470
469
|
task: async () => {
|
|
471
470
|
if (!shouldWriteTheme) return `Skipped — "${directories.themeFilePath}" already exists`;
|
|
472
|
-
|
|
473
|
-
await fsExtra.outputFile(themePath, themeContent, "utf-8");
|
|
471
|
+
await writeFileWithDirs(themePath, await fetchThemeCSSContent());
|
|
474
472
|
return `${directories.themeFilePath} written`;
|
|
475
473
|
}
|
|
476
474
|
},
|
|
477
|
-
{
|
|
478
|
-
title: "Writing utils file",
|
|
479
|
-
task: async () => {
|
|
480
|
-
if (!shouldWriteUtils) return `Skipped — "${directories.utilsFilePath}" already exists`;
|
|
481
|
-
const utilsContent = await fetchUtilsFileContent();
|
|
482
|
-
await fsExtra.outputFile(utilsPath, utilsContent, "utf-8");
|
|
483
|
-
return `${directories.utilsFilePath} written`;
|
|
484
|
-
}
|
|
485
|
-
},
|
|
486
475
|
{
|
|
487
476
|
title: "Preparing components directory",
|
|
488
477
|
task: async () => {
|
|
@@ -585,7 +574,8 @@ async function checkIsComponentInstalled(componentName, config, cwd) {
|
|
|
585
574
|
* 1. Resolve the full dependency tree for every requested component.
|
|
586
575
|
* 2. Ask the user upfront whether to overwrite any that already exist.
|
|
587
576
|
* 3. Fetch and write the source files for components the user approved.
|
|
588
|
-
* 4.
|
|
577
|
+
* 4. Fetch and write the utils file if it doesn't exist but is required by the components.
|
|
578
|
+
* 5. Install any npm packages required by the components being written.
|
|
589
579
|
*/
|
|
590
580
|
function add() {
|
|
591
581
|
return defineCommand({
|
|
@@ -617,19 +607,32 @@ function add() {
|
|
|
617
607
|
const packageDepsToInstall = {};
|
|
618
608
|
for (const [name, entry] of allComponents) if (writeDecisions.get(name)) Object.assign(packageDepsToInstall, entry.packageDeps);
|
|
619
609
|
const approvedComponents = Array.from(allComponents.entries()).filter(([name]) => writeDecisions.get(name));
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
610
|
+
const needsUtils = Array.from(approvedComponents).some(([, entry]) => entry.needsUtils === true);
|
|
611
|
+
const utilsPath = join(cwd, config.utilsFilePath);
|
|
612
|
+
const utilsFileExists = await fsExtra.pathExists(utilsPath);
|
|
613
|
+
await tasks([
|
|
614
|
+
...approvedComponents.map(([name, entry]) => ({
|
|
615
|
+
title: `Installing ${name}`,
|
|
616
|
+
task: async () => {
|
|
617
|
+
for (const registryFilePath of entry.files) {
|
|
618
|
+
const content = await fetchRegistryFileContent(registryFilePath);
|
|
619
|
+
await writeFileWithDirs(mapComponentsRegistryPathToProjectDest(registryFilePath, config, cwd), content);
|
|
620
|
+
}
|
|
621
|
+
return `${name} installed (${entry.files.length} file(s))`;
|
|
626
622
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
623
|
+
})),
|
|
624
|
+
{
|
|
625
|
+
title: "Installing packages",
|
|
626
|
+
task: async (message) => installMissingPackages(packageDepsToInstall, cwd, message)
|
|
627
|
+
},
|
|
628
|
+
...needsUtils && !utilsFileExists ? [{
|
|
629
|
+
title: "Writing utils file",
|
|
630
|
+
task: async () => {
|
|
631
|
+
await writeFileWithDirs(utilsPath, await fetchUtilsFileContent());
|
|
632
|
+
return `${config.utilsFilePath} written`;
|
|
633
|
+
}
|
|
634
|
+
}] : []
|
|
635
|
+
]);
|
|
633
636
|
const skippedNames = Array.from(writeDecisions.entries()).filter(([, shouldWrite]) => !shouldWrite).map(([name]) => name);
|
|
634
637
|
note([
|
|
635
638
|
`Installed > ${approvedComponents.map(([name]) => name).join(", ") || "none"}`,
|
|
@@ -660,6 +663,7 @@ function add() {
|
|
|
660
663
|
* know which of this component's packages become orphaned.
|
|
661
664
|
* 4. Ask for confirmation, then delete the component directory and remove
|
|
662
665
|
* any newly orphaned npm packages.
|
|
666
|
+
* 5. Check if any utils file is no longer needed and remove it if so(with confirmation from the user).
|
|
663
667
|
*/
|
|
664
668
|
function remove() {
|
|
665
669
|
return defineCommand({
|
|
@@ -689,14 +693,17 @@ function remove() {
|
|
|
689
693
|
for (const name of remainingNames) {
|
|
690
694
|
const entry = registry.components.find((component) => component.name === name);
|
|
691
695
|
if (!entry) continue;
|
|
692
|
-
if (entry.componentDeps
|
|
693
|
-
Object.assign(packagesStillNeeded, entry.packageDeps);
|
|
696
|
+
if (entry.componentDeps?.includes(componentName)) dependents.add(name);
|
|
697
|
+
Object.assign(packagesStillNeeded, entry.packageDeps || {});
|
|
694
698
|
}
|
|
695
699
|
if (dependents.size > 0) {
|
|
696
700
|
const list = Array.from(dependents).map((dependent) => ` · ${dependent}`).join("\n");
|
|
697
701
|
throw new Error(`Cannot remove "${componentName}" — the following installed components depend on it:\n${list}`);
|
|
698
702
|
}
|
|
699
|
-
const confirmed = await confirm({
|
|
703
|
+
const confirmed = await confirm({
|
|
704
|
+
message: `Remove "${componentName}"?`,
|
|
705
|
+
initialValue: false
|
|
706
|
+
});
|
|
700
707
|
if (isCancel(confirmed) || !confirmed) {
|
|
701
708
|
cancel("Removal cancelled.");
|
|
702
709
|
process.exit(0);
|
|
@@ -709,10 +716,25 @@ function remove() {
|
|
|
709
716
|
}
|
|
710
717
|
}, {
|
|
711
718
|
title: "Removing orphaned packages",
|
|
712
|
-
task: async (message) => removeOrphanedPackages(targetEntry.packageDeps, packagesStillNeeded, cwd, message)
|
|
719
|
+
task: async (message) => removeOrphanedPackages(targetEntry.packageDeps || {}, packagesStillNeeded, cwd, message)
|
|
713
720
|
}]);
|
|
714
|
-
const removedPackages = Object.keys(targetEntry.packageDeps).filter((pkg) => !(pkg in packagesStillNeeded));
|
|
721
|
+
const removedPackages = Object.keys(targetEntry.packageDeps || {}).filter((pkg) => !(pkg in packagesStillNeeded));
|
|
715
722
|
if (removedPackages.length > 0) note(removedPackages.map((pkg) => ` · ${pkg}`).join("\n"), "Packages removed");
|
|
723
|
+
if (!remainingNames.some((name) => {
|
|
724
|
+
return registry.components.find((c) => c.name === name)?.needsUtils === true;
|
|
725
|
+
})) {
|
|
726
|
+
const utilsPath = join(cwd, config.utilsFilePath);
|
|
727
|
+
if (await fsExtra.pathExists(utilsPath)) {
|
|
728
|
+
const shouldDeleteUtils = await confirm({
|
|
729
|
+
message: "Delete utils file? (no components require it anymore)",
|
|
730
|
+
initialValue: false
|
|
731
|
+
});
|
|
732
|
+
if (!isCancel(shouldDeleteUtils) && shouldDeleteUtils) {
|
|
733
|
+
await fsExtra.remove(utilsPath);
|
|
734
|
+
note(`${config.utilsFilePath} deleted`, "Utils file removed");
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
716
738
|
outro("All set!");
|
|
717
739
|
} catch (error) {
|
|
718
740
|
log.error(`Failed to remove component: ${error}`);
|