@xanahlight/component-forge 0.1.0 β†’ 1.4.0

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.
Files changed (70) hide show
  1. package/README.md +8 -0
  2. package/dist/commands/check/check.test.d.ts +1 -0
  3. package/dist/commands/check/check.test.js +151 -0
  4. package/dist/commands/check/check.test.js.map +1 -0
  5. package/dist/commands/check/index.d.ts +15 -0
  6. package/dist/commands/check/index.js +186 -0
  7. package/dist/commands/check/index.js.map +1 -0
  8. package/dist/commands/explain/explain.test.d.ts +1 -0
  9. package/dist/commands/explain/explain.test.js +57 -0
  10. package/dist/commands/explain/explain.test.js.map +1 -0
  11. package/dist/commands/explain/index.d.ts +12 -0
  12. package/dist/commands/explain/index.js +37 -0
  13. package/dist/commands/explain/index.js.map +1 -0
  14. package/dist/commands/explain/topics.d.ts +3 -0
  15. package/dist/commands/explain/topics.js +138 -0
  16. package/dist/commands/explain/topics.js.map +1 -0
  17. package/dist/commands/generate/index.d.ts +6 -0
  18. package/dist/commands/generate/index.js +100 -0
  19. package/dist/commands/generate/index.js.map +1 -0
  20. package/dist/commands/init/index.d.ts +29 -0
  21. package/dist/commands/init/index.js +157 -0
  22. package/dist/commands/init/index.js.map +1 -0
  23. package/dist/commands/migrate/classifier.d.ts +22 -0
  24. package/dist/commands/migrate/classifier.js +48 -0
  25. package/dist/commands/migrate/classifier.js.map +1 -0
  26. package/dist/commands/migrate/index.d.ts +8 -0
  27. package/dist/commands/migrate/index.js +33 -0
  28. package/dist/commands/migrate/index.js.map +1 -0
  29. package/dist/commands/migrate/migrate.test.d.ts +1 -0
  30. package/dist/commands/migrate/migrate.test.js +176 -0
  31. package/dist/commands/migrate/migrate.test.js.map +1 -0
  32. package/dist/commands/migrate/plan-builder.d.ts +27 -0
  33. package/dist/commands/migrate/plan-builder.js +106 -0
  34. package/dist/commands/migrate/plan-builder.js.map +1 -0
  35. package/dist/commands/migrate/printer.d.ts +6 -0
  36. package/dist/commands/migrate/printer.js +46 -0
  37. package/dist/commands/migrate/printer.js.map +1 -0
  38. package/dist/commands/validate/index.d.ts +26 -0
  39. package/dist/commands/validate/index.js +122 -0
  40. package/dist/commands/validate/index.js.map +1 -0
  41. package/dist/commands/validate/validate.test.d.ts +1 -0
  42. package/dist/commands/validate/validate.test.js +163 -0
  43. package/dist/commands/validate/validate.test.js.map +1 -0
  44. package/dist/index.js +54 -10
  45. package/dist/index.js.map +1 -1
  46. package/dist/shared/format.d.ts +26 -0
  47. package/dist/shared/format.js +40 -0
  48. package/dist/shared/format.js.map +1 -0
  49. package/dist/templates/files.d.ts +1 -1
  50. package/dist/templates/files.js +61 -9
  51. package/dist/templates/files.js.map +1 -1
  52. package/dist/templates/files.test.d.ts +1 -0
  53. package/dist/templates/files.test.js +174 -0
  54. package/dist/templates/files.test.js.map +1 -0
  55. package/dist/types/folder-tree.d.ts +31 -2
  56. package/dist/types/folder-tree.js +18 -0
  57. package/dist/types/folder-tree.js.map +1 -1
  58. package/dist/utils/config.d.ts +19 -6
  59. package/dist/utils/config.js +76 -14
  60. package/dist/utils/config.js.map +1 -1
  61. package/dist/utils/config.test.d.ts +1 -0
  62. package/dist/utils/config.test.js +117 -0
  63. package/dist/utils/config.test.js.map +1 -0
  64. package/dist/utils/template-resolver.d.ts +1 -1
  65. package/dist/utils/template-resolver.js +1 -1
  66. package/dist/utils/template-resolver.js.map +1 -1
  67. package/dist/utils/template-resolver.test.d.ts +1 -0
  68. package/dist/utils/template-resolver.test.js +86 -0
  69. package/dist/utils/template-resolver.test.js.map +1 -0
  70. package/package.json +13 -5
