domain-driver 0.0.5 → 0.1.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.
@@ -0,0 +1,137 @@
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 { makeComponent } from '../component';
7
+ import { makeContainer } from '../container';
8
+ import { makeHook } from '../hook';
9
+ import { makeService } from '../service';
10
+ import { makeRepository } from '../repository';
11
+ import { makeSchema } from '../schema';
12
+ import { resetAliasCache } from '../../utils';
13
+
14
+ let testDir: string;
15
+ let originalCwd: string;
16
+
17
+ function cleanup(): void {
18
+ process.chdir(originalCwd);
19
+ resetAliasCache();
20
+ if (fs.existsSync(testDir)) {
21
+ fs.rmSync(testDir, { recursive: true, force: true });
22
+ }
23
+ }
24
+
25
+ describe('individual commands on existing feature', () => {
26
+ beforeEach(async () => {
27
+ originalCwd = process.cwd();
28
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'dd-individual-'));
29
+ process.chdir(testDir);
30
+ resetAliasCache();
31
+ await makeFeature('test-feature', false);
32
+ });
33
+ afterEach(cleanup);
34
+
35
+ it('make:component creates client component by default', () => {
36
+ makeComponent('test-feature', 'MyButton', 'client');
37
+ const content = fs.readFileSync(
38
+ path.join(testDir, 'app/test-feature/components/client/MyButton.tsx'),
39
+ 'utf-8'
40
+ );
41
+ expect(content).toContain("'use client'");
42
+ expect(content).toContain('export default function MyButton');
43
+ expect(content).toContain('interface MyButtonProps');
44
+ });
45
+
46
+ it('make:component creates server component', () => {
47
+ makeComponent('test-feature', 'DataTable', 'server');
48
+ const content = fs.readFileSync(
49
+ path.join(testDir, 'app/test-feature/components/server/DataTable.tsx'),
50
+ 'utf-8'
51
+ );
52
+ expect(content).not.toContain("'use client'");
53
+ expect(content).toContain('export default function DataTable');
54
+ });
55
+
56
+ it('make:component throws if component already exists', () => {
57
+ makeComponent('test-feature', 'MyButton', 'client');
58
+ expect(() => makeComponent('test-feature', 'MyButton', 'client')).toThrow('already exists');
59
+ });
60
+
61
+ it('make:container creates container', () => {
62
+ makeContainer('test-feature', 'TestContainer');
63
+ const content = fs.readFileSync(
64
+ path.join(testDir, 'app/test-feature/containers/TestContainer.tsx'),
65
+ 'utf-8'
66
+ );
67
+ expect(content).toContain("'use client'");
68
+ expect(content).toContain('export default function TestContainer');
69
+ });
70
+
71
+ it('make:hook creates hook file', () => {
72
+ makeHook('test-feature', 'useTest');
73
+ const content = fs.readFileSync(
74
+ path.join(testDir, 'app/test-feature/hooks/useTest.ts'),
75
+ 'utf-8'
76
+ );
77
+ expect(content).toContain('export function useTest');
78
+ expect(content).toContain('useState');
79
+ });
80
+
81
+ it('make:service creates 5 service files', () => {
82
+ makeService('test-feature', 'TestEntity');
83
+ const base = path.join(testDir, 'app/test-feature/services');
84
+ expect(fs.existsSync(path.join(base, 'ListTestEntity.service.ts'))).toBe(true);
85
+ expect(fs.existsSync(path.join(base, 'ShowTestEntity.service.ts'))).toBe(true);
86
+ expect(fs.existsSync(path.join(base, 'CreateTestEntity.service.ts'))).toBe(true);
87
+ expect(fs.existsSync(path.join(base, 'UpdateTestEntity.service.ts'))).toBe(true);
88
+ expect(fs.existsSync(path.join(base, 'DeleteTestEntity.service.ts'))).toBe(true);
89
+ });
90
+
91
+ it('make:service skips existing files', () => {
92
+ makeService('test-feature', 'TestEntity');
93
+ expect(() => makeService('test-feature', 'TestEntity')).not.toThrow();
94
+ });
95
+
96
+ it('make:repository creates 5 repository files', () => {
97
+ makeRepository('test-feature', 'TestEntity');
98
+ const base = path.join(testDir, 'app/test-feature/repositories');
99
+ expect(fs.existsSync(path.join(base, 'ListTestEntity.repository.ts'))).toBe(true);
100
+ expect(fs.existsSync(path.join(base, 'ShowTestEntity.repository.ts'))).toBe(true);
101
+ expect(fs.existsSync(path.join(base, 'CreateTestEntity.repository.ts'))).toBe(true);
102
+ expect(fs.existsSync(path.join(base, 'UpdateTestEntity.repository.ts'))).toBe(true);
103
+ expect(fs.existsSync(path.join(base, 'DeleteTestEntity.repository.ts'))).toBe(true);
104
+ });
105
+
106
+ it('make:repository generates proper fetch calls', () => {
107
+ makeRepository('test-feature', 'TestEntity');
108
+ const content = fs.readFileSync(
109
+ path.join(testDir, 'app/test-feature/repositories/CreateTestEntity.repository.ts'),
110
+ 'utf-8'
111
+ );
112
+ expect(content).toContain("fetch('/api/test-feature'");
113
+ expect(content).toContain("method: 'POST'");
114
+ expect(content).toContain("'Content-Type': 'application/json'");
115
+ });
116
+
117
+ it('make:schema creates Create and Update schemas', () => {
118
+ makeSchema('test-feature', 'TestEntity');
119
+ const base = path.join(testDir, 'app/test-feature/schemas');
120
+
121
+ const create = fs.readFileSync(path.join(base, 'CreateTestEntity.schema.ts'), 'utf-8');
122
+ expect(create).toContain("import { z } from 'zod'");
123
+ expect(create).toContain('CreateTestEntity');
124
+
125
+ const update = fs.readFileSync(path.join(base, 'UpdateTestEntity.schema.ts'), 'utf-8');
126
+ expect(update).toContain('id: z.string()');
127
+ });
128
+
129
+ it('throws when feature does not exist', () => {
130
+ expect(() => makeComponent('nonexistent', 'Foo', 'client')).toThrow('does not exist');
131
+ expect(() => makeHook('nonexistent', 'useFoo')).toThrow('does not exist');
132
+ expect(() => makeService('nonexistent', 'Foo')).toThrow('does not exist');
133
+ expect(() => makeRepository('nonexistent', 'Foo')).toThrow('does not exist');
134
+ expect(() => makeSchema('nonexistent', 'Foo')).toThrow('does not exist');
135
+ expect(() => makeContainer('nonexistent', 'FooContainer')).toThrow('does not exist');
136
+ });
137
+ });
@@ -1,12 +1,16 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { ensureFeatureExists, writeFileSafe, fileExists } from '../utils';
3
3
 
