@gilav21/shadcn-angular 0.0.25 → 0.0.26
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/commands/add.d.ts +2 -0
- package/dist/commands/add.js +169 -114
- package/dist/commands/add.spec.js +17 -23
- package/dist/commands/diff.d.ts +8 -0
- package/dist/commands/diff.js +99 -0
- package/dist/commands/help.js +15 -6
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +171 -185
- package/dist/commands/list.d.ts +1 -0
- package/dist/commands/list.js +50 -0
- package/dist/index.js +21 -1
- package/dist/registry/index.d.ts +122 -12
- package/dist/registry/index.js +56 -168
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +22 -2
- package/dist/utils/paths.d.ts +7 -0
- package/dist/utils/paths.js +43 -0
- package/dist/utils/shortcut-registry.js +1 -13
- package/package.json +1 -1
- package/scripts/sync-registry.ts +347 -0
- package/src/commands/add.spec.ts +22 -32
- package/src/commands/add.ts +211 -137
- package/src/commands/diff.ts +133 -0
- package/src/commands/help.ts +15 -6
- package/src/commands/init.ts +329 -314
- package/src/commands/list.ts +66 -0
- package/src/index.ts +24 -1
- package/src/registry/index.ts +71 -180
- package/src/utils/config.ts +22 -3
- package/src/utils/paths.ts +52 -0
- package/src/utils/shortcut-registry.ts +1 -15
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getConfig } from '../utils/config.js';
|
|
5
|
+
import { registry, getComponentNames } from '../registry/index.js';
|
|
6
|
+
import { resolveProjectPath, aliasToProjectPath } from '../utils/paths.js';
|
|
7
|
+
import { fetchAndTransform, normalizeContent } from './add.js';
|
|
8
|
+
function formatLineDiff(localLine, remoteLine) {
|
|
9
|
+
if (localLine !== undefined && remoteLine !== undefined) {
|
|
10
|
+
return [chalk.red(`- ${localLine}`), chalk.green(`+ ${remoteLine}`)];
|
|
11
|
+
}
|
|
12
|
+
if (localLine === undefined) {
|
|
13
|
+
return [chalk.green(`+ ${remoteLine}`)];
|
|
14
|
+
}
|
|
15
|
+
return [chalk.red(`- ${localLine}`)];
|
|
16
|
+
}
|
|
17
|
+
export function formatUnifiedDiff(fileName, localContent, remoteContent) {
|
|
18
|
+
const localLines = localContent.split('\n');
|
|
19
|
+
const remoteLines = remoteContent.split('\n');
|
|
20
|
+
const output = [
|
|
21
|
+
chalk.bold(`--- local/${fileName}`),
|
|
22
|
+
chalk.bold(`+++ remote/${fileName}`),
|
|
23
|
+
];
|
|
24
|
+
const maxLines = Math.max(localLines.length, remoteLines.length);
|
|
25
|
+
let diffFound = false;
|
|
26
|
+
for (let i = 0; i < maxLines; i++) {
|
|
27
|
+
const localLine = localLines[i];
|
|
28
|
+
const remoteLine = remoteLines[i];
|
|
29
|
+
if (localLine === remoteLine)
|
|
30
|
+
continue;
|
|
31
|
+
diffFound = true;
|
|
32
|
+
output.push(chalk.cyan(`@@ line ${i + 1} @@`), ...formatLineDiff(localLine, remoteLine));
|
|
33
|
+
}
|
|
34
|
+
return diffFound ? output.join('\n') : '';
|
|
35
|
+
}
|
|
36
|
+
async function diffFile(file, targetDir, options, utilsAlias) {
|
|
37
|
+
const targetPath = path.join(targetDir, file);
|
|
38
|
+
if (!await fs.pathExists(targetPath))
|
|
39
|
+
return null;
|
|
40
|
+
try {
|
|
41
|
+
const localContent = normalizeContent(await fs.readFile(targetPath, 'utf-8'));
|
|
42
|
+
const remoteContent = normalizeContent(await fetchAndTransform(file, { branch: options.branch, remote: options.remote, registry: options.registry }, utilsAlias));
|
|
43
|
+
if (localContent === remoteContent)
|
|
44
|
+
return null;
|
|
45
|
+
const diffOutput = formatUnifiedDiff(file, localContent, remoteContent);
|
|
46
|
+
return diffOutput || null;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return chalk.yellow(` Could not fetch remote version of ${file}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function diffComponent(name, targetDir, options, utilsAlias) {
|
|
53
|
+
const component = registry[name];
|
|
54
|
+
const fileDiffs = [];
|
|
55
|
+
for (const file of component.files) {
|
|
56
|
+
const result = await diffFile(file, targetDir, options, utilsAlias);
|
|
57
|
+
if (result)
|
|
58
|
+
fileDiffs.push(result);
|
|
59
|
+
}
|
|
60
|
+
return fileDiffs;
|
|
61
|
+
}
|
|
62
|
+
export async function diff(components, options) {
|
|
63
|
+
const cwd = process.cwd();
|
|
64
|
+
const config = await getConfig(cwd);
|
|
65
|
+
if (!config) {
|
|
66
|
+
console.log(chalk.red('Error: components.json not found.'));
|
|
67
|
+
console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (!options.registry && config.registry) {
|
|
71
|
+
options.registry = config.registry;
|
|
72
|
+
}
|
|
73
|
+
const uiBasePath = aliasToProjectPath(config.aliases.ui || 'src/components/ui');
|
|
74
|
+
const targetDir = resolveProjectPath(cwd, uiBasePath);
|
|
75
|
+
const utilsAlias = config.aliases.utils;
|
|
76
|
+
const names = components.length > 0
|
|
77
|
+
? components
|
|
78
|
+
: getComponentNames();
|
|
79
|
+
let totalDiffs = 0;
|
|
80
|
+
for (const name of names) {
|
|
81
|
+
if (!(name in registry)) {
|
|
82
|
+
console.log(chalk.red(`Unknown component: ${name}`));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const fileDiffs = await diffComponent(name, targetDir, options, utilsAlias);
|
|
86
|
+
if (fileDiffs.length > 0) {
|
|
87
|
+
totalDiffs++;
|
|
88
|
+
console.log(chalk.bold.cyan(`\n${name}:`));
|
|
89
|
+
for (const d of fileDiffs)
|
|
90
|
+
console.log(d);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (totalDiffs === 0) {
|
|
94
|
+
console.log(chalk.green('\nAll installed components are up to date.'));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(chalk.dim(`\n${totalDiffs} component(s) have differences.`));
|
|
98
|
+
}
|
|
99
|
+
}
|
package/dist/commands/help.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { registry } from '../registry/index.js';
|
|
2
|
+
import { registry, getComponentNames } from '../registry/index.js';
|
|
3
3
|
const CATEGORY_ORDER = ['UI', 'Charts', 'Layout / Page Building', 'Animation', 'Kanban'];
|
|
4
4
|
const ANIMATION_COMPONENTS = new Set([
|
|
5
5
|
'gradient-text', 'flip-text', 'meteors', 'shine-border', 'scroll-progress',
|
|
@@ -10,9 +10,9 @@ const ANIMATION_COMPONENTS = new Set([
|
|
|
10
10
|
const KANBAN_COMPONENTS = new Set(['kanban']);
|
|
11
11
|
const LAYOUT_COMPONENTS = new Set(['bento-grid', 'page-builder']);
|
|
12
12
|
function categorize(name) {
|
|
13
|
-
|
|
14
|
-
if (!def)
|
|
13
|
+
if (!(name in registry))
|
|
15
14
|
return 'UI';
|
|
15
|
+
const def = registry[name];
|
|
16
16
|
const hasChartFile = def.files.some(f => f.startsWith('charts/'));
|
|
17
17
|
if (hasChartFile)
|
|
18
18
|
return 'Charts';
|
|
@@ -29,14 +29,14 @@ function buildComponentsByCategory() {
|
|
|
29
29
|
for (const cat of CATEGORY_ORDER) {
|
|
30
30
|
groups.set(cat, []);
|
|
31
31
|
}
|
|
32
|
-
for (const name of
|
|
32
|
+
for (const name of getComponentNames()) {
|
|
33
33
|
const cat = categorize(name);
|
|
34
34
|
const list = groups.get(cat);
|
|
35
35
|
if (list)
|
|
36
36
|
list.push(name);
|
|
37
37
|
}
|
|
38
38
|
for (const list of groups.values()) {
|
|
39
|
-
list.sort();
|
|
39
|
+
list.sort((a, b) => a.localeCompare(b));
|
|
40
40
|
}
|
|
41
41
|
return groups;
|
|
42
42
|
}
|
|
@@ -57,17 +57,26 @@ function buildCommandsSection() {
|
|
|
57
57
|
' ' + chalk.cyan('init') + ' Initialize shadcn-angular in your project',
|
|
58
58
|
' ' + chalk.gray('-y, --yes') + ' Skip confirmation prompt',
|
|
59
59
|
' ' + chalk.gray('-d, --defaults') + ' Use default configuration',
|
|
60
|
+
' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
|
|
60
61
|
' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
|
|
61
62
|
'',
|
|
62
63
|
' ' + chalk.cyan('add') + ' Add component(s) to your project',
|
|
63
64
|
' ' + chalk.gray('[components...]') + ' One or more component names',
|
|
64
|
-
' ' + chalk.gray('-y, --yes') + ' Skip
|
|
65
|
+
' ' + chalk.gray('-y, --yes') + ' Skip prompts and overwrite conflicts',
|
|
65
66
|
' ' + chalk.gray('-o, --overwrite') + ' Overwrite existing files',
|
|
66
67
|
' ' + chalk.gray('-a, --all') + ' Add all available components',
|
|
67
68
|
' ' + chalk.gray('-p, --path') + ' <path> Custom install path',
|
|
68
69
|
' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
|
|
70
|
+
' ' + chalk.gray('--dry-run') + ' Show what would be installed without changes',
|
|
69
71
|
' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
|
|
70
72
|
'',
|
|
73
|
+
' ' + chalk.cyan('diff') + ' Show differences between local and remote versions',
|
|
74
|
+
' ' + chalk.gray('[components...]') + ' Components to diff (all installed if omitted)',
|
|
75
|
+
' ' + chalk.gray('--remote') + ' Force remote fetch from GitHub registry',
|
|
76
|
+
' ' + chalk.gray('-b, --branch') + ' <branch> GitHub branch to fetch from ' + branchDefault,
|
|
77
|
+
'',
|
|
78
|
+
' ' + chalk.cyan('list') + ' List all components and their install status',
|
|
79
|
+
'',
|
|
71
80
|
' ' + chalk.cyan('help') + ' Show this reference',
|
|
72
81
|
'',
|
|
73
82
|
];
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
3
|
import prompts from 'prompts';
|
|
5
4
|
import chalk from 'chalk';
|
|
6
5
|
import ora from 'ora';
|
|
@@ -9,58 +8,187 @@ import { getStylesTemplate } from '../templates/styles.js';
|
|
|
9
8
|
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
9
|
import { installPackages } from '../utils/package-manager.js';
|
|
11
10
|
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
function
|
|
18
|
-
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
19
|
-
if (fs.existsSync(fromDist)) {
|
|
20
|
-
return fromDist;
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
async function fetchLibFileContent(file, branch) {
|
|
11
|
+
import { getLibRegistryBaseUrl, getLocalLibDir, resolveProjectPath, aliasToProjectPath, } from '../utils/paths.js';
|
|
12
|
+
const onCancel = () => {
|
|
13
|
+
console.log(chalk.dim('\nCancelled.'));
|
|
14
|
+
process.exit(0);
|
|
15
|
+
};
|
|
16
|
+
async function fetchLibFileContent(file, branch, remote, registry) {
|
|
25
17
|
const localLibDir = getLocalLibDir();
|
|
26
|
-
if (localLibDir) {
|
|
18
|
+
if (localLibDir && !remote) {
|
|
27
19
|
const localPath = path.join(localLibDir, file);
|
|
28
20
|
if (await fs.pathExists(localPath)) {
|
|
29
21
|
return fs.readFile(localPath, 'utf-8');
|
|
30
22
|
}
|
|
31
23
|
}
|
|
32
|
-
const url = `${getLibRegistryBaseUrl(branch)}/${file}`;
|
|
24
|
+
const url = `${getLibRegistryBaseUrl(branch, registry)}/${file}`;
|
|
33
25
|
const response = await fetch(url);
|
|
34
26
|
if (!response.ok) {
|
|
35
27
|
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
36
28
|
}
|
|
37
29
|
return response.text();
|
|
38
30
|
}
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
function resolveAliasOrPath(cwd, aliasOrPath) {
|
|
32
|
+
return resolveProjectPath(cwd, aliasToProjectPath(aliasOrPath));
|
|
33
|
+
}
|
|
34
|
+
function toAlias(inputPath) {
|
|
35
|
+
return inputPath.startsWith('src/')
|
|
36
|
+
? '@/' + inputPath.slice(4)
|
|
37
|
+
: inputPath;
|
|
38
|
+
}
|
|
39
|
+
async function promptForConfig() {
|
|
40
|
+
const THEME_COLORS = {
|
|
41
|
+
zinc: '#71717a', slate: '#64748b', stone: '#78716c',
|
|
42
|
+
gray: '#6b7280', neutral: '#737373', red: '#ef4444',
|
|
43
|
+
rose: '#f43f5e', orange: '#f97316', green: '#22c55e',
|
|
44
|
+
blue: '#3b82f6', yellow: '#eab308', violet: '#8b5cf6',
|
|
45
|
+
amber: '#d97706',
|
|
46
|
+
};
|
|
47
|
+
const colorSwatch = (value, label) => `${chalk.hex(THEME_COLORS[value])('██')} ${label}`;
|
|
48
|
+
const themeChoices = [
|
|
49
|
+
'Zinc', 'Slate', 'Stone', 'Gray', 'Neutral',
|
|
50
|
+
'Red', 'Rose', 'Orange', 'Green', 'Blue',
|
|
51
|
+
'Yellow', 'Violet', 'Amber',
|
|
52
|
+
].map(label => {
|
|
53
|
+
const value = label.toLowerCase();
|
|
54
|
+
return { title: colorSwatch(value, label), value };
|
|
55
|
+
});
|
|
56
|
+
const baseColorChoices = ['Neutral', 'Slate', 'Stone', 'Gray', 'Zinc'].map(label => {
|
|
57
|
+
const value = label.toLowerCase();
|
|
58
|
+
return { title: colorSwatch(value, label), value };
|
|
59
|
+
});
|
|
60
|
+
const responses = await prompts([
|
|
61
|
+
{
|
|
62
|
+
type: 'select',
|
|
63
|
+
name: 'baseColor',
|
|
64
|
+
message: 'Which color would you like to use as base color?',
|
|
65
|
+
choices: baseColorChoices,
|
|
66
|
+
initial: 0,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: 'select',
|
|
70
|
+
name: 'theme',
|
|
71
|
+
message: 'Which color would you like to use for the main theme?',
|
|
72
|
+
choices: themeChoices,
|
|
73
|
+
initial: (prev) => {
|
|
74
|
+
const index = themeChoices.findIndex(c => c.value === prev);
|
|
75
|
+
return index === -1 ? 0 : index;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
type: 'text',
|
|
80
|
+
name: 'componentsPath',
|
|
81
|
+
message: 'Where would you like to install components?',
|
|
82
|
+
initial: 'src/components/ui',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'text',
|
|
86
|
+
name: 'utilsPath',
|
|
87
|
+
message: 'Where would you like to install utils?',
|
|
88
|
+
initial: 'src/components/lib',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'text',
|
|
92
|
+
name: 'globalCss',
|
|
93
|
+
message: 'Where is your global styles file?',
|
|
94
|
+
initial: 'src/styles.scss',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: 'confirm',
|
|
98
|
+
name: 'createShortcutRegistry',
|
|
99
|
+
message: 'Would you like to create a shortcut registry scaffold?',
|
|
100
|
+
initial: true,
|
|
101
|
+
},
|
|
102
|
+
], { onCancel });
|
|
103
|
+
const componentsAlias = toAlias(responses.componentsPath);
|
|
104
|
+
const uiAlias = componentsAlias.endsWith('/ui')
|
|
105
|
+
? componentsAlias
|
|
106
|
+
: componentsAlias + '/ui';
|
|
107
|
+
return {
|
|
108
|
+
config: {
|
|
109
|
+
style: 'default',
|
|
110
|
+
tailwind: {
|
|
111
|
+
css: responses.globalCss,
|
|
112
|
+
baseColor: responses.baseColor,
|
|
113
|
+
theme: responses.theme,
|
|
114
|
+
cssVariables: true,
|
|
115
|
+
},
|
|
116
|
+
aliases: {
|
|
117
|
+
components: componentsAlias.replace(/\/ui$/, ''),
|
|
118
|
+
utils: toAlias(responses.utilsPath),
|
|
119
|
+
ui: uiAlias,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
createShortcutRegistry: responses.createShortcutRegistry ?? true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function installMissingDeps(cwd) {
|
|
126
|
+
const allDependencies = [
|
|
127
|
+
'clsx', 'tailwind-merge', 'class-variance-authority',
|
|
128
|
+
'tailwindcss', 'postcss', '@tailwindcss/postcss',
|
|
129
|
+
];
|
|
130
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
131
|
+
let missingDeps = allDependencies;
|
|
132
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
133
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
134
|
+
const installed = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
135
|
+
missingDeps = allDependencies.filter(dep => !installed[dep]);
|
|
136
|
+
}
|
|
137
|
+
if (missingDeps.length > 0) {
|
|
138
|
+
await installPackages(missingDeps, { cwd });
|
|
44
139
|
}
|
|
45
|
-
return resolved;
|
|
46
140
|
}
|
|
47
|
-
function
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
141
|
+
async function setupPostcss(cwd) {
|
|
142
|
+
const postcssConfigFiles = [
|
|
143
|
+
'.postcssrc.json', '.postcssrc.js', '.postcssrc.yaml',
|
|
144
|
+
'postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs',
|
|
145
|
+
];
|
|
146
|
+
const existing = (await Promise.all(postcssConfigFiles.map(async (f) => await fs.pathExists(path.join(cwd, f)) ? f : null))).filter(Boolean);
|
|
147
|
+
if (existing.length === 0) {
|
|
148
|
+
await fs.writeJson(path.join(cwd, '.postcssrc.json'), {
|
|
149
|
+
plugins: { '@tailwindcss/postcss': {} },
|
|
150
|
+
}, { spaces: 4 });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!existing.includes('.postcssrc.json')) {
|
|
154
|
+
console.log(chalk.yellow(` Existing PostCSS config found (${existing[0]}). Skipping .postcssrc.json creation.`));
|
|
155
|
+
console.log(chalk.dim(' Make sure @tailwindcss/postcss is configured in your PostCSS config.'));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function autoConfigureTsconfig(cwd, spinner) {
|
|
159
|
+
const tsconfigPath = path.join(cwd, 'tsconfig.json');
|
|
160
|
+
if (!await fs.pathExists(tsconfigPath))
|
|
161
|
+
return;
|
|
162
|
+
try {
|
|
163
|
+
const raw = await fs.readFile(tsconfigPath, 'utf-8');
|
|
164
|
+
const stripped = raw
|
|
165
|
+
.replaceAll(/\/\/.*$/gm, '')
|
|
166
|
+
.replaceAll(/\/\*[\s\S]*?\*\//g, '');
|
|
167
|
+
const tsconfig = JSON.parse(stripped);
|
|
168
|
+
const compilerOptions = tsconfig.compilerOptions ??= {};
|
|
169
|
+
const paths = compilerOptions.paths ??= {};
|
|
170
|
+
if (paths['@/*'])
|
|
171
|
+
return;
|
|
172
|
+
if (!compilerOptions.baseUrl) {
|
|
173
|
+
compilerOptions.baseUrl = '.';
|
|
174
|
+
}
|
|
175
|
+
paths['@/*'] = ['./src/*'];
|
|
176
|
+
await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
|
|
177
|
+
spinner.text = 'Configured tsconfig.json paths';
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
console.log(chalk.dim(' Could not auto-configure tsconfig.json — please add "@/*": ["./src/*"] to paths manually.'));
|
|
181
|
+
}
|
|
52
182
|
}
|
|
53
183
|
export async function init(options) {
|
|
54
184
|
console.log(chalk.bold('\n🎨 Welcome to shadcn-angular!\n'));
|
|
55
185
|
const cwd = process.cwd();
|
|
56
|
-
// Check if this is an Angular project
|
|
57
186
|
const angularJsonPath = path.join(cwd, 'angular.json');
|
|
58
187
|
if (!await fs.pathExists(angularJsonPath)) {
|
|
59
188
|
console.log(chalk.red('Error: This does not appear to be an Angular project.'));
|
|
60
189
|
console.log(chalk.dim('Please run this command in the root of your Angular project.'));
|
|
61
190
|
process.exit(1);
|
|
62
191
|
}
|
|
63
|
-
// Check if already initialized
|
|
64
192
|
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
65
193
|
if (await fs.pathExists(componentsJsonPath)) {
|
|
66
194
|
const overwrite = options.yes
|
|
@@ -70,200 +198,58 @@ export async function init(options) {
|
|
|
70
198
|
name: 'overwrite',
|
|
71
199
|
message: 'components.json already exists. Overwrite?',
|
|
72
200
|
initial: false,
|
|
73
|
-
})).overwrite;
|
|
201
|
+
}, { onCancel })).overwrite;
|
|
74
202
|
if (!overwrite) {
|
|
75
203
|
console.log(chalk.dim('Initialization cancelled.'));
|
|
76
204
|
return;
|
|
77
205
|
}
|
|
78
206
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
else {
|
|
85
|
-
const THEME_COLORS = {
|
|
86
|
-
zinc: '#71717a',
|
|
87
|
-
slate: '#64748b',
|
|
88
|
-
stone: '#78716c',
|
|
89
|
-
gray: '#6b7280',
|
|
90
|
-
neutral: '#737373',
|
|
91
|
-
red: '#ef4444',
|
|
92
|
-
rose: '#f43f5e',
|
|
93
|
-
orange: '#f97316', // bright orange
|
|
94
|
-
green: '#22c55e',
|
|
95
|
-
blue: '#3b82f6',
|
|
96
|
-
yellow: '#eab308',
|
|
97
|
-
violet: '#8b5cf6',
|
|
98
|
-
amber: '#d97706', // warm amber for preview
|
|
99
|
-
};
|
|
100
|
-
const themeChoices = [
|
|
101
|
-
{ title: 'Zinc', value: 'zinc' },
|
|
102
|
-
{ title: 'Slate', value: 'slate' },
|
|
103
|
-
{ title: 'Stone', value: 'stone' },
|
|
104
|
-
{ title: 'Gray', value: 'gray' },
|
|
105
|
-
{ title: 'Neutral', value: 'neutral' },
|
|
106
|
-
{ title: 'Red', value: 'red' },
|
|
107
|
-
{ title: 'Rose', value: 'rose' },
|
|
108
|
-
{ title: 'Orange', value: 'orange' },
|
|
109
|
-
{ title: 'Green', value: 'green' },
|
|
110
|
-
{ title: 'Blue', value: 'blue' },
|
|
111
|
-
{ title: 'Yellow', value: 'yellow' },
|
|
112
|
-
{ title: 'Violet', value: 'violet' },
|
|
113
|
-
{ title: 'Amber', value: 'amber' },
|
|
114
|
-
].map(c => ({
|
|
115
|
-
...c,
|
|
116
|
-
title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
|
|
117
|
-
}));
|
|
118
|
-
const baseColorChoices = [
|
|
119
|
-
{ title: 'Neutral', value: 'neutral' },
|
|
120
|
-
{ title: 'Slate', value: 'slate' },
|
|
121
|
-
{ title: 'Stone', value: 'stone' },
|
|
122
|
-
{ title: 'Gray', value: 'gray' },
|
|
123
|
-
{ title: 'Zinc', value: 'zinc' },
|
|
124
|
-
].map(c => ({
|
|
125
|
-
...c,
|
|
126
|
-
title: `${chalk.hex(THEME_COLORS[c.value])('██')} ${c.title}`
|
|
127
|
-
}));
|
|
128
|
-
const responses = await prompts([
|
|
129
|
-
{
|
|
130
|
-
type: 'select',
|
|
131
|
-
name: 'baseColor',
|
|
132
|
-
message: 'Which color would you like to use as base color?',
|
|
133
|
-
choices: baseColorChoices,
|
|
134
|
-
initial: 0,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
type: 'select',
|
|
138
|
-
name: 'theme',
|
|
139
|
-
message: 'Which color would you like to use for the main theme?',
|
|
140
|
-
choices: themeChoices,
|
|
141
|
-
initial: (prev) => {
|
|
142
|
-
const index = themeChoices.findIndex(c => c.value === prev);
|
|
143
|
-
return index === -1 ? 0 : index;
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
type: 'text',
|
|
148
|
-
name: 'componentsPath',
|
|
149
|
-
message: 'Where would you like to install components?',
|
|
150
|
-
initial: 'src/components/ui',
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
type: 'text',
|
|
154
|
-
name: 'utilsPath',
|
|
155
|
-
message: 'Where would you like to install utils?',
|
|
156
|
-
initial: 'src/components/lib',
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
type: 'text',
|
|
160
|
-
name: 'globalCss',
|
|
161
|
-
message: 'Where is your global styles file?',
|
|
162
|
-
initial: 'src/styles.scss',
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
type: 'confirm',
|
|
166
|
-
name: 'createShortcutRegistry',
|
|
167
|
-
message: 'Would you like to create a shortcut registry scaffold?',
|
|
168
|
-
initial: true,
|
|
169
|
-
},
|
|
170
|
-
]);
|
|
171
|
-
config = {
|
|
172
|
-
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
173
|
-
style: 'default',
|
|
174
|
-
tailwind: {
|
|
175
|
-
css: responses.globalCss,
|
|
176
|
-
baseColor: responses.baseColor,
|
|
177
|
-
theme: responses.theme,
|
|
178
|
-
cssVariables: true,
|
|
179
|
-
},
|
|
180
|
-
aliases: {
|
|
181
|
-
components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
|
|
182
|
-
utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
|
|
183
|
-
ui: responses.componentsPath.replace('src/', '@/'),
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
createShortcutRegistry = responses.createShortcutRegistry ?? true;
|
|
207
|
+
const { config, createShortcutRegistry } = options.defaults || options.yes
|
|
208
|
+
? { config: getDefaultConfig(), createShortcutRegistry: true }
|
|
209
|
+
: await promptForConfig();
|
|
210
|
+
if (options.registry) {
|
|
211
|
+
config.registry = options.registry;
|
|
187
212
|
}
|
|
188
213
|
const spinner = ora('Initializing project...').start();
|
|
189
214
|
try {
|
|
190
|
-
// Write components.json
|
|
191
215
|
await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
|
|
192
216
|
spinner.text = 'Created components.json';
|
|
193
|
-
// Create utils directory and file
|
|
194
|
-
// Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
|
|
195
|
-
// But we have the 'responses' object from CLI prompt only in the else block above!
|
|
196
|
-
// So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
|
|
197
|
-
// If config came from defaults, aliases are set.
|
|
198
|
-
// We can reverse-map alias to path: @/ -> src/
|
|
199
217
|
const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
|
|
200
218
|
await fs.ensureDir(libDir);
|
|
201
219
|
await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
|
|
202
220
|
spinner.text = 'Created utils.ts';
|
|
203
|
-
const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
|
|
204
|
-
const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts', options.branch);
|
|
205
|
-
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
206
|
-
spinner.text = 'Created shortcut-binding.service.ts';
|
|
207
221
|
if (createShortcutRegistry) {
|
|
222
|
+
const content = await fetchLibFileContent('shortcut-binding.service.ts', options.branch, options.remote, options.registry);
|
|
223
|
+
await fs.writeFile(path.join(libDir, 'shortcut-binding.service.ts'), content);
|
|
224
|
+
spinner.text = 'Created shortcut-binding.service.ts';
|
|
208
225
|
await writeShortcutRegistryIndex(cwd, config, []);
|
|
209
226
|
spinner.text = 'Created shortcut-registry.index.ts';
|
|
210
227
|
}
|
|
211
|
-
// Create tailwind.css file in the same directory as the global styles
|
|
212
228
|
const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
|
|
213
229
|
const stylesDir = path.dirname(userStylesPath);
|
|
214
|
-
const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
|
|
215
|
-
// Write the tailwind.css file with all Tailwind directives
|
|
216
230
|
await fs.ensureDir(stylesDir);
|
|
217
|
-
await fs.writeFile(
|
|
231
|
+
await fs.writeFile(path.join(stylesDir, 'tailwind.css'), getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
|
|
218
232
|
spinner.text = 'Created tailwind.css';
|
|
219
|
-
// Add import to the user's global styles file if not already present
|
|
220
233
|
let userStyles = await fs.pathExists(userStylesPath)
|
|
221
234
|
? await fs.readFile(userStylesPath, 'utf-8')
|
|
222
235
|
: '';
|
|
223
|
-
const tailwindImport = '@import "./tailwind.css";';
|
|
224
236
|
if (!userStyles.includes('tailwind.css')) {
|
|
225
|
-
|
|
226
|
-
userStyles = tailwindImport + '\n\n' + userStyles;
|
|
237
|
+
userStyles = '@import "./tailwind.css";\n\n' + userStyles;
|
|
227
238
|
await fs.writeFile(userStylesPath, userStyles);
|
|
228
239
|
spinner.text = 'Added tailwind.css import to styles';
|
|
229
240
|
}
|
|
230
|
-
// Create components/ui directory
|
|
231
241
|
const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
|
|
232
242
|
await fs.ensureDir(uiDir);
|
|
233
243
|
spinner.text = 'Created components directory';
|
|
234
|
-
// Install dependencies
|
|
235
244
|
spinner.text = 'Installing dependencies...';
|
|
236
|
-
|
|
237
|
-
'clsx',
|
|
238
|
-
'tailwind-merge',
|
|
239
|
-
'class-variance-authority',
|
|
240
|
-
'tailwindcss',
|
|
241
|
-
'postcss',
|
|
242
|
-
'@tailwindcss/postcss'
|
|
243
|
-
];
|
|
244
|
-
await installPackages(dependencies, { cwd });
|
|
245
|
-
// Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
|
|
245
|
+
await installMissingDeps(cwd);
|
|
246
246
|
spinner.text = 'Configuring PostCSS...';
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const configContent = {
|
|
250
|
-
plugins: {
|
|
251
|
-
'@tailwindcss/postcss': {}
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
|
|
255
|
-
}
|
|
247
|
+
await setupPostcss(cwd);
|
|
248
|
+
await autoConfigureTsconfig(cwd, spinner);
|
|
256
249
|
spinner.succeed(chalk.green('Project initialized successfully!'));
|
|
257
250
|
console.log('\n' + chalk.bold('Next steps:'));
|
|
258
251
|
console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
|
|
259
252
|
console.log(chalk.dim(' 2. Import and use in your templates'));
|
|
260
|
-
console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
|
|
261
|
-
console.log(chalk.dim(' "compilerOptions": {'));
|
|
262
|
-
console.log(chalk.dim(' "baseUrl": ".",'));
|
|
263
|
-
console.log(chalk.dim(' "paths": {'));
|
|
264
|
-
console.log(chalk.dim(' "@/*": ["./src/*"]'));
|
|
265
|
-
console.log(chalk.dim(' }'));
|
|
266
|
-
console.log(chalk.dim(' }'));
|
|
267
253
|
console.log('');
|
|
268
254
|
}
|
|
269
255
|
catch (error) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function list(): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { getConfig } from '../utils/config.js';
|
|
5
|
+
import { registry, getComponentNames } from '../registry/index.js';
|
|
6
|
+
import { resolveProjectPath, aliasToProjectPath } from '../utils/paths.js';
|
|
7
|
+
export async function list() {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const config = await getConfig(cwd);
|
|
10
|
+
if (!config) {
|
|
11
|
+
console.log(chalk.red('Error: components.json not found.'));
|
|
12
|
+
console.log(chalk.dim('Run `npx @gilav21/shadcn-angular init` first.'));
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const uiBasePath = aliasToProjectPath(config.aliases.ui || 'src/components/ui');
|
|
16
|
+
const targetDir = resolveProjectPath(cwd, uiBasePath);
|
|
17
|
+
const statuses = [];
|
|
18
|
+
for (const name of getComponentNames()) {
|
|
19
|
+
const component = registry[name];
|
|
20
|
+
const allPresent = await allFilesExist(component.files, targetDir);
|
|
21
|
+
statuses.push({
|
|
22
|
+
name,
|
|
23
|
+
status: allPresent ? 'installed' : 'not installed',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const installed = statuses.filter(s => s.status === 'installed').sort((a, b) => a.name.localeCompare(b.name));
|
|
27
|
+
const notInstalled = statuses.filter(s => s.status === 'not installed').sort((a, b) => a.name.localeCompare(b.name));
|
|
28
|
+
console.log(chalk.bold(`\nInstalled (${installed.length}):`));
|
|
29
|
+
if (installed.length === 0) {
|
|
30
|
+
console.log(chalk.dim(' None'));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
for (const s of installed) {
|
|
34
|
+
console.log(chalk.green(' ✓ ') + s.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk.bold(`\nAvailable (${notInstalled.length}):`));
|
|
38
|
+
for (const s of notInstalled) {
|
|
39
|
+
console.log(chalk.dim(' - ') + s.name);
|
|
40
|
+
}
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
async function allFilesExist(files, targetDir) {
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
if (!await fs.pathExists(path.join(targetDir, file))) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|