package/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  🌐 English | [Русский](README.ru.md)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@xanahlight/component-forge.svg)](https://www.npmjs.com/package/@xanahlight/component-forge)
6
+ [![CI](https://github.com/vladislavprozorov/component-forge/actions/workflows/ci.yml/badge.svg)](https://github.com/vladislavprozorov/component-forge/actions/workflows/ci.yml)
6
7
  [![Node.js](https://img.shields.io/node/v/@xanahlight/component-forge.svg)](https://nodejs.org)
7
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
9
 
@@ -236,6 +237,13 @@ npm run build
236
237
  node dist/index.js init fsd
237
238
  ```
238
239
 
240
+ Run tests:
241
+
242
+ ```bash
243
+ npm test
244
+ npm run test:coverage
245
+ ```
246
+
239
247
  > Node.js 20+ required.
240
248
 
241
249
  ---
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_os_1 = __importDefault(require("node:os"));
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const vitest_1 = require("vitest");
10
+ const index_1 = require("./index");
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+ let tmpDir;
15
+ function write(relPath, content) {
16
+ const full = node_path_1.default.join(tmpDir, relPath);
17
+ fs_extra_1.default.ensureDirSync(node_path_1.default.dirname(full));
18
+ fs_extra_1.default.writeFileSync(full, content);
19
+ }
20
+ (0, vitest_1.beforeEach)(() => {
21
+ tmpDir = fs_extra_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'cf-check-'));
22
+ });
23
+ (0, vitest_1.afterEach)(() => {
24
+ fs_extra_1.default.removeSync(tmpDir);
25
+ });
26
+ // ---------------------------------------------------------------------------
27
+ // parseImports
28
+ // ---------------------------------------------------------------------------
29
+ (0, vitest_1.describe)('parseImports', () => {
30
+ (0, vitest_1.it)('extracts named imports', () => {
31
+ const src = `import { foo } from './foo'\nimport type { Bar } from '../bar'`;
32
+ (0, vitest_1.expect)((0, index_1.parseImports)(src)).toEqual(['./foo', '../bar']);
33
+ });
34
+ (0, vitest_1.it)('extracts default imports', () => {
35
+ (0, vitest_1.expect)((0, index_1.parseImports)(`import React from 'react'`)).toEqual(['react']);
36
+ });
37
+ (0, vitest_1.it)('extracts re-exports', () => {
38
+ (0, vitest_1.expect)((0, index_1.parseImports)(`export { foo } from './foo'`)).toEqual(['./foo']);
39
+ });
40
+ (0, vitest_1.it)('ignores non-import lines', () => {
41
+ (0, vitest_1.expect)((0, index_1.parseImports)(`const x = 1\nconsole.log(x)`)).toEqual([]);
42
+ });
43
+ });
44
+ // ---------------------------------------------------------------------------
45
+ // resolveLayer
46
+ // ---------------------------------------------------------------------------
47
+ (0, vitest_1.describe)('resolveLayer', () => {
48
+ (0, vitest_1.it)('returns first path segment as layer', () => {
49
+ (0, vitest_1.expect)((0, index_1.resolveLayer)('features/auth/index.ts')).toBe('features');
50
+ (0, vitest_1.expect)((0, index_1.resolveLayer)('shared/ui/Button.tsx')).toBe('shared');
51
+ });
52
+ (0, vitest_1.it)('returns single-segment layer', () => {
53
+ (0, vitest_1.expect)((0, index_1.resolveLayer)('app')).toBe('app');
54
+ });
55
+ });
56
+ // ---------------------------------------------------------------------------
57
+ // collectSourceFiles
58
+ // ---------------------------------------------------------------------------
59
+ (0, vitest_1.describe)('collectSourceFiles', () => {
60
+ (0, vitest_1.it)('finds ts and tsx files', () => {
61
+ write('features/auth/index.ts', '');
62
+ write('shared/ui/Button.tsx', '');
63
+ write('shared/ui/Button.test.ts', ''); // should be excluded
64
+ const files = (0, index_1.collectSourceFiles)(tmpDir);
65
+ (0, vitest_1.expect)(files).toContain(node_path_1.default.join('features', 'auth', 'index.ts'));
66
+ (0, vitest_1.expect)(files).toContain(node_path_1.default.join('shared', 'ui', 'Button.tsx'));
67
+ (0, vitest_1.expect)(files.some((f) => f.endsWith('.test.ts'))).toBe(false);
68
+ });
69
+ (0, vitest_1.it)('returns empty array for non-existent dir', () => {
70
+ (0, vitest_1.expect)((0, index_1.collectSourceFiles)('/non/existent/path')).toEqual([]);
71
+ });
72
+ });
73
+ // ---------------------------------------------------------------------------
74
+ // FSD violations
75
+ // ---------------------------------------------------------------------------
76
+ (0, vitest_1.describe)('runCheck β€” FSD', () => {
77
+ (0, vitest_1.it)('passes when lower layer imports from even lower layer (entities β†’ shared)', () => {
78
+ write('entities/user/index.ts', `import { Button } from '../../shared/ui/Button'`);
79
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
80
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
81
+ (0, vitest_1.expect)(result.checkedFiles).toBe(1);
82
+ });
83
+ (0, vitest_1.it)('detects feature importing from feature (same level)', () => {
84
+ write('features/auth/index.ts', `import { foo } from '../../features/search/index'`);
85
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
86
+ (0, vitest_1.expect)(result.violations).toHaveLength(1);
87
+ (0, vitest_1.expect)(result.violations[0].message).toMatch(/must not import from "features"/);
88
+ });
89
+ (0, vitest_1.it)('detects entity importing from feature (higher level)', () => {
90
+ write('entities/user/index.ts', `import { login } from '../../features/auth/index'`);
91
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
92
+ (0, vitest_1.expect)(result.violations).toHaveLength(1);
93
+ (0, vitest_1.expect)(result.violations[0].message).toMatch(/must not import from "features"/);
94
+ });
95
+ (0, vitest_1.it)('detects shared importing from entities (higher level)', () => {
96
+ write('shared/ui/Avatar.ts', `import { User } from '../../entities/user/model'`);
97
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
98
+ (0, vitest_1.expect)(result.violations).toHaveLength(1);
99
+ (0, vitest_1.expect)(result.violations[0].message).toMatch(/must not import from "entities"/);
100
+ });
101
+ (0, vitest_1.it)('passes when feature imports from entities (lower level)', () => {
102
+ write('features/auth/index.ts', `import { User } from '../../entities/user/index'`);
103
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
104
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
105
+ });
106
+ (0, vitest_1.it)('passes when pages imports from widgets (lower level)', () => {
107
+ write('pages/home/index.ts', `import { Header } from '../../widgets/header/index'`);
108
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
109
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
110
+ });
111
+ (0, vitest_1.it)('ignores external (non-relative) imports', () => {
112
+ write('features/auth/index.ts', `import React from 'react'`);
113
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
114
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
115
+ });
116
+ (0, vitest_1.it)('reports multiple violations across multiple files', () => {
117
+ write('entities/user/index.ts', `import { login } from '../../features/auth/index'`);
118
+ write('shared/ui/Button.ts', `import { User } from '../../entities/user/model'`);
119
+ const result = (0, index_1.runCheck)(tmpDir, 'fsd');
120
+ (0, vitest_1.expect)(result.violations).toHaveLength(2);
121
+ (0, vitest_1.expect)(result.checkedFiles).toBe(2);
122
+ });
123
+ });
124
+ // ---------------------------------------------------------------------------
125
+ // Modular violations
126
+ // ---------------------------------------------------------------------------
127
+ (0, vitest_1.describe)('runCheck β€” modular', () => {
128
+ (0, vitest_1.it)('passes when module imports from another module', () => {
129
+ write('modules/auth/index.ts', `import { foo } from '../../modules/ui/index'`);
130
+ const result = (0, index_1.runCheck)(tmpDir, 'modular');
131
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
132
+ });
133
+ (0, vitest_1.it)('detects shared importing from modules', () => {
134
+ write('shared/utils/helper.ts', `import { login } from '../../modules/auth/index'`);
135
+ const result = (0, index_1.runCheck)(tmpDir, 'modular');
136
+ (0, vitest_1.expect)(result.violations).toHaveLength(1);
137
+ (0, vitest_1.expect)(result.violations[0].message).toMatch(/must not import from "modules"/);
138
+ });
139
+ (0, vitest_1.it)('detects core importing from modules', () => {
140
+ write('core/router.ts', `import { AuthModule } from '../modules/auth/index'`);
141
+ const result = (0, index_1.runCheck)(tmpDir, 'modular');
142
+ (0, vitest_1.expect)(result.violations).toHaveLength(1);
143
+ (0, vitest_1.expect)(result.violations[0].message).toMatch(/must not import from "modules"/);
144
+ });
145
+ (0, vitest_1.it)('passes when module imports from shared', () => {
146
+ write('modules/auth/index.ts', `import { Button } from '../../shared/ui/Button'`);
147
+ const result = (0, index_1.runCheck)(tmpDir, 'modular');
148
+ (0, vitest_1.expect)(result.violations).toHaveLength(0);
149
+ });
150
+ });
151
+ //# sourceMappingURL=check.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.test.js","sourceRoot":"","sources":["../../../src/commands/check/check.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Architecture } from '../../types/folder-tree';
2
+ export interface CheckViolation {
3
+ file: string;
4
+ importPath: string;
5
+ message: string;
6
+ }
7
+ export interface CheckResult {
8
+ violations: CheckViolation[];
9
+ checkedFiles: number;
10
+ }
11
+ export declare function parseImports(source: string): string[];
12
+ export declare function resolveLayer(relPath: string): string | null;
13
+ export declare function collectSourceFiles(dir: string, base?: string): string[];
14
+ export declare function runCheck(srcPath: string, architecture: Architecture): CheckResult;
15
+ export declare function checkCommand(): void;
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseImports = parseImports;
7
+ exports.resolveLayer = resolveLayer;
8
+ exports.collectSourceFiles = collectSourceFiles;
9
+ exports.runCheck = runCheck;
10
+ exports.checkCommand = checkCommand;
11
+ const node_fs_1 = __importDefault(require("node:fs"));
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const config_1 = require("../../utils/config");
15
+ const logger_1 = require("../../utils/logger");
16
+ // ---------------------------------------------------------------------------
17
+ // FSD layer hierarchy β€” higher index = higher layer
18
+ // A layer may only import from layers with a LOWER index.
19
+ // ---------------------------------------------------------------------------
20
+ const FSD_LAYER_ORDER = [
21
+ 'shared',
22
+ 'entities',
23
+ 'features',
24
+ 'widgets',
25
+ 'pages',
26
+ 'processes',
27
+ 'app',
28
+ ];
29
+ // For modular architecture β€” no strict hierarchy between modules,
30
+ // but modules must not import from app-level dirs and shared cannot
31
+ // import from modules.
32
+ const MODULAR_FORBIDDEN = {
33
+ shared: ['modules', 'core'],
34
+ core: ['modules'],
35
+ };
36
+ // ---------------------------------------------------------------------------
37
+ // Import parsing β€” extracts all static import paths from a TS/TSX file
38
+ // ---------------------------------------------------------------------------
39
+ const IMPORT_RE = /^\s*(?:import|export)\s+(?:[^'"]*\s+from\s+)?['"]([^'"]+)['"]/gm;
40
+ function parseImports(source) {
41
+ const imports = [];
42
+ let match;
43
+ const re = new RegExp(IMPORT_RE.source, IMPORT_RE.flags);
44
+ while ((match = re.exec(source)) !== null) {
45
+ imports.push(match[1]);
46
+ }
47
+ return imports;
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Layer resolution β€” given a file path relative to srcDir, returns its layer
51
+ // ---------------------------------------------------------------------------
52
+ function resolveLayer(relPath) {
53
+ // relPath looks like "features/auth/index.ts" or "shared/ui/Button.ts"
54
+ const parts = relPath.split(node_path_1.default.sep);
55
+ return parts[0] ?? null;
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // FSD violation check
59
+ // ---------------------------------------------------------------------------
60
+ function checkFsdViolations(srcPath, relFilePath, imports) {
61
+ const violations = [];
62
+ const fileLayer = resolveLayer(relFilePath);
63
+ if (!fileLayer)
64
+ return violations;
65
+ const fileLayerIdx = FSD_LAYER_ORDER.indexOf(fileLayer);
66
+ if (fileLayerIdx === -1)
67
+ return violations; // unknown layer β€” skip
68
+ for (const imp of imports) {
69
+ // Only check relative imports that could cross layer boundaries
70
+ // e.g. "../../features/auth" β€” resolve to get the target layer
71
+ if (!imp.startsWith('.'))
72
+ continue;
73
+ const fileDir = node_path_1.default.dirname(node_path_1.default.join(srcPath, relFilePath));
74
+ const resolved = node_path_1.default.resolve(fileDir, imp);
75
+ const relResolved = node_path_1.default.relative(srcPath, resolved);
76
+ const targetLayer = resolveLayer(relResolved);
77
+ if (!targetLayer)
78
+ continue;
79
+ const targetLayerIdx = FSD_LAYER_ORDER.indexOf(targetLayer);
80
+ if (targetLayerIdx === -1)
81
+ continue; // unknown target layer
82
+ // Violation: importing from the same layer OR a higher layer
83
+ if (targetLayerIdx >= fileLayerIdx) {
84
+ violations.push({
85
+ file: relFilePath,
86
+ importPath: imp,
87
+ message: `Layer "${fileLayer}" must not import from "${targetLayer}" ` +
88
+ `(${targetLayer} is at the same level or higher in FSD hierarchy)`,
89
+ });
90
+ }
91
+ }
92
+ return violations;
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Modular violation check
96
+ // ---------------------------------------------------------------------------
97
+ function checkModularViolations(srcPath, relFilePath, imports) {
98
+ const violations = [];
99
+ const fileLayer = resolveLayer(relFilePath);
100
+ if (!fileLayer)
101
+ return violations;
102
+ const forbidden = MODULAR_FORBIDDEN[fileLayer];
103
+ if (!forbidden)
104
+ return violations;
105
+ for (const imp of imports) {
106
+ if (!imp.startsWith('.'))
107
+ continue;
108
+ const fileDir = node_path_1.default.dirname(node_path_1.default.join(srcPath, relFilePath));
109
+ const resolved = node_path_1.default.resolve(fileDir, imp);
110
+ const relResolved = node_path_1.default.relative(srcPath, resolved);
111
+ const targetLayer = resolveLayer(relResolved);
112
+ if (!targetLayer)
113
+ continue;
114
+ if (forbidden.includes(targetLayer)) {
115
+ violations.push({
116
+ file: relFilePath,
117
+ importPath: imp,
118
+ message: `Layer "${fileLayer}" must not import from "${targetLayer}" in modular architecture`,
119
+ });
120
+ }
121
+ }
122
+ return violations;
123
+ }
124
+ // ---------------------------------------------------------------------------
125
+ // File collector β€” recursively finds .ts / .tsx files under srcPath
126
+ // ---------------------------------------------------------------------------
127
+ function collectSourceFiles(dir, base = dir) {
128
+ const results = [];
129
+ if (!node_fs_1.default.existsSync(dir))
130
+ return results;
131
+ for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
132
+ const full = node_path_1.default.join(dir, entry.name);
133
+ if (entry.isDirectory()) {
134
+ results.push(...collectSourceFiles(full, base));
135
+ }
136
+ else if (/\.(ts|tsx)$/.test(entry.name) && !entry.name.endsWith('.test.ts')) {
137
+ results.push(node_path_1.default.relative(base, full));
138
+ }
139
+ }
140
+ return results;
141
+ }
142
+ // ---------------------------------------------------------------------------
143
+ // Core check logic β€” exported for testing
144
+ // ---------------------------------------------------------------------------
145
+ function runCheck(srcPath, architecture) {
146
+ const files = collectSourceFiles(srcPath);
147
+ const violations = [];
148
+ for (const relFile of files) {
149
+ const fullPath = node_path_1.default.join(srcPath, relFile);
150
+ const source = node_fs_1.default.readFileSync(fullPath, 'utf8');
151
+ const imports = parseImports(source);
152
+ if (architecture === 'fsd') {
153
+ violations.push(...checkFsdViolations(srcPath, relFile, imports));
154
+ }
155
+ else {
156
+ violations.push(...checkModularViolations(srcPath, relFile, imports));
157
+ }
158
+ }
159
+ return { violations, checkedFiles: files.length };
160
+ }
161
+ // ---------------------------------------------------------------------------
162
+ // Command entry point
163
+ // ---------------------------------------------------------------------------
164
+ function checkCommand() {
165
+ const config = (0, config_1.loadProjectConfig)();
166
+ const srcPath = node_path_1.default.join(process.cwd(), config.srcDir);
167
+ logger_1.logger.info(`Checking architecture boundaries (${config.architecture})…\n`);
168
+ const { violations, checkedFiles } = runCheck(srcPath, config.architecture);
169
+ if (violations.length === 0) {
170
+ logger_1.logger.success(`βœ“ No violations found in ${checkedFiles} file(s). Architecture looks clean!`);
171
+ return;
172
+ }
173
+ console.log(chalk_1.default.red(`βœ– Found ${violations.length} violation(s) in ${checkedFiles} file(s):\n`));
174
+ for (const v of violations) {
175
+ console.log(chalk_1.default.bold(chalk_1.default.white(` ${v.file}`)));
176
+ console.log(chalk_1.default.gray(` import "${v.importPath}"`));
177
+ console.log(chalk_1.default.red(` ${v.message}\n`));
178
+ }
179
+ logger_1.logger.info(chalk_1.default.yellow('Tip: ') +
180
+ 'Each layer should only import from layers below it in the hierarchy.');
181
+ if (config.architecture === 'fsd') {
182
+ logger_1.logger.info(chalk_1.default.gray(' FSD order (low β†’ high): shared β†’ entities β†’ features β†’ widgets β†’ pages β†’ app'));
183
+ }
184
+ process.exit(1);
185
+ }
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/check/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const index_1 = require("./index");
5
+ (0, vitest_1.describe)('renderTopic', () => {
6
+ (0, vitest_1.it)('renders fsd topic with key sections', () => {
7
+ const output = (0, index_1.renderTopic)('fsd');
8
+ (0, vitest_1.expect)(output).toContain('Feature-Sliced Design');
9
+ (0, vitest_1.expect)(output).toContain('shared');
10
+ (0, vitest_1.expect)(output).toContain('entities');
11
+ (0, vitest_1.expect)(output).toContain('features');
12
+ (0, vitest_1.expect)(output).toContain('widgets');
13
+ (0, vitest_1.expect)(output).toContain('pages');
14
+ (0, vitest_1.expect)(output).toContain('app');
15
+ (0, vitest_1.expect)(output).toContain('Import rules');
16
+ });
17
+ (0, vitest_1.it)('renders modular topic with key sections', () => {
18
+ const output = (0, index_1.renderTopic)('modular');
19
+ (0, vitest_1.expect)(output).toContain('Modular Architecture');
20
+ (0, vitest_1.expect)(output).toContain('modules');
21
+ (0, vitest_1.expect)(output).toContain('core');
22
+ (0, vitest_1.expect)(output).toContain('shared');
23
+ (0, vitest_1.expect)(output).toContain('Import rules');
24
+ });
25
+ (0, vitest_1.it)('renders layers topic with FSD layer reference', () => {
26
+ const output = (0, index_1.renderTopic)('layers');
27
+ (0, vitest_1.expect)(output).toContain('FSD Layer Reference');
28
+ (0, vitest_1.expect)(output).toContain('shared');
29
+ (0, vitest_1.expect)(output).toContain('entities');
30
+ (0, vitest_1.expect)(output).toContain('features');
31
+ });
32
+ (0, vitest_1.it)('renders all topic combining all sections', () => {
33
+ const output = (0, index_1.renderTopic)('all');
34
+ (0, vitest_1.expect)(output).toContain('Feature-Sliced Design');
35
+ (0, vitest_1.expect)(output).toContain('Modular Architecture');
36
+ (0, vitest_1.expect)(output).toContain('FSD Layer Reference');
37
+ });
38
+ (0, vitest_1.it)('returns error message for unknown topic', () => {
39
+ const output = (0, index_1.renderTopic)('unknown-topic');
40
+ (0, vitest_1.expect)(output).toContain('Unknown topic');
41
+ (0, vitest_1.expect)(output).toContain('unknown-topic');
42
+ (0, vitest_1.expect)(output).toContain('Available topics');
43
+ });
44
+ (0, vitest_1.it)('is case-insensitive', () => {
45
+ const lower = (0, index_1.renderTopic)('fsd');
46
+ const upper = (0, index_1.renderTopic)('FSD');
47
+ // Same length means same content (ANSI codes are identical)
48
+ (0, vitest_1.expect)(lower.length).toBe(upper.length);
49
+ });
50
+ (0, vitest_1.it)('AVAILABLE_TOPICS contains expected topics', () => {
51
+ (0, vitest_1.expect)(index_1.AVAILABLE_TOPICS).toContain('fsd');
52
+ (0, vitest_1.expect)(index_1.AVAILABLE_TOPICS).toContain('modular');
53
+ (0, vitest_1.expect)(index_1.AVAILABLE_TOPICS).toContain('layers');
54
+ (0, vitest_1.expect)(index_1.AVAILABLE_TOPICS).toContain('all');
55
+ });
56
+ });
57
+ //# sourceMappingURL=explain.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.test.js","sourceRoot":"","sources":["../../../src/commands/explain/explain.test.ts"],"names":[],"mappings":";;AAAA,mCAA6C;AAE7C,mCAAuD;AAEvD,IAAA,iBAAQ,EAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAA,WAAE,EAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,KAAK,CAAC,CAAA;QACjC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAA;QACjD,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAClC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACnC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QACjC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAC/B,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,SAAS,CAAC,CAAA;QACrC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAChD,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACnC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QAChC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAClC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,QAAQ,CAAC,CAAA;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;QAC/C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAClC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;QACpC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,KAAK,CAAC,CAAA;QACjC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAA;QACjD,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAA;QAChD,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,MAAM,GAAG,IAAA,mBAAW,EAAC,eAAe,CAAC,CAAA;QAC3C,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAA;QACzC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAA;QACzC,IAAA,eAAM,EAAC,MAAM,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,qBAAqB,EAAE,GAAG,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAA,mBAAW,EAAC,KAAK,CAAC,CAAA;QAChC,MAAM,KAAK,GAAG,IAAA,mBAAW,EAAC,KAAK,CAAC,CAAA;QAChC,4DAA4D;QAC5D,IAAA,eAAM,EAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,IAAA,WAAE,EAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,IAAA,eAAM,EAAC,wBAAgB,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QACzC,IAAA,eAAM,EAAC,wBAAgB,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QAC7C,IAAA,eAAM,EAAC,wBAAgB,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAC5C,IAAA,eAAM,EAAC,wBAAgB,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,12 @@
1
+ import { TOPICS } from './topics';
2
+ export { TOPICS };
3
+ export declare const AVAILABLE_TOPICS: readonly string[];
4
+ /**
5
+ * Renders the requested topic to a string.
6
+ * Pure function β€” no stdout side-effects β€” making it fully testable.
7
+ */
8
+ export declare function renderTopic(topic: string): string;
9
+ /**
10
+ * CLI entry point β€” writes the rendered topic to stdout.
11
+ */
12
+ export declare function explainCommand(topic: string): void;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AVAILABLE_TOPICS = exports.TOPICS = void 0;
7
+ exports.renderTopic = renderTopic;
8
+ exports.explainCommand = explainCommand;
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const format_1 = require("../../shared/format");
11
+ const topics_1 = require("./topics");
12
+ Object.defineProperty(exports, "TOPICS", { enumerable: true, get: function () { return topics_1.TOPICS; } });
13
+ exports.AVAILABLE_TOPICS = Object.keys(topics_1.TOPICS);
14
+ /**
15
+ * Renders the requested topic to a string.
16
+ * Pure function β€” no stdout side-effects β€” making it fully testable.
17
+ */
18
+ function renderTopic(topic) {
19
+ const renderer = topics_1.TOPICS[topic.toLowerCase()];
20
+ if (!renderer) {
21
+ return [
22
+ chalk_1.default.red(`\n Unknown topic: "${topic}"\n`),
23
+ format_1.fmt.line(` Available topics: ${chalk_1.default.cyan(exports.AVAILABLE_TOPICS.join(', '))}`),
24
+ format_1.fmt.line(''),
25
+ format_1.fmt.line(` Example: component-forge explain fsd`),
26
+ format_1.fmt.line(''),
27
+ ].join('\n');
28
+ }
29
+ return renderer();
30
+ }
31
+ /**
32
+ * CLI entry point β€” writes the rendered topic to stdout.
33
+ */
34
+ function explainCommand(topic) {
35
+ process.stdout.write(renderTopic(topic));
36
+ }
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/explain/index.ts"],"names":[],"mappings":";;;;;;AAaA,kCAcC;AAKD,wCAEC;AAlCD,kDAAyB;AAEzB,gDAAyC;AAEzC,qCAAiC;AAExB,uFAFA,eAAM,OAEA;AACF,QAAA,gBAAgB,GAAG,MAAM,CAAC,IAAI,CAAC,eAAM,CAAsB,CAAA;AAExE;;;GAGG;AACH,SAAgB,WAAW,CAAC,KAAa;IACvC,MAAM,QAAQ,GAAG,eAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;IAE5C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,eAAK,CAAC,GAAG,CAAC,uBAAuB,KAAK,KAAK,CAAC;YAC5C,YAAG,CAAC,IAAI,CAAC,uBAAuB,eAAK,CAAC,IAAI,CAAC,wBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC1E,YAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACZ,YAAG,CAAC,IAAI,CAAC,wCAAwC,CAAC;YAClD,YAAG,CAAC,IAAI,CAAC,EAAE,CAAC;SACb,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACd,CAAC;IAED,OAAO,QAAQ,EAAE,CAAA;AACnB,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,KAAa;IAC1C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;AAC1C,CAAC"}
@@ -0,0 +1,3 @@
1
+ type TopicRenderer = () => string;
2
+ export declare const TOPICS: Record<string, TopicRenderer>;
3
+ export {};