domain-driver 0.1.0 → 0.3.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 (85) hide show
  1. package/README.md +159 -145
  2. package/dist/cli.js +208 -0
  3. package/dist/commands/action.js +118 -0
  4. package/dist/commands/component.js +18 -19
  5. package/dist/commands/container.js +10 -31
  6. package/dist/commands/controller.js +85 -0
  7. package/dist/commands/feature.js +59 -51
  8. package/dist/commands/hints.js +28 -0
  9. package/dist/commands/hook.js +10 -111
  10. package/dist/commands/repository.js +17 -115
  11. package/dist/commands/resolve.js +61 -0
  12. package/dist/commands/schema.js +22 -69
  13. package/dist/commands/service.js +15 -113
  14. package/dist/commands/sides.js +34 -0
  15. package/dist/commands/target.js +32 -0
  16. package/dist/commands/types.js +9 -17
  17. package/dist/commands/update.js +148 -0
  18. package/dist/commands/write.js +66 -0
  19. package/dist/index.js +8 -122
  20. package/dist/init/content.js +73 -0
  21. package/dist/init/init.js +66 -0
  22. package/dist/init/markers.js +21 -0
  23. package/dist/postinstall.js +80 -0
  24. package/dist/stack/detect.js +125 -0
  25. package/dist/stack/profiles/nest.js +18 -0
  26. package/dist/stack/profiles/next-frontend.js +28 -0
  27. package/dist/stack/profiles/next-fullstack.js +44 -0
  28. package/dist/stack/profiles/node.js +17 -0
  29. package/dist/stack/profiles/react.js +19 -0
  30. package/dist/stack/registry.js +63 -0
  31. package/dist/stack/types.js +24 -0
  32. package/dist/templates/actions.js +68 -0
  33. package/dist/templates/backend/server-repository.js +22 -0
  34. package/dist/templates/context.js +52 -0
  35. package/dist/templates/controllers/express.js +60 -0
  36. package/dist/templates/controllers/fastify.js +54 -0
  37. package/dist/templates/controllers/generic.js +30 -0
  38. package/dist/templates/controllers/hono.js +52 -0
  39. package/dist/templates/controllers/nest.js +63 -0
  40. package/dist/templates/controllers/next-action-route.js +40 -0
  41. package/dist/templates/controllers/next-route.js +62 -0
  42. package/dist/templates/controllers/node.js +46 -0
  43. package/dist/templates/controllers/shape.js +28 -0
  44. package/dist/templates/frontend/client-repository.js +38 -0
  45. package/dist/templates/frontend/component.js +18 -0
  46. package/dist/templates/frontend/container.js +28 -0
  47. package/{src/commands/hook.ts → dist/templates/frontend/hook.js} +28 -41
  48. package/dist/templates/frontend/page.js +26 -0
  49. package/dist/templates/nest/dto.js +12 -0
  50. package/dist/templates/nest/injectable.js +4 -0
  51. package/dist/templates/nest/module.js +41 -0
  52. package/dist/templates/service.js +40 -0
  53. package/dist/templates/shared/schema.js +13 -0
  54. package/dist/templates/shared/types.js +12 -0
  55. package/dist/templates/signatures.js +15 -0
  56. package/dist/update/cache.js +80 -0
  57. package/dist/update/check.js +47 -0
  58. package/dist/update/install-mode.js +96 -0
  59. package/dist/update/package-manager.js +99 -0
  60. package/dist/update/registry.js +77 -0
  61. package/dist/update/version.js +70 -0
  62. package/dist/utils/alias.js +80 -0
  63. package/dist/utils/fs.js +70 -0
  64. package/dist/utils/imports.js +61 -0
  65. package/dist/utils/naming.js +31 -0
  66. package/dist/utils/paths.js +44 -0
  67. package/package.json +27 -6
  68. package/scripts/postinstall.js +7 -0
  69. package/.github/workflows/publish.yml +0 -28
  70. package/dist/utils.js +0 -132
  71. package/src/__tests__/utils.test.ts +0 -128
  72. package/src/commands/__tests__/feature.test.ts +0 -85
  73. package/src/commands/__tests__/imports.test.ts +0 -115
  74. package/src/commands/__tests__/individual.test.ts +0 -137
  75. package/src/commands/component.ts +0 -33
  76. package/src/commands/container.ts +0 -42
  77. package/src/commands/feature.ts +0 -74
  78. package/src/commands/repository.ts +0 -95
  79. package/src/commands/schema.ts +0 -47
  80. package/src/commands/service.ts +0 -93
  81. package/src/commands/types.ts +0 -25
  82. package/src/index.ts +0 -126
  83. package/src/utils.ts +0 -115
  84. package/tsconfig.json +0 -13
  85. package/vitest.config.ts +0 -7
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toPascalCase = toPascalCase;
4
+ exports.lowerFirst = lowerFirst;
5
+ exports.validateFeatureName = validateFeatureName;
6
+ exports.upperFirst = upperFirst;
7
+ exports.toKebabCase = toKebabCase;
8
+ const FEATURE_NAME_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
9
+ function toPascalCase(name) {
10
+ return name
11
+ .split('-')
12
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
13
+ .join('');
14
+ }
15
+ function lowerFirst(name) {
16
+ return name.charAt(0).toLowerCase() + name.slice(1);
17
+ }
18
+ function validateFeatureName(name) {
19
+ if (!FEATURE_NAME_PATTERN.test(name)) {
20
+ throw new Error(`Feature name "${name}" must be kebab-case, for example coffee-type.`);
21
+ }
22
+ }
23
+ function upperFirst(name) {
24
+ return name.charAt(0).toUpperCase() + name.slice(1);
25
+ }
26
+ function toKebabCase(name) {
27
+ return name
28
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
29
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
30
+ .toLowerCase();
31
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.featureDir = featureDir;
37
+ exports.apiRouteDir = apiRouteDir;
38
+ const path = __importStar(require("path"));
39
+ function featureDir(stack, feature) {
40
+ return path.join(process.cwd(), stack.featureRoot, feature);
41
+ }
42
+ function apiRouteDir(stack, feature) {
43
+ return path.join(process.cwd(), stack.featureRoot, 'api', feature);
44
+ }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "domain-driver",
3
- "version": "0.1.0",
4
- "description": "CLI scaffolding tool for domain-driven development in Next.js",
5
- "main": "index.js",
3
+ "version": "0.3.0",
4
+ "description": "CLI scaffolding tool for domain-driven feature folders in Next.js, React, Node, and NestJS projects, with per-action files, bespoke actions, and agent guidance",
5
+ "main": "dist/index.js",
6
6
  "bin": {
7
7
  "domain-driver": "./dist/index.js"
8
8
  },
@@ -10,15 +10,36 @@
10
10
  "type": "git",
11
11
  "url": "https://github.com/IsaacHatilima/domain-driver"
12
12
  },
