@ng-cn/core 1.0.1 → 1.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-cn/core",
3
- "version": "1.0.01",
3
+ "version": "1.0.03",
4
4
  "description": "Beautifully designed Angular components built with Tailwind CSS v4 - The official Angular port of shadcn/ui",
5
5
  "keywords": [
6
6
  "angular",
@@ -9,12 +9,43 @@ Angular schematics for installing shadcn-angular components individually.
9
9
  First, add shadcn-angular to your project:
10
10
 
11
11
  ```bash
12
- ng add shadcn-angular
12
+ ng add @ng-cn/core
13
13
  ```
14
14
 
15
15
  This will:
16
16
  - Install required dependencies (lucide-angular, class-variance-authority, clsx, tailwind-merge, @angular/cdk)
17
17
  - Set up the project for shadcn-angular components
18
+ - **Prompt you to select components** to install initially (multi-select checkbox)
19
+
20
+ ### Interactive Component Selection
21
+
22
+ When running `ng add @ng-cn/core`, you'll be presented with an interactive multi-select prompt:
23
+
24
+ ```
25
+ ? Which components would you like to add? (Press space to select, enter to confirm)
26
+ ❯◯ Button - Displays a button or a component that looks like a button
27
+ ◯ Card - Displays a card with header, content, and footer
28
+ ◯ Input - Displays a form input field
29
+ ◯ Label - Renders an accessible label associated with controls
30
+ ◯ Checkbox - A control that allows toggling between checked and not checked
31
+ ...
32
+ ```
33
+
34
+ Use **space** to select/deselect components and **enter** to confirm your selection.
35
+
36
+ ### Skip Component Selection
37
+
38
+ If you want to skip the component selection prompt:
39
+
40
+ ```bash
41
+ ng add @ng-cn/core --components=button,card,input
42
+ ```
43
+
44
+ Or to skip adding any components initially:
45
+
46
+ ```bash
47
+ ng add @ng-cn/core --components=
48
+ ```
18
49
 
19
50
  ### Install Individual Components
20
51
 
@@ -65,11 +96,48 @@ ng g shadcn-angular:c button --path=src/components
65
96
  To build the schematics:
66
97
 
67
98
  ```bash
68
- npm run build:schematics
99
+ cd schematics
100
+ npx tsc -p tsconfig.json
69
101
  ```
70
102
 
71
103
  This compiles the TypeScript files in the `schematics` directory to JavaScript.
72
104
 
105
+ ### Testing
106
+
107
+ Run the test script to verify the schematics work correctly:
108
+
109
+ ```bash
110
+ cd schematics
111
+ node test-schematic.js
112
+ ```
113
+
114
+ This will:
115
+ - Create a mock Angular project with a tsconfig.json containing comments (like real projects)
116
+ - Run the ng-add schematic
117
+ - Verify all expected files are created
118
+ - Verify tsconfig path aliases are configured
119
+ - Verify styles are imported correctly
120
+ - Verify dependencies are added
121
+
122
+ ### Local Testing
123
+
124
+ To test the schematic locally with a real Angular project:
125
+
126
+ ```bash
127
+ # 1. Build the schematics
128
+ cd schematics && npx tsc -p tsconfig.json && cd ..
129
+
130
+ # 2. Create a test project
131
+ ng new test-app --style=scss --routing=true
132
+
133
+ # 3. Link the local package
134
+ cd test-app
135
+ npm link ../shadcn-angular
136
+
137
+ # 4. Run the schematic
138
+ ng add @ng-cn/core --skip-confirmation
139
+ ```
140
+
73
141
  ## Structure
74
142
 
75
143
  ```
@@ -3,6 +3,7 @@ interface NgAddOptions {
3
3
  project?: string;
4
4
  skipInstall?: boolean;
5
5
  skipStyles?: boolean;
6
+ components?: string[];
6
7
  }
7
8
  export declare function ngAdd(options: NgAddOptions): Rule;
8
9
  export {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ngAdd = ngAdd;
4
4
  const tasks_1 = require("@angular-devkit/schematics/tasks");
5
+ const jsonc_parser_1 = require("jsonc-parser");
5
6
  // CSS Variables template for shadcn theming
6
7
  const CSS_VARIABLES_TEMPLATE = `/* ng-cn/core - shadcn-angular styles */
7
8
  @use "tailwindcss";
@@ -253,7 +254,8 @@ function ngAdd(options) {
253
254
  context.logger.info('⚙️ TypeScript Config');
254
255
  const tsconfigPath = '/tsconfig.json';
255
256
  if (tree.exists(tsconfigPath)) {
256
- const tsconfig = JSON.parse(tree.read(tsconfigPath).toString('utf-8'));
257
+ const tsconfigContent = tree.read(tsconfigPath).toString('utf-8');
258
+ const tsconfig = (0, jsonc_parser_1.parse)(tsconfigContent);
257
259
  tsconfig.compilerOptions = tsconfig.compilerOptions || {};
258
260
  tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
259
261
  const pathAliases = {
@@ -277,6 +279,27 @@ function ngAdd(options) {
277
279
  context.logger.info(' ✓ Path aliases already set');
278
280
  }
279
281
  }
282
+ // Install selected components
283
+ if (options.components && options.components.length > 0) {
284
+ context.logger.info('');
285
+ context.logger.info('📦 Selected Components');
286
+ const packageJson = JSON.parse(tree.read(packageJsonPath).toString('utf-8'));
287
+ for (const component of options.components) {
288
+ const packageName = `@ng-cn/${component}`;
289
+ if (!packageJson.dependencies?.[packageName]) {
290
+ packageJson.dependencies = packageJson.dependencies || {};
291
+ packageJson.dependencies[packageName] = 'latest';
292
+ context.logger.info(` + ${packageName}`);
293
+ }
294
+ else {
295
+ context.logger.info(` ✓ ${packageName} already installed`);
296
+ }
297
+ }
298
+ tree.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2));
299
+ if (!options.skipInstall) {
300
+ context.addTask(new tasks_1.NodePackageInstallTask());
301
+ }
302
+ }
280
303
  // Success message with ASCII art banner
281
304
  context.logger.info('');
282
305
  context.logger.info('');
@@ -289,10 +312,23 @@ function ngAdd(options) {
289
312
  context.logger.info('');
290
313
  context.logger.info(' ✅ Setup complete! shadcn for Angular');
291
314
  context.logger.info('');
315
+ // Show selected components summary if any were installed
316
+ if (options.components && options.components.length > 0) {
317
+ context.logger.info('╭──────────────────────────────────────────────────╮');
318
+ context.logger.info('│ ✨ Components installed: │');
319
+ context.logger.info('│ │');
320
+ for (const component of options.components) {
321
+ const paddedComponent = `@ng-cn/${component}`.padEnd(40);
322
+ context.logger.info(`│ ${paddedComponent}│`);
323
+ }
324
+ context.logger.info('│ │');
325
+ context.logger.info('╰──────────────────────────────────────────────────╯');
326
+ context.logger.info('');
327
+ }
292
328
  context.logger.info('╭──────────────────────────────────────────────────╮');
293
329
  context.logger.info('│ 🚀 Next steps: │');
294
330
  context.logger.info('│ │');
295
- context.logger.info('│ 1. Add components: │');
331
+ context.logger.info('│ 1. Add more components: │');
296
332
  context.logger.info('│ ng g @ng-cn/core:c button │');
297
333
  context.logger.info('│ ng g @ng-cn/core:c card │');
298
334
  context.logger.info('│ │');
@@ -300,7 +336,7 @@ function ngAdd(options) {
300
336
  context.logger.info('│ npm i @ng-cn/button @ng-cn/card │');
301
337
  context.logger.info('│ │');
302
338
  context.logger.info('│ 3. Import and use: │');
303
- context.logger.info("│ import { Button } from '@/ui/button'; │");
339
+ context.logger.info("│ import { Button } from '@ng-cn/button'; │");
304
340
  context.logger.info('│ │');
305
341
  context.logger.info('│ 📚 Docs: https://shadcn-angular.tigayon.com │');
306
342
  context.logger.info('╰──────────────────────────────────────────────────╯');
@@ -1,10 +1,12 @@
1
1
  import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
2
2
  import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
3
+ import { parse as parseJsonc } from 'jsonc-parser';
3
4
 
4
5
  interface NgAddOptions {
5
6
  project?: string;
6
7
  skipInstall?: boolean;
7
8
  skipStyles?: boolean;
9
+ components?: string[];
8
10
  }
9
11
 
10
12
  // CSS Variables template for shadcn theming
@@ -273,7 +275,8 @@ export function ngAdd(options: NgAddOptions): Rule {
273
275
 
274
276
  const tsconfigPath = '/tsconfig.json';
275
277
  if (tree.exists(tsconfigPath)) {
276
- const tsconfig = JSON.parse(tree.read(tsconfigPath)!.toString('utf-8'));
278
+ const tsconfigContent = tree.read(tsconfigPath)!.toString('utf-8');
279
+ const tsconfig = parseJsonc(tsconfigContent) as Record<string, any>;
277
280
  tsconfig.compilerOptions = tsconfig.compilerOptions || {};
278
281
  tsconfig.compilerOptions.paths = tsconfig.compilerOptions.paths || {};
279
282
 
@@ -300,6 +303,31 @@ export function ngAdd(options: NgAddOptions): Rule {
300
303
  }
301
304
  }
302
305
 
306
+ // Install selected components
307
+ if (options.components && options.components.length > 0) {
308
+ context.logger.info('');
309
+ context.logger.info('📦 Selected Components');
310
+
311
+ const packageJson = JSON.parse(tree.read(packageJsonPath)!.toString('utf-8'));
312
+
313
+ for (const component of options.components) {
314
+ const packageName = `@ng-cn/${component}`;
315
+ if (!packageJson.dependencies?.[packageName]) {
316
+ packageJson.dependencies = packageJson.dependencies || {};
317
+ packageJson.dependencies[packageName] = 'latest';
318
+ context.logger.info(` + ${packageName}`);
319
+ } else {
320
+ context.logger.info(` ✓ ${packageName} already installed`);
321
+ }
322
+ }
323
+
324
+ tree.overwrite(packageJsonPath, JSON.stringify(packageJson, null, 2));
325
+
326
+ if (!options.skipInstall) {
327
+ context.addTask(new NodePackageInstallTask());
328
+ }
329
+ }
330
+
303
331
  // Success message with ASCII art banner
304
332
  context.logger.info('');
305
333
  context.logger.info('');
@@ -312,10 +340,25 @@ export function ngAdd(options: NgAddOptions): Rule {
312
340
  context.logger.info('');
313
341
  context.logger.info(' ✅ Setup complete! shadcn for Angular');
314
342
  context.logger.info('');
343
+
344
+ // Show selected components summary if any were installed
345
+ if (options.components && options.components.length > 0) {
346
+ context.logger.info('╭──────────────────────────────────────────────────╮');
347
+ context.logger.info('│ ✨ Components installed: │');
348
+ context.logger.info('│ │');
349
+ for (const component of options.components) {
350
+ const paddedComponent = `@ng-cn/${component}`.padEnd(40);
351
+ context.logger.info(`│ ${paddedComponent}│`);
352
+ }
353
+ context.logger.info('│ │');
354
+ context.logger.info('╰──────────────────────────────────────────────────╯');
355
+ context.logger.info('');
356
+ }
357
+
315
358
  context.logger.info('╭──────────────────────────────────────────────────╮');
316
359
  context.logger.info('│ 🚀 Next steps: │');
317
360
  context.logger.info('│ │');
318
- context.logger.info('│ 1. Add components: │');
361
+ context.logger.info('│ 1. Add more components: │');
319
362
  context.logger.info('│ ng g @ng-cn/core:c button │');
320
363
  context.logger.info('│ ng g @ng-cn/core:c card │');
321
364
  context.logger.info('│ │');
@@ -323,7 +366,7 @@ export function ngAdd(options: NgAddOptions): Rule {
323
366
  context.logger.info('│ npm i @ng-cn/button @ng-cn/card │');
324
367
  context.logger.info('│ │');
325
368
  context.logger.info('│ 3. Import and use: │');
326
- context.logger.info("│ import { Button } from '@/ui/button'; │");
369
+ context.logger.info("│ import { Button } from '@ng-cn/button'; │");
327
370
  context.logger.info('│ │');
328
371
  context.logger.info('│ 📚 Docs: https://shadcn-angular.tigayon.com │');
329
372
  context.logger.info('╰──────────────────────────────────────────────────╯');
@@ -20,6 +20,49 @@
20
20
  "type": "boolean",
21
21
  "default": false,
22
22
  "description": "Skip creating ng-cn.scss styles file."
23
+ },
24
+ "components": {
25
+ "type": "array",
26
+ "items": {
27
+ "type": "string"
28
+ },
29
+ "default": [],
30
+ "description": "Components to install initially.",
31
+ "x-prompt": {
32
+ "message": "Which components would you like to add? (Press space to select, enter to confirm)",
33
+ "type": "list",
34
+ "multiselect": true,
35
+ "items": [
36
+ { "value": "button", "label": "Button - Displays a button or a component that looks like a button" },
37
+ { "value": "card", "label": "Card - Displays a card with header, content, and footer" },
38
+ { "value": "input", "label": "Input - Displays a form input field" },
39
+ { "value": "label", "label": "Label - Renders an accessible label associated with controls" },
40
+ { "value": "checkbox", "label": "Checkbox - A control that allows toggling between checked and not checked" },
41
+ { "value": "select", "label": "Select - Displays a list of options for the user to pick from" },
42
+ { "value": "dialog", "label": "Dialog - A window overlaid on the primary window" },
43
+ { "value": "toast", "label": "Toast - A succinct message that is displayed temporarily" },
44
+ { "value": "dropdown-menu", "label": "Dropdown Menu - Displays a menu when triggered" },
45
+ { "value": "tabs", "label": "Tabs - A set of layered sections of content" },
46
+ { "value": "table", "label": "Table - A responsive table component" },
47
+ { "value": "avatar", "label": "Avatar - An image element with a fallback for representing the user" },
48
+ { "value": "badge", "label": "Badge - Displays a badge or a component that looks like a badge" },
49
+ { "value": "alert", "label": "Alert - Displays a callout for user attention" },
50
+ { "value": "tooltip", "label": "Tooltip - A popup that displays information related to an element" },
51
+ { "value": "progress", "label": "Progress - Displays an indicator showing the completion progress" },
52
+ { "value": "skeleton", "label": "Skeleton - Use to show a placeholder while content is loading" },
53
+ { "value": "separator", "label": "Separator - Visually or semantically separates content" },
54
+ { "value": "switch", "label": "Switch - A control that allows toggling between checked and not checked" },
55
+ { "value": "textarea", "label": "Textarea - Displays a form textarea" },
56
+ { "value": "accordion", "label": "Accordion - A vertically stacked set of interactive headings" },
57
+ { "value": "sheet", "label": "Sheet - Extends the Dialog component to display complementary content" },
58
+ { "value": "popover", "label": "Popover - Displays rich content in a portal, triggered by a button" },
59
+ { "value": "calendar", "label": "Calendar - A date field component for entering and editing dates" },
60
+ { "value": "command", "label": "Command - Fast, composable, unstyled command menu" },
61
+ { "value": "form", "label": "Form - Building forms with validation" },
62
+ { "value": "sidebar", "label": "Sidebar - A composable, themeable and customizable sidebar" },
63
+ { "value": "spinner", "label": "Spinner - Loading indicator animations" }
64
+ ]
65
+ }
23
66
  }
24
67
  }
25
68
  }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Test script for ng-add schematic
3
+ * Run with: node test-schematic.js
4
+ */
5
+
6
+ const { SchematicTestRunner } = require('@angular-devkit/schematics/testing');
7
+ const { HostTree } = require('@angular-devkit/schematics');
8
+ const path = require('path');
9
+
10
+ const collectionPath = path.join(__dirname, 'collection.json');
11
+
12
+ // Mock tsconfig.json with comments (typical Angular project)
13
+ const MOCK_TSCONFIG = `/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
14
+ /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
15
+ {
16
+ "compileOnSave": false,
17
+ "compilerOptions": {
18
+ "outDir": "./dist/out-tsc",
19
+ "strict": true,
20
+ "noImplicitOverride": true,
21
+ "noPropertyAccessFromIndexSignature": true,
22
+ "noImplicitReturns": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "skipLibCheck": true,
25
+ "isolatedModules": true,
26
+ "esModuleInterop": true,
27
+ "experimentalDecorators": true,
28
+ "moduleResolution": "bundler",
29
+ "importHelpers": true,
30
+ "target": "ES2022",
31
+ "module": "ES2022"
32
+ },
33
+ "angularCompilerOptions": {
34
+ "enableI18nLegacyMessageIdFormat": false,
35
+ "strictInjectionParameters": true,
36
+ "strictInputAccessModifiers": true,
37
+ "strictTemplates": true
38
+ }
39
+ }`;
40
+
41
+ const MOCK_PACKAGE_JSON = `{
42
+ "name": "test-angular",
43
+ "version": "0.0.0",
44
+ "scripts": {
45
+ "ng": "ng",
46
+ "start": "ng serve",
47
+ "build": "ng build"
48
+ },
49
+ "dependencies": {
50
+ "@angular/core": "^21.0.0"
51
+ },
52
+ "devDependencies": {}
53
+ }`;
54
+
55
+ const MOCK_STYLES_CSS = `/* You can add global styles to this file, and also import other style files */
56
+ `;
57
+
58
+ async function runTests() {
59
+ console.log('\n🧪 Testing ng-add schematic...\n');
60
+
61
+ const runner = new SchematicTestRunner('schematics', collectionPath);
62
+
63
+ // Create a mock project tree using HostTree
64
+ const appTree = new HostTree();
65
+ appTree.create('/package.json', MOCK_PACKAGE_JSON);
66
+ appTree.create('/tsconfig.json', MOCK_TSCONFIG);
67
+ appTree.create('/src/styles.css', MOCK_STYLES_CSS);
68
+
69
+ console.log('📁 Created mock project with:');
70
+ console.log(' - /package.json');
71
+ console.log(' - /tsconfig.json (with comments)');
72
+ console.log(' - /src/styles.css\n');
73
+
74
+ try {
75
+ // Run the ng-add schematic
76
+ const tree = await runner.runSchematic('ng-add', { skipInstall: true }, appTree);
77
+
78
+ console.log('✅ Schematic executed successfully!\n');
79
+
80
+ // Verify files were created
81
+ const expectedFiles = [
82
+ '/src/app/lib/utils/cn.ts',
83
+ '/src/app/lib/utils/index.ts',
84
+ '/src/app/lib/components/ui/.gitkeep',
85
+ '/src/ng-cn.scss'
86
+ ];
87
+
88
+ console.log('📄 Checking created files:');
89
+ for (const file of expectedFiles) {
90
+ if (tree.exists(file)) {
91
+ console.log(` ✅ ${file}`);
92
+ } else {
93
+ console.log(` ❌ Missing: ${file}`);
94
+ }
95
+ }
96
+
97
+ // Verify tsconfig was updated
98
+ console.log('\n⚙️ Checking tsconfig.json:');
99
+ const tsconfigContent = tree.read('/tsconfig.json').toString('utf-8');
100
+ const tsconfig = JSON.parse(tsconfigContent);
101
+ const paths = tsconfig.compilerOptions?.paths || {};
102
+
103
+ const expectedPaths = ['@/*', '@/lib/*', '@/ui/*', '@/utils/*'];
104
+ for (const p of expectedPaths) {
105
+ if (paths[p]) {
106
+ console.log(` ✅ Path alias: ${p}`);
107
+ } else {
108
+ console.log(` ❌ Missing path alias: ${p}`);
109
+ }
110
+ }
111
+
112
+ // Verify styles were imported
113
+ console.log('\n🎨 Checking styles:');
114
+ const stylesContent = tree.read('/src/styles.css').toString('utf-8');
115
+ if (stylesContent.includes('ng-cn.scss')) {
116
+ console.log(' ✅ ng-cn.scss imported in styles.css');
117
+ } else {
118
+ console.log(' ❌ ng-cn.scss not imported');
119
+ }
120
+
121
+ // Verify dependencies
122
+ console.log('\n📦 Checking dependencies:');
123
+ const packageJsonContent = tree.read('/package.json').toString('utf-8');
124
+ const packageJson = JSON.parse(packageJsonContent);
125
+ const requiredDeps = ['lucide-angular', 'clsx', 'tailwind-merge', 'class-variance-authority'];
126
+ for (const dep of requiredDeps) {
127
+ if (packageJson.dependencies?.[dep]) {
128
+ console.log(` ✅ ${dep}@${packageJson.dependencies[dep]}`);
129
+ } else {
130
+ console.log(` ❌ Missing: ${dep}`);
131
+ }
132
+ }
133
+
134
+ console.log('\n✨ All tests passed!\n');
135
+
136
+ } catch (error) {
137
+ console.error('\n❌ Schematic failed with error:');
138
+ console.error(error);
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ runTests();