4
4
  type ComponentType = 'client' | 'server';
5
5
 
6
6
  function renderComponent(name: string, type: ComponentType): string {
7
7
  const directive = type === 'client' ? "'use client';\n\n" : '';
8
8
 
9
- return `${directive}export default function ${name}() {
9
+ return `${directive}interface ${name}Props {
10
+ id: string;
11
+ }
12
+
13
+ export default function ${name}({ id }: ${name}Props) {
10
14
  return (
11
15
  <div>
12
16
  <h1>${name}</h1>
@@ -16,21 +20,14 @@ function renderComponent(name: string, type: ComponentType): string {
16
20
  `;
17
21
  }
18
22
 
19
- export function makeComponent(feature: string, name: string, type: ComponentType = 'client') {
20
- const base = path.join(process.cwd(), 'app', feature, 'components', type);
21
-
22
- if (!fs.existsSync(base)) {
23
- console.error(`❌ Feature "${feature}" does not exist. Run make:feature ${feature} first.`);
24
- process.exit(1);
25
- }
26
-
23
+ export function makeComponent(feature: string, name: string, type: ComponentType = 'client'): void {
24
+ const base = ensureFeatureExists(feature, `components/${type}`);
27
25
  const filePath = path.join(base, `${name}.tsx`);
28
26
 
29
- if (fs.existsSync(filePath)) {
30
- console.error(`❌ Component "${name}" already exists at ${filePath}`);
31
- process.exit(1);
27
+ if (fileExists(filePath)) {
28
+ throw new Error(`Component "${name}" already exists at ${filePath}`);
32
29
  }
33
30
 
34
- fs.writeFileSync(filePath, renderComponent(name, type));
31
+ writeFileSafe(filePath, renderComponent(name, type));
35
32
  console.log(`✅ Component "${name}" created at ${filePath}`);
36
- }
33
+ }
@@ -1,36 +1,42 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { ensureFeatureExists, writeFileSafe, fileExists, resolveImport } from '../utils';
3
+
4
+ function renderContainer(containerName: string, name: string, feature: string): string {
5
+ const hookName = `use${name}`;
6
+ const hookPath = resolveImport(feature, `hooks/${hookName}`);
7
+ const componentPath = resolveImport(feature, `components/client/${name}`);
3
8
 
4
- function renderContainer(name: string): string {
5
9
  return `'use client';
6
10
 
7
- interface Props {}
11
+ import { ${hookName} } from '${hookPath}';
12
+ import ${name} from '${componentPath}';
13
+
14
+ export default function ${containerName}() {
15
+ const { items, loading, error } = ${hookName}();
16
+
17
+ if (loading) return <div>Loading...</div>;
18
+ if (error) return <div>Error: {error}</div>;
8
19
 
9
- export default function ${name}({ }: Props) {
10
20
  return (
11
21
  <div>
12
- <h1>${name}</h1>
22
+ {items.map((item) => (
23
+ <${name} key={item.id} {...item} />
24
+ ))}
13
25
  </div>
14
26
  );
15
27
  }
16
28
  `;
17
29
  }
18
30
 
19
- export function makeContainer(feature: string, name: string) {
20
- const base = path.join(process.cwd(), 'app', feature, 'containers');
21
-
22
- if (!fs.existsSync(base)) {
23
- console.error(`❌ Feature "${feature}" does not exist. Run make:feature ${feature} first.`);
24
- process.exit(1);
25
- }
26
-
31
+ export function makeContainer(feature: string, name: string, pascalName?: string): void {
32
+ const base = ensureFeatureExists(feature, 'containers');
27
33
  const filePath = path.join(base, `${name}.tsx`);
28
34
 
29
- if (fs.existsSync(filePath)) {
30
- console.error(`❌ Container "${name}" already exists at ${filePath}`);
31
- process.exit(1);
35
+ if (fileExists(filePath)) {
36
+ throw new Error(`Container "${name}" already exists at ${filePath}`);
32
37
  }
33
38
 
34
- fs.writeFileSync(filePath, renderContainer(name));
39
+ const entityName = pascalName ?? name.replace(/Container$/, '');
40
+ writeFileSafe(filePath, renderContainer(name, entityName, feature));
35
41
  console.log(`✅ Container "${name}" created at ${filePath}`);
36
- }
42
+ }
@@ -1,11 +1,12 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { toPascalCase, featureBasePath, writeFileSafe, mkdirSafe, fileExists, resolveImport } from '../utils';
3
3
  import { makeComponent } from './component';
4
4
  import { makeHook } from './hook';
5
5
  import { makeService } from './service';
6
6
  import { makeRepository } from './repository';
7
7
  import { makeSchema } from './schema';
8
8
  import { makeContainer } from './container';
9
+ import { makeTypes } from './types';
9
10
 
10
11
  const FEATURE_DIRS = [
11
12
  'components/server',
@@ -15,54 +16,59 @@ const FEATURE_DIRS = [
15
16
  'services',
16
17
  'repositories',
17
18
  'schemas',
19
+ 'types',
18
20
  ];
19
21
 
20
- function toPascalCase(name: string): string {
21
- return name
22
- .split('-')
23
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
24
- .join('');
25
- }
22
+ function renderPage(name: string, pascalName: string): string {
23
+ const containerPath = resolveImport(name, `containers/${pascalName}Container`, true);
24
+
25
+ return `import ${pascalName}Container from '${containerPath}';
26
26
 
27
- function renderPage(name: string): string {
28
- const componentName = toPascalCase(name);
29
- return `export default function ${componentName}Page() {
27
+ export default function ${pascalName}Page() {
30
28
  return (
31
29
  <div>
32
- <h1>${componentName}</h1>
30
+ <${pascalName}Container />
33
31
  </div>
34
32
  );
35
33
  }
36
34
  `;
37
35
  }
38
36
 
39
- export async function makeFeature(name: string, all: boolean = false) {
40
- const base = path.join(process.cwd(), 'app', name);
37
+ export async function makeFeature(name: string, all: boolean = false): Promise<void> {
38
+ const base = featureBasePath(name);
41
39
  const pascalName = toPascalCase(name);
42
40
 
43
- if (fs.existsSync(base)) {
44
- console.error(`❌ Feature "${name}" already exists at ${base}`);
45
- process.exit(1);
41
+ if (fileExists(base)) {
42
+ throw new Error(`Feature "${name}" already exists at ${base}`);
46
43
  }
47
44
 
48
45
  for (const dir of FEATURE_DIRS) {
49
- fs.mkdirSync(path.join(base, dir), { recursive: true });
46
+ mkdirSafe(path.join(base, dir));
50
47
  if (!all) {
51
- fs.writeFileSync(path.join(base, dir, '.gitkeep'), '');
48
+ writeFileSafe(path.join(base, dir, '.gitkeep'), '');
52
49
  }
53
50
  }
54
51
 
55
- fs.writeFileSync(path.join(base, 'page.tsx'), renderPage(name));
52
+ if (all) {
53
+ writeFileSafe(path.join(base, 'page.tsx'), renderPage(name, pascalName));
54
+ } else {
55
+ writeFileSafe(
56
+ path.join(base, 'page.tsx'),
57
+ `export default function ${pascalName}Page() {\n return (\n <div>\n <h1>${pascalName}</h1>\n </div>\n );\n}\n`
58
+ );
59
+ }
60
+
56
61
  console.log(`✅ Feature "${name}" scaffolded at ${base}`);
57
62
 
58
63
  if (all) {
59
- makeComponent(name, pascalName, 'client');
60
- makeContainer(name, `${pascalName}Container`);
61
- makeHook(name, `use${pascalName}`);
64
+ makeTypes(name, pascalName);
65
+ makeSchema(name, pascalName);
66
+ makeRepository(name, pascalName);
62
67
  makeService(name, pascalName);
63
- makeRepository(name, `${pascalName}Repository`);
64
- makeSchema(name, `${pascalName}Schema`);
68
+ makeHook(name, `use${pascalName}`, pascalName);
69
+ makeComponent(name, pascalName, 'client');
70
+ makeContainer(name, `${pascalName}Container`, pascalName);
65
71
 
66
72
  console.log(`✅ All files scaffolded for "${name}"`);
67
73
  }
68
- }
74
+ }
@@ -1,32 +1,122 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { ensureFeatureExists, writeFileSafe, fileExists, resolveImport } from '../utils';
3
3
 
4
- function renderHook(name: string): string {
5
- return `import { useState } from 'react';
4
+ function renderHook(hookName: string, name: string, feature: string): string {
5
+ const typePath = resolveImport(feature, `types/${name}.types`);
6
+ const svcPath = (op: string) => resolveImport(feature, `services/${op}${name}.service`);
7
+ const schemaPath = (op: string) => resolveImport(feature, `schemas/${op}${name}.schema`);
6
8
 
7
- export function ${name}() {
8
- const [data, setData] = useState(null);
9
+ return `'use client';
9
10
 
10
- return { data };
11
- }
12
- `;
13
- }
11
+ import { useState, useEffect, useCallback } from 'react';
12
+ import { ${name} } from '${typePath}';
13
+ import { List${name}Service } from '${svcPath('List')}';
14
+ import { Show${name}Service } from '${svcPath('Show')}';
15
+ import { Create${name}Service } from '${svcPath('Create')}';
16
+ import { Update${name}Service } from '${svcPath('Update')}';
17
+ import { Delete${name}Service } from '${svcPath('Delete')}';
18
+ import { Create${name} } from '${schemaPath('Create')}';
19
+ import { Update${name} } from '${schemaPath('Update')}';
20
+
21
+ const listService = new List${name}Service();
22
+ const showService = new Show${name}Service();
23
+ const createService = new Create${name}Service();
24
+ const updateService = new Update${name}Service();
25
+ const deleteService = new Delete${name}Service();
26
+
27
+ export function ${hookName}() {
28
+ const [items, setItems] = useState<${name}[]>([]);
29
+ const [selected, setSelected] = useState<${name} | null>(null);
30
+ const [loading, setLoading] = useState(false);
31
+ const [error, setError] = useState<string | null>(null);
32
+
33
+ const fetchAll = useCallback(async () => {
34
+ setLoading(true);
35
+ setError(null);
36
+ try {
37
+ const data = await listService.handle();
38
+ setItems(data);
39
+ } catch (err: unknown) {
40
+ setError(err instanceof Error ? err.message : 'Failed to fetch');
41
+ } finally {
42
+ setLoading(false);
43
+ }
44
+ }, []);
45
+
46
+ const fetchOne = useCallback(async (id: string) => {
47
+ setLoading(true);
48
+ setError(null);
49
+ try {
50
+ const data = await showService.handle(id);
51
+ setSelected(data);
52
+ } catch (err: unknown) {
53
+ setError(err instanceof Error ? err.message : 'Failed to fetch');
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ }, []);
14
58
 
15
- export function makeHook(feature: string, name: string) {
16
- const base = path.join(process.cwd(), 'app', feature, 'hooks');
59
+ const create = useCallback(async (data: Create${name}) => {
60
+ setLoading(true);
61
+ setError(null);
62
+ try {
63
+ const created = await createService.handle(data);
64
+ setItems((prev) => [...prev, created]);
65
+ return created;
66
+ } catch (err: unknown) {
67
+ setError(err instanceof Error ? err.message : 'Failed to create');
68
+ return null;
69
+ } finally {
70
+ setLoading(false);
71
+ }
72
+ }, []);
73
+
74
+ const update = useCallback(async (id: string, data: Update${name}) => {
75
+ setLoading(true);
76
+ setError(null);
77
+ try {
78
+ const updated = await updateService.handle(id, data);
79
+ setItems((prev) => prev.map((item) => (item.id === id ? updated : item)));
80
+ return updated;
81
+ } catch (err: unknown) {
82
+ setError(err instanceof Error ? err.message : 'Failed to update');
83
+ return null;
84
+ } finally {
85
+ setLoading(false);
86
+ }
87
+ }, []);
17
88
 
18
- if (!fs.existsSync(base)) {
19
- console.error(`❌ Feature "${feature}" does not exist. Run make:feature ${feature} first.`);
20
- process.exit(1);
89
+ const remove = useCallback(async (id: string) => {
90
+ setLoading(true);
91
+ setError(null);
92
+ try {
93
+ await deleteService.handle(id);
94
+ setItems((prev) => prev.filter((item) => item.id !== id));
95
+ } catch (err: unknown) {
96
+ setError(err instanceof Error ? err.message : 'Failed to delete');
97
+ } finally {
98
+ setLoading(false);
21
99
  }
100
+ }, []);
22
101
 
102
+ useEffect(() => {
103
+ fetchAll();
104
+ }, [fetchAll]);
105
+
106
+ return { items, selected, loading, error, fetchAll, fetchOne, create, update, remove };
107
+ }
108
+ `;
109
+ }
110
+
111
+ export function makeHook(feature: string, name: string, pascalName?: string): void {
112
+ const base = ensureFeatureExists(feature, 'hooks');
23
113
  const filePath = path.join(base, `${name}.ts`);
24
114
 
25
- if (fs.existsSync(filePath)) {
26
- console.error(`❌ Hook "${name}" already exists at ${filePath}`);
27
- process.exit(1);
115
+ if (fileExists(filePath)) {
116
+ throw new Error(`Hook "${name}" already exists at ${filePath}`);
28
117
  }
29
118
 
30
- fs.writeFileSync(filePath, renderHook(name));
119
+ const entityName = pascalName ?? name.replace(/^use/, '');
120
+ writeFileSafe(filePath, renderHook(name, entityName, feature));
31
121
  console.log(`✅ Hook "${name}" created at ${filePath}`);
32
- }
122
+ }
@@ -1,81 +1,95 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { ensureFeatureExists, writeFileSafe, fileExists, resolveImport } from '../utils';
3
3
 
4
4
  type RepositoryAction = 'List' | 'Create' | 'Update' | 'Delete' | 'Show';
5
5
 
6
6
  const REPOSITORY_ACTIONS: RepositoryAction[] = ['List', 'Create', 'Update', 'Delete', 'Show'];
7
7
 
8
8
  function renderRepository(action: RepositoryAction, name: string, feature: string): string {
9
+ const typePath = resolveImport(feature, `types/${name}.types`);
10
+ const schemaPath = (op: string) => resolveImport(feature, `schemas/${op}${name}.schema`);
11
+
9
12
  switch (action) {
10
13
  case 'List':
11
- return `export class List${name}Repository {
12
- async handle() {
14
+ return `import { ${name} } from '${typePath}';
15
+
16
+ export class List${name}Repository {
17
+ async handle(): Promise<${name}[]> {
13
18
  const response = await fetch('/api/${feature}');
19
+ if (!response.ok) throw new Error('Failed to fetch ${name} list');
14
20
  return response.json();
15
21
  }
16
22
  }
17
23
  `;
18
24
  case 'Show':
19
- return `export class Show${name}Repository {
20
- async handle(id: string) {
25
+ return `import { ${name} } from '${typePath}';
26
+
27
+ export class Show${name}Repository {
28
+ async handle(id: string): Promise<${name}> {
21
29
  const response = await fetch(\`/api/${feature}/\${id}\`);
30
+ if (!response.ok) throw new Error('Failed to fetch ${name}');
22
31
  return response.json();
23
32
  }
24
33
  }
25
34
  `;
26
35
  case 'Create':
27
- return `export class Create${name}Repository {
28
- async handle(data: unknown) {
36
+ return `import { ${name} } from '${typePath}';
37
+ import { Create${name} } from '${schemaPath('Create')}';
38
+
39
+ export class Create${name}Repository {
40
+ async handle(data: Create${name}): Promise<${name}> {
29
41
  const response = await fetch('/api/${feature}', {
30
42
  method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
31
44
  body: JSON.stringify(data),
32
45
  });
46
+ if (!response.ok) throw new Error('Failed to create ${name}');
33
47
  return response.json();
34
48
  }
35
49
  }
36
50
  `;
37
51
  case 'Update':
38
- return `export class Update${name}Repository {
39
- async handle(id: string, data: unknown) {
52
+ return `import { ${name} } from '${typePath}';
53
+ import { Update${name} } from '${schemaPath('Update')}';
54
+
55
+ export class Update${name}Repository {
56
+ async handle(id: string, data: Update${name}): Promise<${name}> {
40
57
  const response = await fetch(\`/api/${feature}/\${id}\`, {
41
58
  method: 'PUT',
59
+ headers: { 'Content-Type': 'application/json' },
42
60
  body: JSON.stringify(data),
43
61
  });
62
+ if (!response.ok) throw new Error('Failed to update ${name}');
44
63
  return response.json();
45
64
  }
46
65
  }
47
66
  `;
48
67
  case 'Delete':
49
68
  return `export class Delete${name}Repository {
50
- async handle(id: string) {
69
+ async handle(id: string): Promise<void> {
51
70
  const response = await fetch(\`/api/${feature}/\${id}\`, {
52
71
  method: 'DELETE',
53
72
  });
54
- return response.json();
73
+ if (!response.ok) throw new Error('Failed to delete ${name}');
55
74
  }
56
75
  }
57
76
  `;
58
77
  }
59
78
  }
60
79
 
61
- export function makeRepository(feature: string, name: string) {
62
- const base = path.join(process.cwd(), 'app', feature, 'repositories');
63
-
64
- if (!fs.existsSync(base)) {
65
- console.error(`❌ Feature "${feature}" does not exist. Run make:feature ${feature} first.`);
66
- process.exit(1);
67
- }
80
+ export function makeRepository(feature: string, name: string): void {
81
+ const base = ensureFeatureExists(feature, 'repositories');
68
82
 
69
83
  for (const action of REPOSITORY_ACTIONS) {
70
84
  const filePath = path.join(base, `${action}${name}.repository.ts`);
71
85
 
72
- if (fs.existsSync(filePath)) {
86
+ if (fileExists(filePath)) {
73
87
  console.warn(`⚠️ Skipping "${action}${name}.repository.ts" — already exists`);
74
88
  continue;
75
89
  }
76
90
 
77
- fs.writeFileSync(filePath, renderRepository(action, name, feature));
91
+ writeFileSafe(filePath, renderRepository(action, name, feature));
78
92
  }
79
93
 
80
94
  console.log(`✅ Repositories for "${name}" created at ${base}`);
81
- }
95
+ }
@@ -1,5 +1,5 @@
1
- import * as fs from 'fs';
2
1
  import * as path from 'path';
2
+ import { ensureFeatureExists, writeFileSafe, fileExists } from '../utils';
3
3
 
4
4
  type SchemaAction = 'Create' | 'Update';
5
5
 
@@ -29,23 +29,18 @@ export type Update${name} = z.infer<typeof Update${name}Schema>;
29
29
  }
30
30
  }
31
31
 
32
- export function makeSchema(feature: string, name: string) {
33
- const base = path.join(process.cwd(), 'app', feature, 'schemas');
34
-
35
- if (!fs.existsSync(base)) {
36
- console.error(`❌ Feature "${feature}" does not exist. Run make:feature ${feature} first.`);
37
- process.exit(1);
38
- }
32
+ export function makeSchema(feature: string, name: string): void {
33
+ const base = ensureFeatureExists(feature, 'schemas');
39
34
 
40
35
  for (const action of SCHEMA_ACTIONS) {
41
36
  const filePath = path.join(base, `${action}${name}.schema.ts`);
42
37
 
43
- if (fs.existsSync(filePath)) {
38
+ if (fileExists(filePath)) {
44
39
  console.warn(`⚠️ Skipping "${action}${name}.schema.ts" — already exists`);
45
40
  continue;
46
41
  }
47
42
 
48
- fs.writeFileSync(filePath, renderSchema(action, name));
43
+ writeFileSafe(filePath, renderSchema(action, name));
49
44
  }
50
45
 
51
46
  console.log(`✅ Schemas for "${name}" created at ${base}`);