13
+ "files": [
14
+ "dist",
15
+ "scripts/postinstall.js"
16
+ ],
13
17
  "scripts": {
14
- "build": "tsc",
15
- "test": "vitest run"
18
+ "build": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
19
+ "test": "vitest run",
20
+ "test:coverage": "vitest run --coverage",
21
+ "postinstall": "node ./scripts/postinstall.js"
16
22
  },
17
- "keywords": [],
23
+ "keywords": [
24
+ "cli",
25
+ "scaffolding",
26
+ "domain-driven",
27
+ "nextjs",
28
+ "react",
29
+ "nestjs",
30
+ "node",
31
+ "express",
32
+ "fastify",
33
+ "hono"
34
+ ],
18
35
  "author": "Isaac Hatilima",
19
36
  "license": "MIT",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
20
40
  "devDependencies": {
21
41
  "@types/node": "^25.4.0",
42
+ "@vitest/coverage-v8": "^4.1.2",
22
43
  "typescript": "^5.9.3",
23
44
  "vitest": "^4.1.2"
24
45
  },
@@ -0,0 +1,7 @@
1
+ // Runs after `npm install` in a consuming project. Never fails the install:
2
+ // dist/ may be absent on a fresh clone, and init may throw for any reason.
3
+ try {
4
+ require('../dist/postinstall.js').run();
5
+ } catch (_error) {
6
+ // intentionally silent
7
+ }
@@ -1,28 +0,0 @@
1
- name: Publish to npm
2
-
3
- on:
4
- push:
5
- branches:
6
- - master
7
- tags:
8
- - 'v*'
9
-
10
- jobs:
11
- publish:
12
- runs-on: ubuntu-latest
13
- permissions:
14
- id-token: write
15
-
16
- steps:
17
- - uses: actions/checkout@v4
18
-
19
- - uses: actions/setup-node@v4
20
- with:
21
- node-version: '18'
22
- registry-url: 'https://registry.npmjs.org'
23
-
24
- - run: npm install
25
- - run: npm run build
26
- - run: npm publish --provenance --access public
27
- env:
28
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/dist/utils.js DELETED
@@ -1,132 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.toPascalCase = toPascalCase;
37
- exports.featureBasePath = featureBasePath;
38
- exports.ensureFeatureExists = ensureFeatureExists;
39
- exports.writeFileSafe = writeFileSafe;
40
- exports.mkdirSafe = mkdirSafe;
41
- exports.fileExists = fileExists;
42
- exports.detectAlias = detectAlias;
43
- exports.resetAliasCache = resetAliasCache;
44
- exports.resolveImport = resolveImport;
45
- const fs = __importStar(require("fs"));
46
- const path = __importStar(require("path"));
47
- function toPascalCase(name) {
48
- return name
49
- .split('-')
50
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
51
- .join('');
52
- }
53
- function featureBasePath(feature) {
54
- return path.join(process.cwd(), 'app', feature);
55
- }
56
- function ensureFeatureExists(feature, subdir) {
57
- const base = path.join(featureBasePath(feature), subdir);
58
- if (!fs.existsSync(base)) {
59
- throw new Error(`Feature "${feature}" does not exist. Run: domain-driver make:feature ${feature}`);
60
- }
61
- return base;
62
- }
63
- function writeFileSafe(filePath, content) {
64
- try {
65
- fs.writeFileSync(filePath, content);
66
- }
67
- catch (error) {
68
- const message = error instanceof Error ? error.message : 'Unknown error';
69
- throw new Error(`Failed to write ${filePath}: ${message}`);
70
- }
71
- }
72
- function mkdirSafe(dirPath) {
73
- try {
74
- fs.mkdirSync(dirPath, { recursive: true });
75
- }
76
- catch (error) {
77
- const message = error instanceof Error ? error.message : 'Unknown error';
78
- throw new Error(`Failed to create directory ${dirPath}: ${message}`);
79
- }
80
- }
81
- function fileExists(filePath) {
82
- return fs.existsSync(filePath);
83
- }
84
- let cachedAlias;
85
- function detectAlias() {
86
- if (cachedAlias)
87
- return cachedAlias;
88
- const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
89
- if (!fs.existsSync(tsconfigPath)) {
90
- cachedAlias = { alias: null, appDir: 'app' };
91
- return cachedAlias;
92
- }
93
- try {
94
- const raw = fs.readFileSync(tsconfigPath, 'utf-8');
95
- const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
96
- const tsconfig = JSON.parse(stripped);
97
- const paths = tsconfig?.compilerOptions?.paths;
98
- if (paths) {
99
- for (const [key, values] of Object.entries(paths)) {
100
- const targets = values;
101
- const hasAppMapping = targets.some((v) => v === './app/*' || v === 'app/*' || v === './src/app/*' || v === 'src/app/*');
102
- if (hasAppMapping && key.endsWith('/*')) {
103
- const prefix = key.slice(0, -1);
104
- const appDir = targets[0].replace('/*', '').replace('./', '');
105
- cachedAlias = { alias: prefix, appDir };
106
- return cachedAlias;
107
- }
108
- const hasSrcMapping = targets.some((v) => v === './src/*' || v === 'src/*' || v === './*');
109
- if (hasSrcMapping && key.endsWith('/*')) {
110
- const prefix = key.slice(0, -1);
111
- cachedAlias = { alias: prefix, appDir: 'app' };
112
- return cachedAlias;
113
- }
114
- }
115
- }
116
- }
117
- catch {
118
- // tsconfig parse failed, fall back to relative imports
119
- }
120
- cachedAlias = { alias: null, appDir: 'app' };
121
- return cachedAlias;
122
- }
123
- function resetAliasCache() {
124
- cachedAlias = undefined;
125
- }
126
- function resolveImport(fromFeature, relativePath, fromRoot = false) {
127
- const { alias } = detectAlias();
128
- if (alias) {
129
- return `${alias}app/${fromFeature}/${relativePath}`;
130
- }
131
- return fromRoot ? `./${relativePath}` : `../${relativePath}`;
132
- }
@@ -1,128 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { toPascalCase, detectAlias, resolveImport, resetAliasCache } from '../utils';
6
-
7
- describe('toPascalCase', () => {
8
- it('converts kebab-case to PascalCase', () => {
9
- expect(toPascalCase('coffee-type')).toBe('CoffeeType');
10
- });
11
-
12
- it('handles single word', () => {
13
- expect(toPascalCase('user')).toBe('User');
14
- });
15
-
16
- it('handles multiple hyphens', () => {
17
- expect(toPascalCase('my-long-feature-name')).toBe('MyLongFeatureName');
18
- });
19
- });
20
-
21
- describe('detectAlias', () => {
22
- let testDir: string;
23
- let originalCwd: string;
24
-
25
- beforeEach(() => {
26
- originalCwd = process.cwd();
27
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-alias-'));
28
- process.chdir(testDir);
29
- resetAliasCache();
30
- });
31
-
32
- afterEach(() => {
33
- process.chdir(originalCwd);
34
- fs.rmSync(testDir, { recursive: true, force: true });
35
- });
36
-
37
- it('returns null alias when no tsconfig exists', () => {
38
- const result = detectAlias();
39
- expect(result.alias).toBeNull();
40
- });
41
-
42
- it('detects @/ alias mapping to ./app/*', () => {
43
- fs.writeFileSync(
44
- path.join(testDir, 'tsconfig.json'),
45
- JSON.stringify({
46
- compilerOptions: {
47
- paths: { '@/*': ['./app/*'] },
48
- },
49
- })
50
- );
51
- const result = detectAlias();
52
- expect(result.alias).toBe('@/');
53
- });
54
-
55
- it('detects @/ alias mapping to ./src/*', () => {
56
- fs.writeFileSync(
57
- path.join(testDir, 'tsconfig.json'),
58
- JSON.stringify({
59
- compilerOptions: {
60
- paths: { '@/*': ['./src/*'] },
61
- },
62
- })
63
- );
64
- const result = detectAlias();
65
- expect(result.alias).toBe('@/');
66
- });
67
-
68
- it('detects custom alias like ~/*', () => {
69
- fs.writeFileSync(
70
- path.join(testDir, 'tsconfig.json'),
71
- JSON.stringify({
72
- compilerOptions: {
73
- paths: { '~/*': ['./app/*'] },
74
- },
75
- })
76
- );
77
- const result = detectAlias();
78
- expect(result.alias).toBe('~/');
79
- });
80
-
81
- it('returns null when paths has no matching pattern', () => {
82
- fs.writeFileSync(
83
- path.join(testDir, 'tsconfig.json'),
84
- JSON.stringify({
85
- compilerOptions: {
86
- paths: { '@components/*': ['./components/*'] },
87
- },
88
- })
89
- );
90
- const result = detectAlias();
91
- expect(result.alias).toBeNull();
92
- });
93
- });
94
-
95
- describe('resolveImport', () => {
96
- let testDir: string;
97
- let originalCwd: string;
98
-
99
- beforeEach(() => {
100
- originalCwd = process.cwd();
101
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-resolve-'));
102
- process.chdir(testDir);
103
- resetAliasCache();
104
- });
105
-
106
- afterEach(() => {
107
- process.chdir(originalCwd);
108
- fs.rmSync(testDir, { recursive: true, force: true });
109
- });
110
-
111
- it('uses relative path when no alias', () => {
112
- const result = resolveImport('coffee-type', 'hooks/useCoffeeType');
113
- expect(result).toBe('../hooks/useCoffeeType');
114
- });
115
-
116
- it('uses alias path when @/ alias exists', () => {
117
- fs.writeFileSync(
118
- path.join(testDir, 'tsconfig.json'),
119
- JSON.stringify({
120
- compilerOptions: {
121
- paths: { '@/*': ['./app/*'] },
122
- },
123
- })
124
- );
125
- const result = resolveImport('coffee-type', 'hooks/useCoffeeType');
126
- expect(result).toBe('@/app/coffee-type/hooks/useCoffeeType');
127
- });
128
- });
@@ -1,85 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { makeFeature } from '../feature';
6
- import { resetAliasCache } from '../../utils';
7
-
8
- let testDir: string;
9
- let originalCwd: string;
10
-
11
- function cleanup(): void {
12
- process.chdir(originalCwd);
13
- resetAliasCache();
14
- if (fs.existsSync(testDir)) {
15
- fs.rmSync(testDir, { recursive: true, force: true });
16
- }
17
- }
18
-
19
- describe('make:feature', () => {
20
- beforeEach(() => {
21
- originalCwd = process.cwd();
22
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-feature-'));
23
- process.chdir(testDir);
24
- resetAliasCache();
25
- });
26
- afterEach(cleanup);
27
-
28
- it('creates feature directories with .gitkeep files', async () => {
29
- await makeFeature('test-feature', false);
30
-
31
- const base = path.join(testDir, 'app', 'test-feature');
32
- expect(fs.existsSync(base)).toBe(true);
33
- expect(fs.existsSync(path.join(base, 'components/client/.gitkeep'))).toBe(true);
34
- expect(fs.existsSync(path.join(base, 'components/server/.gitkeep'))).toBe(true);
35
- expect(fs.existsSync(path.join(base, 'containers/.gitkeep'))).toBe(true);
36
- expect(fs.existsSync(path.join(base, 'hooks/.gitkeep'))).toBe(true);
37
- expect(fs.existsSync(path.join(base, 'services/.gitkeep'))).toBe(true);
38
- expect(fs.existsSync(path.join(base, 'repositories/.gitkeep'))).toBe(true);
39
- expect(fs.existsSync(path.join(base, 'schemas/.gitkeep'))).toBe(true);
40
- expect(fs.existsSync(path.join(base, 'types/.gitkeep'))).toBe(true);
41
- });
42
-
43
- it('creates page.tsx with PascalCase component name', async () => {
44
- await makeFeature('test-feature', false);
45
-
46
- const page = fs.readFileSync(path.join(testDir, 'app/test-feature/page.tsx'), 'utf-8');
47
- expect(page).toContain('TestFeaturePage');
48
- expect(page).toContain('export default function');
49
- });
50
-
51
- it('throws if feature already exists', async () => {
52
- await makeFeature('test-feature', false);
53
- await expect(makeFeature('test-feature', false)).rejects.toThrow('already exists');
54
- });
55
-
56
- it('scaffolds all files with -a flag', async () => {
57
- await makeFeature('coffee-type', true);
58
-
59
- const base = path.join(testDir, 'app', 'coffee-type');
60
-
61
- // No .gitkeep files when -a is used
62
- expect(fs.existsSync(path.join(base, 'components/client/.gitkeep'))).toBe(false);
63
-
64
- // All files created with correct names
65
- expect(fs.existsSync(path.join(base, 'page.tsx'))).toBe(true);
66
- expect(fs.existsSync(path.join(base, 'components/client/CoffeeType.tsx'))).toBe(true);
67
- expect(fs.existsSync(path.join(base, 'containers/CoffeeTypeContainer.tsx'))).toBe(true);
68
- expect(fs.existsSync(path.join(base, 'hooks/useCoffeeType.ts'))).toBe(true);
69
- expect(fs.existsSync(path.join(base, 'schemas/CreateCoffeeType.schema.ts'))).toBe(true);
70
- expect(fs.existsSync(path.join(base, 'schemas/UpdateCoffeeType.schema.ts'))).toBe(true);
71
- expect(fs.existsSync(path.join(base, 'services/ListCoffeeType.service.ts'))).toBe(true);
72
- expect(fs.existsSync(path.join(base, 'services/CreateCoffeeType.service.ts'))).toBe(true);
73
- expect(fs.existsSync(path.join(base, 'repositories/ListCoffeeType.repository.ts'))).toBe(true);
74
- expect(fs.existsSync(path.join(base, 'repositories/CreateCoffeeType.repository.ts'))).toBe(true);
75
- expect(fs.existsSync(path.join(base, 'types/CoffeeType.types.ts'))).toBe(true);
76
- });
77
-
78
- it('page imports container when -a flag is used', async () => {
79
- await makeFeature('coffee-type', true);
80
-
81
- const page = fs.readFileSync(path.join(testDir, 'app/coffee-type/page.tsx'), 'utf-8');
82
- expect(page).toContain("import CoffeeTypeContainer from './containers/CoffeeTypeContainer'");
83
- expect(page).toContain('<CoffeeTypeContainer />');
84
- });
85
- });
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { makeFeature } from '../feature';
6
- import { resetAliasCache } from '../../utils';
7
-
8
- let testDir: string;
9
- let originalCwd: string;
10
-
11
- function readFile(relativePath: string): string {
12
- return fs.readFileSync(path.join(testDir, 'app', relativePath), 'utf-8');
13
- }
14
-
15
- function cleanup(): void {
16
- process.chdir(originalCwd);
17
- resetAliasCache();
18
- if (fs.existsSync(testDir)) {
19
- fs.rmSync(testDir, { recursive: true, force: true });
20
- }
21
- }
22
-
23
- describe('inter-layer imports (no alias)', () => {
24
- beforeEach(async () => {
25
- originalCwd = process.cwd();
26
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-imports-'));
27
- process.chdir(testDir);
28
- resetAliasCache();
29
- await makeFeature('coffee-type', true);
30
- });
31
- afterEach(cleanup);
32
-
33
- it('types file is created with interface', () => {
34
- const content = readFile('coffee-type/types/CoffeeType.types.ts');
35
- expect(content).toContain('export interface CoffeeType');
36
- expect(content).toContain('id: string');
37
- });
38
-
39
- it('container imports hook and component with relative paths', () => {
40
- const content = readFile('coffee-type/containers/CoffeeTypeContainer.tsx');
41
- expect(content).toContain("from '../hooks/useCoffeeType'");
42
- expect(content).toContain("from '../components/client/CoffeeType'");
43
- });
44
-
45
- it('hook imports type, services, and schemas with relative paths', () => {
46
- const content = readFile('coffee-type/hooks/useCoffeeType.ts');
47
- expect(content).toContain("import { CoffeeType } from '../types/CoffeeType.types'");
48
- expect(content).toContain("from '../services/ListCoffeeType.service'");
49
- expect(content).toContain("from '../schemas/CreateCoffeeType.schema'");
50
- });
51
-
52
- it('services import type and repositories with relative paths', () => {
53
- const content = readFile('coffee-type/services/CreateCoffeeType.service.ts');
54
- expect(content).toContain("import { CoffeeType } from '../types/CoffeeType.types'");
55
- expect(content).toContain("from '../repositories/CreateCoffeeType.repository'");
56
- });
57
-
58
- it('repositories import type and schema types', () => {
59
- const create = readFile('coffee-type/repositories/CreateCoffeeType.repository.ts');
60
- expect(create).toContain("import { CoffeeType } from '../types/CoffeeType.types'");
61
- expect(create).toContain("from '../schemas/CreateCoffeeType.schema'");
62
-
63
- const list = readFile('coffee-type/repositories/ListCoffeeType.repository.ts');
64
- expect(list).toContain("import { CoffeeType } from '../types/CoffeeType.types'");
65
- });
66
-
67
- it('delete repository has no schema import but has type import', () => {
68
- const del = readFile('coffee-type/repositories/DeleteCoffeeType.repository.ts');
69
- expect(del).not.toContain('import');
70
- });
71
- });
72
-
73
- describe('inter-layer imports (with @/ alias)', () => {
74
- beforeEach(async () => {
75
- originalCwd = process.cwd();
76
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-alias-imports-'));
77
- process.chdir(testDir);
78
- resetAliasCache();
79
-
80
- fs.writeFileSync(
81
- path.join(testDir, 'tsconfig.json'),
82
- JSON.stringify({
83
- compilerOptions: {
84
- paths: { '@/*': ['./app/*'] },
85
- },
86
- })
87
- );
88
-
89
- await makeFeature('coffee-type', true);
90
- });
91
- afterEach(cleanup);
92
-
93
- it('container imports use @/ alias', () => {
94
- const content = readFile('coffee-type/containers/CoffeeTypeContainer.tsx');
95
- expect(content).toContain("from '@/app/coffee-type/hooks/useCoffeeType'");
96
- expect(content).toContain("from '@/app/coffee-type/components/client/CoffeeType'");
97
- });
98
-
99
- it('hook imports use @/ alias', () => {
100
- const content = readFile('coffee-type/hooks/useCoffeeType.ts');
101
- expect(content).toContain("from '@/app/coffee-type/types/CoffeeType.types'");
102
- expect(content).toContain("from '@/app/coffee-type/services/ListCoffeeType.service'");
103
- });
104
-
105
- it('services import repositories with @/ alias', () => {
106
- const content = readFile('coffee-type/services/CreateCoffeeType.service.ts');
107
- expect(content).toContain("from '@/app/coffee-type/types/CoffeeType.types'");
108
- expect(content).toContain("from '@/app/coffee-type/repositories/CreateCoffeeType.repository'");
109
- });
110
-
111
- it('page imports container with @/ alias', () => {
112
- const content = readFile('coffee-type/page.tsx');
113
- expect(content).toContain("from '@/app/coffee-type/containers/CoffeeTypeContainer'");
114
- });
115
- });