@gilav21/shadcn-angular 0.0.21 ā 0.0.23
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 +13 -1
- package/dist/commands/add.js +39 -20
- package/dist/commands/add.spec.js +301 -11
- package/dist/registry/index.js +3 -0
- package/package.json +1 -1
- package/src/commands/add.spec.ts +441 -12
- package/src/commands/add.ts +621 -598
- package/src/commands/init.ts +314 -314
- package/src/registry/index.ts +3 -0
package/src/commands/init.ts
CHANGED
|
@@ -1,314 +1,314 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import prompts from 'prompts';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import ora from 'ora';
|
|
7
|
-
import { getDefaultConfig, type Config } from '../utils/config.js';
|
|
8
|
-
import { getStylesTemplate } from '../templates/styles.js';
|
|
9
|
-
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
|
-
import { installPackages } from '../utils/package-manager.js';
|
|
11
|
-
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
12
|
-
|
|
13
|
-
function getLibRegistryBaseUrl(branch: string) {
|
|
14
|
-
return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/lib`;
|
|
15
|
-
}
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
|
-
|
|
19
|
-
function getLocalLibDir(): string | null {
|
|
20
|
-
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
21
|
-
if (fs.existsSync(fromDist)) {
|
|
22
|
-
return fromDist;
|
|
23
|
-
}
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async function fetchLibFileContent(file: string, branch: string): Promise<string> {
|
|
28
|
-
const localLibDir = getLocalLibDir();
|
|
29
|
-
if (localLibDir) {
|
|
30
|
-
const localPath = path.join(localLibDir, file);
|
|
31
|
-
if (await fs.pathExists(localPath)) {
|
|
32
|
-
return fs.readFile(localPath, 'utf-8');
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const url = `${getLibRegistryBaseUrl(branch)}/${file}`;
|
|
37
|
-
const response = await fetch(url);
|
|
38
|
-
if (!response.ok) {
|
|
39
|
-
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
40
|
-
}
|
|
41
|
-
return response.text();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface InitOptions {
|
|
45
|
-
yes?: boolean;
|
|
46
|
-
defaults?: boolean;
|
|
47
|
-
branch: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveProjectPath(cwd: string, inputPath: string): string {
|
|
51
|
-
const resolved = path.resolve(cwd, inputPath);
|
|
52
|
-
const relative = path.relative(cwd, resolved);
|
|
53
|
-
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
54
|
-
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
55
|
-
}
|
|
56
|
-
return resolved;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
|
|
60
|
-
const normalized = aliasOrPath.startsWith('@/')
|
|
61
|
-
? path.join('src', aliasOrPath.slice(2))
|
|
62
|
-
: aliasOrPath;
|
|
63
|
-
return resolveProjectPath(cwd, normalized);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function init(options: InitOptions) {
|
|
67
|
-
console.log(chalk.bold('\nšØ Welcome to shadcn-angular!\n'));
|
|
68
|
-
|
|
69
|
-
const cwd = process.cwd();
|
|
70
|
-
|
|
71
|
-
// Check if this is an Angular project
|
|
72
|
-
const angularJsonPath = path.join(cwd, 'angular.json');
|
|
73
|
-
if (!await fs.pathExists(angularJsonPath)) {
|
|
74
|
-
console.log(chalk.red('Error: This does not appear to be an Angular project.'));
|
|
75
|
-
console.log(chalk.dim('Please run this command in the root of your Angular project.'));
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check if already initialized
|
|
80
|
-
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
81
|
-
if (await fs.pathExists(componentsJsonPath)) {
|
|
82
|
-
const overwrite = options.yes
|
|
83
|
-
? true
|
|
84
|
-
: (await prompts({
|
|
85
|
-
type: 'confirm',
|
|
86
|
-
name: 'overwrite',
|
|
87
|
-
message: 'components.json already exists. Overwrite?',
|
|
88
|
-
initial: false,
|
|
89
|
-
})).overwrite;
|
|
90
|
-
if (!overwrite) {
|
|
91
|
-
console.log(chalk.dim('Initialization cancelled.'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let config: Config;
|
|
97
|
-
let createShortcutRegistry = true;
|
|
98
|
-
|
|
99
|
-
if (options.defaults || options.yes) {
|
|
100
|
-
config = getDefaultConfig();
|
|
101
|
-
} else {
|
|
102
|
-
const THEME_COLORS: Record<string, string> = {
|
|
103
|
-
zinc: '#71717a',
|
|
104
|
-
slate: '#64748b',
|
|
105
|
-
stone: '#78716c',
|
|
106
|
-
gray: '#6b7280',
|
|
107
|
-
neutral: '#737373',
|
|
108
|
-
red: '#ef4444',
|
|
109
|
-
rose: '#f43f5e',
|
|
110
|
-
orange: '#f97316', // bright orange
|
|
111
|
-
green: '#22c55e',
|
|
112
|
-
blue: '#3b82f6',
|
|
113
|
-
yellow: '#eab308',
|
|
114
|
-
violet: '#8b5cf6',
|
|
115
|
-
amber: '#d97706', // warm amber for preview
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const themeChoices = [
|
|
119
|
-
{ title: 'Zinc', value: 'zinc' },
|
|
120
|
-
{ title: 'Slate', value: 'slate' },
|
|
121
|
-
{ title: 'Stone', value: 'stone' },
|
|
122
|
-
{ title: 'Gray', value: 'gray' },
|
|
123
|
-
{ title: 'Neutral', value: 'neutral' },
|
|
124
|
-
{ title: 'Red', value: 'red' },
|
|
125
|
-
{ title: 'Rose', value: 'rose' },
|
|
126
|
-
{ title: 'Orange', value: 'orange' },
|
|
127
|
-
{ title: 'Green', value: 'green' },
|
|
128
|
-
{ title: 'Blue', value: 'blue' },
|
|
129
|
-
{ title: 'Yellow', value: 'yellow' },
|
|
130
|
-
{ title: 'Violet', value: 'violet' },
|
|
131
|
-
{ title: 'Amber', value: 'amber' },
|
|
132
|
-
].map(c => ({
|
|
133
|
-
...c,
|
|
134
|
-
title: `${chalk.hex(THEME_COLORS[c.value])('āā')} ${c.title}`
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
const baseColorChoices = [
|
|
138
|
-
{ title: 'Neutral', value: 'neutral' },
|
|
139
|
-
{ title: 'Slate', value: 'slate' },
|
|
140
|
-
{ title: 'Stone', value: 'stone' },
|
|
141
|
-
{ title: 'Gray', value: 'gray' },
|
|
142
|
-
{ title: 'Zinc', value: 'zinc' },
|
|
143
|
-
].map(c => ({
|
|
144
|
-
...c,
|
|
145
|
-
title: `${chalk.hex(THEME_COLORS[c.value])('āā')} ${c.title}`
|
|
146
|
-
}));
|
|
147
|
-
|
|
148
|
-
const responses = await prompts([
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
type: 'select',
|
|
152
|
-
name: 'baseColor',
|
|
153
|
-
message: 'Which color would you like to use as base color?',
|
|
154
|
-
choices: baseColorChoices,
|
|
155
|
-
initial: 0,
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
type: 'select',
|
|
159
|
-
name: 'theme',
|
|
160
|
-
message: 'Which color would you like to use for the main theme?',
|
|
161
|
-
choices: themeChoices,
|
|
162
|
-
initial: (prev: string) => {
|
|
163
|
-
const index = themeChoices.findIndex(c => c.value === prev);
|
|
164
|
-
return index === -1 ? 0 : index;
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
type: 'text',
|
|
169
|
-
name: 'componentsPath',
|
|
170
|
-
message: 'Where would you like to install components?',
|
|
171
|
-
initial: 'src/components/ui',
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
type: 'text',
|
|
175
|
-
name: 'utilsPath',
|
|
176
|
-
message: 'Where would you like to install utils?',
|
|
177
|
-
initial: 'src/components/lib',
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
type: 'text',
|
|
181
|
-
name: 'globalCss',
|
|
182
|
-
message: 'Where is your global styles file?',
|
|
183
|
-
initial: 'src/styles.scss',
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
type: 'confirm',
|
|
187
|
-
name: 'createShortcutRegistry',
|
|
188
|
-
message: 'Would you like to create a shortcut registry scaffold?',
|
|
189
|
-
initial: true,
|
|
190
|
-
},
|
|
191
|
-
]);
|
|
192
|
-
|
|
193
|
-
config = {
|
|
194
|
-
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
195
|
-
style: 'default',
|
|
196
|
-
tailwind: {
|
|
197
|
-
css: responses.globalCss,
|
|
198
|
-
baseColor: responses.baseColor,
|
|
199
|
-
theme: responses.theme,
|
|
200
|
-
cssVariables: true,
|
|
201
|
-
},
|
|
202
|
-
aliases: {
|
|
203
|
-
components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
|
|
204
|
-
utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
|
|
205
|
-
ui: responses.componentsPath.replace('src/', '@/'),
|
|
206
|
-
},
|
|
207
|
-
};
|
|
208
|
-
createShortcutRegistry = responses.createShortcutRegistry ?? true;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const spinner = ora('Initializing project...').start();
|
|
212
|
-
|
|
213
|
-
try {
|
|
214
|
-
// Write components.json
|
|
215
|
-
await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
|
|
216
|
-
spinner.text = 'Created components.json';
|
|
217
|
-
|
|
218
|
-
// Create utils directory and file
|
|
219
|
-
// Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
|
|
220
|
-
// But we have the 'responses' object from CLI prompt only in the else block above!
|
|
221
|
-
// So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
|
|
222
|
-
// If config came from defaults, aliases are set.
|
|
223
|
-
// We can reverse-map alias to path: @/ -> src/
|
|
224
|
-
|
|
225
|
-
const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
|
|
226
|
-
|
|
227
|
-
await fs.ensureDir(libDir);
|
|
228
|
-
await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
|
|
229
|
-
spinner.text = 'Created utils.ts';
|
|
230
|
-
|
|
231
|
-
const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
|
|
232
|
-
const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts', options.branch);
|
|
233
|
-
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
234
|
-
spinner.text = 'Created shortcut-binding.service.ts';
|
|
235
|
-
|
|
236
|
-
if (createShortcutRegistry) {
|
|
237
|
-
await writeShortcutRegistryIndex(cwd, config, []);
|
|
238
|
-
spinner.text = 'Created shortcut-registry.index.ts';
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Create tailwind.css file in the same directory as the global styles
|
|
242
|
-
const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
|
|
243
|
-
const stylesDir = path.dirname(userStylesPath);
|
|
244
|
-
const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
|
|
245
|
-
|
|
246
|
-
// Write the tailwind.css file with all Tailwind directives
|
|
247
|
-
await fs.ensureDir(stylesDir);
|
|
248
|
-
await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
|
|
249
|
-
spinner.text = 'Created tailwind.css';
|
|
250
|
-
|
|
251
|
-
// Add import to the user's global styles file if not already present
|
|
252
|
-
let userStyles = await fs.pathExists(userStylesPath)
|
|
253
|
-
? await fs.readFile(userStylesPath, 'utf-8')
|
|
254
|
-
: '';
|
|
255
|
-
|
|
256
|
-
const tailwindImport = '@import "./tailwind.css";';
|
|
257
|
-
if (!userStyles.includes('tailwind.css')) {
|
|
258
|
-
// Add import at the top of the file
|
|
259
|
-
userStyles = tailwindImport + '\n\n' + userStyles;
|
|
260
|
-
await fs.writeFile(userStylesPath, userStyles);
|
|
261
|
-
spinner.text = 'Added tailwind.css import to styles';
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Create components/ui directory
|
|
265
|
-
const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
|
|
266
|
-
await fs.ensureDir(uiDir);
|
|
267
|
-
spinner.text = 'Created components directory';
|
|
268
|
-
|
|
269
|
-
// Install dependencies
|
|
270
|
-
spinner.text = 'Installing dependencies...';
|
|
271
|
-
const dependencies = [
|
|
272
|
-
'clsx',
|
|
273
|
-
'tailwind-merge',
|
|
274
|
-
'class-variance-authority',
|
|
275
|
-
'tailwindcss',
|
|
276
|
-
'postcss',
|
|
277
|
-
'@tailwindcss/postcss'
|
|
278
|
-
];
|
|
279
|
-
await installPackages(dependencies, { cwd });
|
|
280
|
-
|
|
281
|
-
// Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
|
|
282
|
-
spinner.text = 'Configuring PostCSS...';
|
|
283
|
-
const postcssrcPath = path.join(cwd, '.postcssrc.json');
|
|
284
|
-
|
|
285
|
-
if (!await fs.pathExists(postcssrcPath)) {
|
|
286
|
-
const configContent = {
|
|
287
|
-
plugins: {
|
|
288
|
-
'@tailwindcss/postcss': {}
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
spinner.succeed(chalk.green('Project initialized successfully!'));
|
|
296
|
-
|
|
297
|
-
console.log('\n' + chalk.bold('Next steps:'));
|
|
298
|
-
console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
|
|
299
|
-
console.log(chalk.dim(' 2. Import and use in your templates'));
|
|
300
|
-
console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
|
|
301
|
-
console.log(chalk.dim(' "compilerOptions": {'));
|
|
302
|
-
console.log(chalk.dim(' "baseUrl": ".",'));
|
|
303
|
-
console.log(chalk.dim(' "paths": {'));
|
|
304
|
-
console.log(chalk.dim(' "@/*": ["./src/*"]'));
|
|
305
|
-
console.log(chalk.dim(' }'));
|
|
306
|
-
console.log(chalk.dim(' }'));
|
|
307
|
-
console.log('');
|
|
308
|
-
|
|
309
|
-
} catch (error) {
|
|
310
|
-
spinner.fail('Failed to initialize project');
|
|
311
|
-
console.error(error);
|
|
312
|
-
process.exit(1);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import prompts from 'prompts';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { getDefaultConfig, type Config } from '../utils/config.js';
|
|
8
|
+
import { getStylesTemplate } from '../templates/styles.js';
|
|
9
|
+
import { getUtilsTemplate } from '../templates/utils.js';
|
|
10
|
+
import { installPackages } from '../utils/package-manager.js';
|
|
11
|
+
import { writeShortcutRegistryIndex } from '../utils/shortcut-registry.js';
|
|
12
|
+
|
|
13
|
+
function getLibRegistryBaseUrl(branch: string) {
|
|
14
|
+
return `https://raw.githubusercontent.com/gilav21/shadcn-angular/${branch}/packages/components/lib`;
|
|
15
|
+
}
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
function getLocalLibDir(): string | null {
|
|
20
|
+
const fromDist = path.resolve(__dirname, '../../../components/lib');
|
|
21
|
+
if (fs.existsSync(fromDist)) {
|
|
22
|
+
return fromDist;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function fetchLibFileContent(file: string, branch: string): Promise<string> {
|
|
28
|
+
const localLibDir = getLocalLibDir();
|
|
29
|
+
if (localLibDir) {
|
|
30
|
+
const localPath = path.join(localLibDir, file);
|
|
31
|
+
if (await fs.pathExists(localPath)) {
|
|
32
|
+
return fs.readFile(localPath, 'utf-8');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const url = `${getLibRegistryBaseUrl(branch)}/${file}`;
|
|
37
|
+
const response = await fetch(url);
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`Failed to fetch library file from ${url}: ${response.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
return response.text();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface InitOptions {
|
|
45
|
+
yes?: boolean;
|
|
46
|
+
defaults?: boolean;
|
|
47
|
+
branch: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveProjectPath(cwd: string, inputPath: string): string {
|
|
51
|
+
const resolved = path.resolve(cwd, inputPath);
|
|
52
|
+
const relative = path.relative(cwd, resolved);
|
|
53
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
54
|
+
throw new Error(`Path must stay inside the project directory: ${inputPath}`);
|
|
55
|
+
}
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveAliasOrPath(cwd: string, aliasOrPath: string): string {
|
|
60
|
+
const normalized = aliasOrPath.startsWith('@/')
|
|
61
|
+
? path.join('src', aliasOrPath.slice(2))
|
|
62
|
+
: aliasOrPath;
|
|
63
|
+
return resolveProjectPath(cwd, normalized);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function init(options: InitOptions) {
|
|
67
|
+
console.log(chalk.bold('\nšØ Welcome to shadcn-angular!\n'));
|
|
68
|
+
|
|
69
|
+
const cwd = process.cwd();
|
|
70
|
+
|
|
71
|
+
// Check if this is an Angular project
|
|
72
|
+
const angularJsonPath = path.join(cwd, 'angular.json');
|
|
73
|
+
if (!await fs.pathExists(angularJsonPath)) {
|
|
74
|
+
console.log(chalk.red('Error: This does not appear to be an Angular project.'));
|
|
75
|
+
console.log(chalk.dim('Please run this command in the root of your Angular project.'));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if already initialized
|
|
80
|
+
const componentsJsonPath = path.join(cwd, 'components.json');
|
|
81
|
+
if (await fs.pathExists(componentsJsonPath)) {
|
|
82
|
+
const overwrite = options.yes
|
|
83
|
+
? true
|
|
84
|
+
: (await prompts({
|
|
85
|
+
type: 'confirm',
|
|
86
|
+
name: 'overwrite',
|
|
87
|
+
message: 'components.json already exists. Overwrite?',
|
|
88
|
+
initial: false,
|
|
89
|
+
})).overwrite;
|
|
90
|
+
if (!overwrite) {
|
|
91
|
+
console.log(chalk.dim('Initialization cancelled.'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let config: Config;
|
|
97
|
+
let createShortcutRegistry = true;
|
|
98
|
+
|
|
99
|
+
if (options.defaults || options.yes) {
|
|
100
|
+
config = getDefaultConfig();
|
|
101
|
+
} else {
|
|
102
|
+
const THEME_COLORS: Record<string, string> = {
|
|
103
|
+
zinc: '#71717a',
|
|
104
|
+
slate: '#64748b',
|
|
105
|
+
stone: '#78716c',
|
|
106
|
+
gray: '#6b7280',
|
|
107
|
+
neutral: '#737373',
|
|
108
|
+
red: '#ef4444',
|
|
109
|
+
rose: '#f43f5e',
|
|
110
|
+
orange: '#f97316', // bright orange
|
|
111
|
+
green: '#22c55e',
|
|
112
|
+
blue: '#3b82f6',
|
|
113
|
+
yellow: '#eab308',
|
|
114
|
+
violet: '#8b5cf6',
|
|
115
|
+
amber: '#d97706', // warm amber for preview
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const themeChoices = [
|
|
119
|
+
{ title: 'Zinc', value: 'zinc' },
|
|
120
|
+
{ title: 'Slate', value: 'slate' },
|
|
121
|
+
{ title: 'Stone', value: 'stone' },
|
|
122
|
+
{ title: 'Gray', value: 'gray' },
|
|
123
|
+
{ title: 'Neutral', value: 'neutral' },
|
|
124
|
+
{ title: 'Red', value: 'red' },
|
|
125
|
+
{ title: 'Rose', value: 'rose' },
|
|
126
|
+
{ title: 'Orange', value: 'orange' },
|
|
127
|
+
{ title: 'Green', value: 'green' },
|
|
128
|
+
{ title: 'Blue', value: 'blue' },
|
|
129
|
+
{ title: 'Yellow', value: 'yellow' },
|
|
130
|
+
{ title: 'Violet', value: 'violet' },
|
|
131
|
+
{ title: 'Amber', value: 'amber' },
|
|
132
|
+
].map(c => ({
|
|
133
|
+
...c,
|
|
134
|
+
title: `${chalk.hex(THEME_COLORS[c.value])('āā')} ${c.title}`
|
|
135
|
+
}));
|
|
136
|
+
|
|
137
|
+
const baseColorChoices = [
|
|
138
|
+
{ title: 'Neutral', value: 'neutral' },
|
|
139
|
+
{ title: 'Slate', value: 'slate' },
|
|
140
|
+
{ title: 'Stone', value: 'stone' },
|
|
141
|
+
{ title: 'Gray', value: 'gray' },
|
|
142
|
+
{ title: 'Zinc', value: 'zinc' },
|
|
143
|
+
].map(c => ({
|
|
144
|
+
...c,
|
|
145
|
+
title: `${chalk.hex(THEME_COLORS[c.value])('āā')} ${c.title}`
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
const responses = await prompts([
|
|
149
|
+
|
|
150
|
+
{
|
|
151
|
+
type: 'select',
|
|
152
|
+
name: 'baseColor',
|
|
153
|
+
message: 'Which color would you like to use as base color?',
|
|
154
|
+
choices: baseColorChoices,
|
|
155
|
+
initial: 0,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type: 'select',
|
|
159
|
+
name: 'theme',
|
|
160
|
+
message: 'Which color would you like to use for the main theme?',
|
|
161
|
+
choices: themeChoices,
|
|
162
|
+
initial: (prev: string) => {
|
|
163
|
+
const index = themeChoices.findIndex(c => c.value === prev);
|
|
164
|
+
return index === -1 ? 0 : index;
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
type: 'text',
|
|
169
|
+
name: 'componentsPath',
|
|
170
|
+
message: 'Where would you like to install components?',
|
|
171
|
+
initial: 'src/components/ui',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
type: 'text',
|
|
175
|
+
name: 'utilsPath',
|
|
176
|
+
message: 'Where would you like to install utils?',
|
|
177
|
+
initial: 'src/components/lib',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
type: 'text',
|
|
181
|
+
name: 'globalCss',
|
|
182
|
+
message: 'Where is your global styles file?',
|
|
183
|
+
initial: 'src/styles.scss',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
type: 'confirm',
|
|
187
|
+
name: 'createShortcutRegistry',
|
|
188
|
+
message: 'Would you like to create a shortcut registry scaffold?',
|
|
189
|
+
initial: true,
|
|
190
|
+
},
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
config = {
|
|
194
|
+
$schema: 'https://shadcn-angular.dev/schema.json',
|
|
195
|
+
style: 'default',
|
|
196
|
+
tailwind: {
|
|
197
|
+
css: responses.globalCss,
|
|
198
|
+
baseColor: responses.baseColor,
|
|
199
|
+
theme: responses.theme,
|
|
200
|
+
cssVariables: true,
|
|
201
|
+
},
|
|
202
|
+
aliases: {
|
|
203
|
+
components: responses.componentsPath.replace('src/', '@/'), // Basic heuristic
|
|
204
|
+
utils: responses.utilsPath.replace('src/', '@/').replace('.ts', ''),
|
|
205
|
+
ui: responses.componentsPath.replace('src/', '@/'),
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
createShortcutRegistry = responses.createShortcutRegistry ?? true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const spinner = ora('Initializing project...').start();
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
// Write components.json
|
|
215
|
+
await fs.writeJson(componentsJsonPath, config, { spaces: 2 });
|
|
216
|
+
spinner.text = 'Created components.json';
|
|
217
|
+
|
|
218
|
+
// Create utils directory and file
|
|
219
|
+
// Resolve path from the config alias, assuming @/ maps to src/ logic for file creation if not provided directly
|
|
220
|
+
// But we have the 'responses' object from CLI prompt only in the else block above!
|
|
221
|
+
// So we should rely on config to reconstruct the path, or better yet, if we are in 'defaults' mode, check what config is.
|
|
222
|
+
// If config came from defaults, aliases are set.
|
|
223
|
+
// We can reverse-map alias to path: @/ -> src/
|
|
224
|
+
|
|
225
|
+
const libDir = resolveAliasOrPath(cwd, config.aliases.utils);
|
|
226
|
+
|
|
227
|
+
await fs.ensureDir(libDir);
|
|
228
|
+
await fs.writeFile(path.join(libDir, 'utils.ts'), getUtilsTemplate());
|
|
229
|
+
spinner.text = 'Created utils.ts';
|
|
230
|
+
|
|
231
|
+
const shortcutServicePath = path.join(libDir, 'shortcut-binding.service.ts');
|
|
232
|
+
const shortcutServiceContent = await fetchLibFileContent('shortcut-binding.service.ts', options.branch);
|
|
233
|
+
await fs.writeFile(shortcutServicePath, shortcutServiceContent);
|
|
234
|
+
spinner.text = 'Created shortcut-binding.service.ts';
|
|
235
|
+
|
|
236
|
+
if (createShortcutRegistry) {
|
|
237
|
+
await writeShortcutRegistryIndex(cwd, config, []);
|
|
238
|
+
spinner.text = 'Created shortcut-registry.index.ts';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create tailwind.css file in the same directory as the global styles
|
|
242
|
+
const userStylesPath = resolveProjectPath(cwd, config.tailwind.css);
|
|
243
|
+
const stylesDir = path.dirname(userStylesPath);
|
|
244
|
+
const tailwindCssPath = path.join(stylesDir, 'tailwind.css');
|
|
245
|
+
|
|
246
|
+
// Write the tailwind.css file with all Tailwind directives
|
|
247
|
+
await fs.ensureDir(stylesDir);
|
|
248
|
+
await fs.writeFile(tailwindCssPath, getStylesTemplate(config.tailwind.baseColor, config.tailwind.theme));
|
|
249
|
+
spinner.text = 'Created tailwind.css';
|
|
250
|
+
|
|
251
|
+
// Add import to the user's global styles file if not already present
|
|
252
|
+
let userStyles = await fs.pathExists(userStylesPath)
|
|
253
|
+
? await fs.readFile(userStylesPath, 'utf-8')
|
|
254
|
+
: '';
|
|
255
|
+
|
|
256
|
+
const tailwindImport = '@import "./tailwind.css";';
|
|
257
|
+
if (!userStyles.includes('tailwind.css')) {
|
|
258
|
+
// Add import at the top of the file
|
|
259
|
+
userStyles = tailwindImport + '\n\n' + userStyles;
|
|
260
|
+
await fs.writeFile(userStylesPath, userStyles);
|
|
261
|
+
spinner.text = 'Added tailwind.css import to styles';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create components/ui directory
|
|
265
|
+
const uiDir = resolveAliasOrPath(cwd, config.aliases.ui);
|
|
266
|
+
await fs.ensureDir(uiDir);
|
|
267
|
+
spinner.text = 'Created components directory';
|
|
268
|
+
|
|
269
|
+
// Install dependencies
|
|
270
|
+
spinner.text = 'Installing dependencies...';
|
|
271
|
+
const dependencies = [
|
|
272
|
+
'clsx',
|
|
273
|
+
'tailwind-merge',
|
|
274
|
+
'class-variance-authority',
|
|
275
|
+
'tailwindcss',
|
|
276
|
+
'postcss',
|
|
277
|
+
'@tailwindcss/postcss'
|
|
278
|
+
];
|
|
279
|
+
await installPackages(dependencies, { cwd });
|
|
280
|
+
|
|
281
|
+
// Setup PostCSS - create .postcssrc.json which is the preferred format for Angular
|
|
282
|
+
spinner.text = 'Configuring PostCSS...';
|
|
283
|
+
const postcssrcPath = path.join(cwd, '.postcssrc.json');
|
|
284
|
+
|
|
285
|
+
if (!await fs.pathExists(postcssrcPath)) {
|
|
286
|
+
const configContent = {
|
|
287
|
+
plugins: {
|
|
288
|
+
'@tailwindcss/postcss': {}
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
await fs.writeJson(postcssrcPath, configContent, { spaces: 4 });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
spinner.succeed(chalk.green('Project initialized successfully!'));
|
|
296
|
+
|
|
297
|
+
console.log('\n' + chalk.bold('Next steps:'));
|
|
298
|
+
console.log(chalk.dim(' 1. Add components: ') + chalk.cyan('npx @gilav21/shadcn-angular add button'));
|
|
299
|
+
console.log(chalk.dim(' 2. Import and use in your templates'));
|
|
300
|
+
console.log(chalk.dim(' 3. Update your ') + chalk.bold('tsconfig.json') + chalk.dim(' paths:'));
|
|
301
|
+
console.log(chalk.dim(' "compilerOptions": {'));
|
|
302
|
+
console.log(chalk.dim(' "baseUrl": ".",'));
|
|
303
|
+
console.log(chalk.dim(' "paths": {'));
|
|
304
|
+
console.log(chalk.dim(' "@/*": ["./src/*"]'));
|
|
305
|
+
console.log(chalk.dim(' }'));
|
|
306
|
+
console.log(chalk.dim(' }'));
|
|
307
|
+
console.log('');
|
|
308
|
+
|
|
309
|
+
} catch (error) {
|
|
310
|
+
spinner.fail('Failed to initialize project');
|
|
311
|
+
console.error(error);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
}
|
package/src/registry/index.ts
CHANGED
|
@@ -149,6 +149,7 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
149
149
|
'data-table/data-table.component.ts',
|
|
150
150
|
'data-table/data-table-column-header.component.ts',
|
|
151
151
|
'data-table/data-table-pagination.component.ts',
|
|
152
|
+
'data-table/data-table-multiselect-filter.component.ts',
|
|
152
153
|
'data-table/data-table.types.ts',
|
|
153
154
|
'data-table/data-table.utils.ts',
|
|
154
155
|
'data-table/index.ts',
|
|
@@ -166,6 +167,8 @@ export const registry: Record<string, ComponentDefinition> = {
|
|
|
166
167
|
'popover',
|
|
167
168
|
'component-outlet',
|
|
168
169
|
'icon',
|
|
170
|
+
'command',
|
|
171
|
+
'badge',
|
|
169
172
|
],
|
|
170
173
|
libFiles: ['xlsx.ts'],
|
|
171
174
|
optionalDependencies: [
|