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.
- package/README.md +159 -145
- package/dist/cli.js +208 -0
- package/dist/commands/action.js +118 -0
- package/dist/commands/component.js +18 -19
- package/dist/commands/container.js +10 -31
- package/dist/commands/controller.js +85 -0
- package/dist/commands/feature.js +59 -51
- package/dist/commands/hints.js +28 -0
- package/dist/commands/hook.js +10 -111
- package/dist/commands/repository.js +17 -115
- package/dist/commands/resolve.js +61 -0
- package/dist/commands/schema.js +22 -69
- package/dist/commands/service.js +15 -113
- package/dist/commands/sides.js +34 -0
- package/dist/commands/target.js +32 -0
- package/dist/commands/types.js +9 -17
- package/dist/commands/update.js +148 -0
- package/dist/commands/write.js +66 -0
- package/dist/index.js +8 -122
- package/dist/init/content.js +73 -0
- package/dist/init/init.js +66 -0
- package/dist/init/markers.js +21 -0
- package/dist/postinstall.js +80 -0
- package/dist/stack/detect.js +125 -0
- package/dist/stack/profiles/nest.js +18 -0
- package/dist/stack/profiles/next-frontend.js +28 -0
- package/dist/stack/profiles/next-fullstack.js +44 -0
- package/dist/stack/profiles/node.js +17 -0
- package/dist/stack/profiles/react.js +19 -0
- package/dist/stack/registry.js +63 -0
- package/dist/stack/types.js +24 -0
- package/dist/templates/actions.js +68 -0
- package/dist/templates/backend/server-repository.js +22 -0
- package/dist/templates/context.js +52 -0
- package/dist/templates/controllers/express.js +60 -0
- package/dist/templates/controllers/fastify.js +54 -0
- package/dist/templates/controllers/generic.js +30 -0
- package/dist/templates/controllers/hono.js +52 -0
- package/dist/templates/controllers/nest.js +63 -0
- package/dist/templates/controllers/next-action-route.js +40 -0
- package/dist/templates/controllers/next-route.js +62 -0
- package/dist/templates/controllers/node.js +46 -0
- package/dist/templates/controllers/shape.js +28 -0
- package/dist/templates/frontend/client-repository.js +38 -0
- package/dist/templates/frontend/component.js +18 -0
- package/dist/templates/frontend/container.js +28 -0
- package/{src/commands/hook.ts → dist/templates/frontend/hook.js} +28 -41
- package/dist/templates/frontend/page.js +26 -0
- package/dist/templates/nest/dto.js +12 -0
- package/dist/templates/nest/injectable.js +4 -0
- package/dist/templates/nest/module.js +41 -0
- package/dist/templates/service.js +40 -0
- package/dist/templates/shared/schema.js +13 -0
- package/dist/templates/shared/types.js +12 -0
- package/dist/templates/signatures.js +15 -0
- package/dist/update/cache.js +80 -0
- package/dist/update/check.js +47 -0
- package/dist/update/install-mode.js +96 -0
- package/dist/update/package-manager.js +99 -0
- package/dist/update/registry.js +77 -0
- package/dist/update/version.js +70 -0
- package/dist/utils/alias.js +80 -0
- package/dist/utils/fs.js +70 -0
- package/dist/utils/imports.js +61 -0
- package/dist/utils/naming.js +31 -0
- package/dist/utils/paths.js +44 -0
- package/package.json +27 -6
- package/scripts/postinstall.js +7 -0
- package/.github/workflows/publish.yml +0 -28
- package/dist/utils.js +0 -132
- package/src/__tests__/utils.test.ts +0 -128
- package/src/commands/__tests__/feature.test.ts +0 -85
- package/src/commands/__tests__/imports.test.ts +0 -115
- package/src/commands/__tests__/individual.test.ts +0 -137
- package/src/commands/component.ts +0 -33
- package/src/commands/container.ts +0 -42
- package/src/commands/feature.ts +0 -74
- package/src/commands/repository.ts +0 -95
- package/src/commands/schema.ts +0 -47
- package/src/commands/service.ts +0 -93
- package/src/commands/types.ts +0 -25
- package/src/index.ts +0 -126
- package/src/utils.ts +0 -115
- package/tsconfig.json +0 -13
- package/vitest.config.ts +0 -7
|
@@ -1,137 +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 { 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,33 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { ensureFeatureExists, writeFileSafe, fileExists } from '../utils';
|
|
3
|
-
|
|
4
|
-
type ComponentType = 'client' | 'server';
|
|
5
|
-
|
|
6
|
-
function renderComponent(name: string, type: ComponentType): string {
|
|
7
|
-
const directive = type === 'client' ? "'use client';\n\n" : '';
|
|
8
|
-
|
|
9
|
-
return `${directive}interface ${name}Props {
|
|
10
|
-
id: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function ${name}({ id }: ${name}Props) {
|
|
14
|
-
return (
|
|
15
|
-
<div>
|
|
16
|
-
<h1>${name}</h1>
|
|
17
|
-
</div>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function makeComponent(feature: string, name: string, type: ComponentType = 'client'): void {
|
|
24
|
-
const base = ensureFeatureExists(feature, `components/${type}`);
|
|
25
|
-
const filePath = path.join(base, `${name}.tsx`);
|
|
26
|
-
|
|
27
|
-
if (fileExists(filePath)) {
|
|
28
|
-
throw new Error(`Component "${name}" already exists at ${filePath}`);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
writeFileSafe(filePath, renderComponent(name, type));
|
|
32
|
-
console.log(`✅ Component "${name}" created at ${filePath}`);
|
|
33
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
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}`);
|
|
8
|
-
|
|
9
|
-
return `'use client';
|
|
10
|
-
|
|
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>;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div>
|
|
22
|
-
{items.map((item) => (
|
|
23
|
-
<${name} key={item.id} {...item} />
|
|
24
|
-
))}
|
|
25
|
-
</div>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function makeContainer(feature: string, name: string, pascalName?: string): void {
|
|
32
|
-
const base = ensureFeatureExists(feature, 'containers');
|
|
33
|
-
const filePath = path.join(base, `${name}.tsx`);
|
|
34
|
-
|
|
35
|
-
if (fileExists(filePath)) {
|
|
36
|
-
throw new Error(`Container "${name}" already exists at ${filePath}`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const entityName = pascalName ?? name.replace(/Container$/, '');
|
|
40
|
-
writeFileSafe(filePath, renderContainer(name, entityName, feature));
|
|
41
|
-
console.log(`✅ Container "${name}" created at ${filePath}`);
|
|
42
|
-
}
|
package/src/commands/feature.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { toPascalCase, featureBasePath, writeFileSafe, mkdirSafe, fileExists, resolveImport } from '../utils';
|
|
3
|
-
import { makeComponent } from './component';
|
|
4
|
-
import { makeHook } from './hook';
|
|
5
|
-
import { makeService } from './service';
|
|
6
|
-
import { makeRepository } from './repository';
|
|
7
|
-
import { makeSchema } from './schema';
|
|
8
|
-
import { makeContainer } from './container';
|
|
9
|
-
import { makeTypes } from './types';
|
|
10
|
-
|
|
11
|
-
const FEATURE_DIRS = [
|
|
12
|
-
'components/server',
|
|
13
|
-
'components/client',
|
|
14
|
-
'containers',
|
|
15
|
-
'hooks',
|
|
16
|
-
'services',
|
|
17
|
-
'repositories',
|
|
18
|
-
'schemas',
|
|
19
|
-
'types',
|
|
20
|
-
];
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
export default function ${pascalName}Page() {
|
|
28
|
-
return (
|
|
29
|
-
<div>
|
|
30
|
-
<${pascalName}Container />
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export async function makeFeature(name: string, all: boolean = false): Promise<void> {
|
|
38
|
-
const base = featureBasePath(name);
|
|
39
|
-
const pascalName = toPascalCase(name);
|
|
40
|
-
|
|
41
|
-
if (fileExists(base)) {
|
|
42
|
-
throw new Error(`Feature "${name}" already exists at ${base}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const dir of FEATURE_DIRS) {
|
|
46
|
-
mkdirSafe(path.join(base, dir));
|
|
47
|
-
if (!all) {
|
|
48
|
-
writeFileSafe(path.join(base, dir, '.gitkeep'), '');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
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
|
-
|
|
61
|
-
console.log(`✅ Feature "${name}" scaffolded at ${base}`);
|
|
62
|
-
|
|
63
|
-
if (all) {
|
|
64
|
-
makeTypes(name, pascalName);
|
|
65
|
-
makeSchema(name, pascalName);
|
|
66
|
-
makeRepository(name, pascalName);
|
|
67
|
-
makeService(name, pascalName);
|
|
68
|
-
makeHook(name, `use${pascalName}`, pascalName);
|
|
69
|
-
makeComponent(name, pascalName, 'client');
|
|
70
|
-
makeContainer(name, `${pascalName}Container`, pascalName);
|
|
71
|
-
|
|
72
|
-
console.log(`✅ All files scaffolded for "${name}"`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { ensureFeatureExists, writeFileSafe, fileExists, resolveImport } from '../utils';
|
|
3
|
-
|
|
4
|
-
type RepositoryAction = 'List' | 'Create' | 'Update' | 'Delete' | 'Show';
|
|
5
|
-
|
|
6
|
-
const REPOSITORY_ACTIONS: RepositoryAction[] = ['List', 'Create', 'Update', 'Delete', 'Show'];
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
switch (action) {
|
|
13
|
-
case 'List':
|
|
14
|
-
return `import { ${name} } from '${typePath}';
|
|
15
|
-
|
|
16
|
-
export class List${name}Repository {
|
|
17
|
-
async handle(): Promise<${name}[]> {
|
|
18
|
-
const response = await fetch('/api/${feature}');
|
|
19
|
-
if (!response.ok) throw new Error('Failed to fetch ${name} list');
|
|
20
|
-
return response.json();
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
`;
|
|
24
|
-
case 'Show':
|
|
25
|
-
return `import { ${name} } from '${typePath}';
|
|
26
|
-
|
|
27
|
-
export class Show${name}Repository {
|
|
28
|
-
async handle(id: string): Promise<${name}> {
|
|
29
|
-
const response = await fetch(\`/api/${feature}/\${id}\`);
|
|
30
|
-
if (!response.ok) throw new Error('Failed to fetch ${name}');
|
|
31
|
-
return response.json();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
`;
|
|
35
|
-
case 'Create':
|
|
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}> {
|
|
41
|
-
const response = await fetch('/api/${feature}', {
|
|
42
|
-
method: 'POST',
|
|
43
|
-
headers: { 'Content-Type': 'application/json' },
|
|
44
|
-
body: JSON.stringify(data),
|
|
45
|
-
});
|
|
46
|
-
if (!response.ok) throw new Error('Failed to create ${name}');
|
|
47
|
-
return response.json();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
`;
|
|
51
|
-
case 'Update':
|
|
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}> {
|
|
57
|
-
const response = await fetch(\`/api/${feature}/\${id}\`, {
|
|
58
|
-
method: 'PUT',
|
|
59
|
-
headers: { 'Content-Type': 'application/json' },
|
|
60
|
-
body: JSON.stringify(data),
|
|
61
|
-
});
|
|
62
|
-
if (!response.ok) throw new Error('Failed to update ${name}');
|
|
63
|
-
return response.json();
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
`;
|
|
67
|
-
case 'Delete':
|
|
68
|
-
return `export class Delete${name}Repository {
|
|
69
|
-
async handle(id: string): Promise<void> {
|
|
70
|
-
const response = await fetch(\`/api/${feature}/\${id}\`, {
|
|
71
|
-
method: 'DELETE',
|
|
72
|
-
});
|
|
73
|
-
if (!response.ok) throw new Error('Failed to delete ${name}');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
`;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function makeRepository(feature: string, name: string): void {
|
|
81
|
-
const base = ensureFeatureExists(feature, 'repositories');
|
|
82
|
-
|
|
83
|
-
for (const action of REPOSITORY_ACTIONS) {
|
|
84
|
-
const filePath = path.join(base, `${action}${name}.repository.ts`);
|
|
85
|
-
|
|
86
|
-
if (fileExists(filePath)) {
|
|
87
|
-
console.warn(`⚠️ Skipping "${action}${name}.repository.ts" — already exists`);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
writeFileSafe(filePath, renderRepository(action, name, feature));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
console.log(`✅ Repositories for "${name}" created at ${base}`);
|
|
95
|
-
}
|
package/src/commands/schema.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { ensureFeatureExists, writeFileSafe, fileExists } from '../utils';
|
|
3
|
-
|
|
4
|
-
type SchemaAction = 'Create' | 'Update';
|
|
5
|
-
|
|
6
|
-
const SCHEMA_ACTIONS: SchemaAction[] = ['Create', 'Update'];
|
|
7
|
-
|
|
8
|
-
function renderSchema(action: SchemaAction, name: string): string {
|
|
9
|
-
switch (action) {
|
|
10
|
-
case 'Create':
|
|
11
|
-
return `import { z } from 'zod';
|
|
12
|
-
|
|
13
|
-
export const Create${name}Schema = z.object({
|
|
14
|
-
// add create fields here
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export type Create${name} = z.infer<typeof Create${name}Schema>;
|
|
18
|
-
`;
|
|
19
|
-
case 'Update':
|
|
20
|
-
return `import { z } from 'zod';
|
|
21
|
-
|
|
22
|
-
export const Update${name}Schema = z.object({
|
|
23
|
-
id: z.string(),
|
|
24
|
-
// add update fields here
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
export type Update${name} = z.infer<typeof Update${name}Schema>;
|
|
28
|
-
`;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function makeSchema(feature: string, name: string): void {
|
|
33
|
-
const base = ensureFeatureExists(feature, 'schemas');
|
|
34
|
-
|
|
35
|
-
for (const action of SCHEMA_ACTIONS) {
|
|
36
|
-
const filePath = path.join(base, `${action}${name}.schema.ts`);
|
|
37
|
-
|
|
38
|
-
if (fileExists(filePath)) {
|
|
39
|
-
console.warn(`⚠️ Skipping "${action}${name}.schema.ts" — already exists`);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
writeFileSafe(filePath, renderSchema(action, name));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
console.log(`✅ Schemas for "${name}" created at ${base}`);
|
|
47
|
-
}
|
package/src/commands/service.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { ensureFeatureExists, writeFileSafe, fileExists, resolveImport } from '../utils';
|
|
3
|
-
|
|
4
|
-
type ServiceAction = 'List' | 'Create' | 'Update' | 'Delete' | 'Show';
|
|
5
|
-
|
|
6
|
-
const SERVICE_ACTIONS: ServiceAction[] = ['List', 'Create', 'Update', 'Delete', 'Show'];
|
|
7
|
-
|
|
8
|
-
function renderService(action: ServiceAction, name: string, feature: string): string {
|
|
9
|
-
const typePath = resolveImport(feature, `types/${name}.types`);
|
|
10
|
-
const repoPath = (op: string) => resolveImport(feature, `repositories/${op}${name}.repository`);
|
|
11
|
-
const schemaPath = (op: string) => resolveImport(feature, `schemas/${op}${name}.schema`);
|
|
12
|
-
|
|
13
|
-
switch (action) {
|
|
14
|
-
case 'List':
|
|
15
|
-
return `import { ${name} } from '${typePath}';
|
|
16
|
-
import { List${name}Repository } from '${repoPath('List')}';
|
|
17
|
-
|
|
18
|
-
const repository = new List${name}Repository();
|
|
19
|
-
|
|
20
|
-
export class List${name}Service {
|
|
21
|
-
async handle(): Promise<${name}[]> {
|
|
22
|
-
return repository.handle();
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
`;
|
|
26
|
-
case 'Show':
|
|
27
|
-
return `import { ${name} } from '${typePath}';
|
|
28
|
-
import { Show${name}Repository } from '${repoPath('Show')}';
|
|
29
|
-
|
|
30
|
-
const repository = new Show${name}Repository();
|
|
31
|
-
|
|
32
|
-
export class Show${name}Service {
|
|
33
|
-
async handle(id: string): Promise<${name}> {
|
|
34
|
-
return repository.handle(id);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
`;
|
|
38
|
-
case 'Create':
|
|
39
|
-
return `import { ${name} } from '${typePath}';
|
|
40
|
-
import { Create${name} } from '${schemaPath('Create')}';
|
|
41
|
-
import { Create${name}Repository } from '${repoPath('Create')}';
|
|
42
|
-
|
|
43
|
-
const repository = new Create${name}Repository();
|
|
44
|
-
|
|
45
|
-
export class Create${name}Service {
|
|
46
|
-
async handle(data: Create${name}): Promise<${name}> {
|
|
47
|
-
return repository.handle(data);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
`;
|
|
51
|
-
case 'Update':
|
|
52
|
-
return `import { ${name} } from '${typePath}';
|
|
53
|
-
import { Update${name} } from '${schemaPath('Update')}';
|
|
54
|
-
import { Update${name}Repository } from '${repoPath('Update')}';
|
|
55
|
-
|
|
56
|
-
const repository = new Update${name}Repository();
|
|
57
|
-
|
|
58
|
-
export class Update${name}Service {
|
|
59
|
-
async handle(id: string, data: Update${name}): Promise<${name}> {
|
|
60
|
-
return repository.handle(id, data);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
`;
|
|
64
|
-
case 'Delete':
|
|
65
|
-
return `import { Delete${name}Repository } from '${repoPath('Delete')}';
|
|
66
|
-
|
|
67
|
-
const repository = new Delete${name}Repository();
|
|
68
|
-
|
|
69
|
-
export class Delete${name}Service {
|
|
70
|
-
async handle(id: string): Promise<void> {
|
|
71
|
-
return repository.handle(id);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
`;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function makeService(feature: string, name: string): void {
|
|
79
|
-
const base = ensureFeatureExists(feature, 'services');
|
|
80
|
-
|
|
81
|
-
for (const action of SERVICE_ACTIONS) {
|
|
82
|
-
const filePath = path.join(base, `${action}${name}.service.ts`);
|
|
83
|
-
|
|
84
|
-
if (fileExists(filePath)) {
|
|
85
|
-
console.warn(`⚠️ Skipping "${action}${name}.service.ts" — already exists`);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
writeFileSafe(filePath, renderService(action, name, feature));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(`✅ Services for "${name}" created at ${base}`);
|
|
93
|
-
}
|
package/src/commands/types.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
import { ensureFeatureExists, writeFileSafe, fileExists } from '../utils';
|
|
3
|
-
|
|
4
|
-
function renderTypes(name: string): string {
|
|
5
|
-
return `export interface ${name} {
|
|
6
|
-
id: string;
|
|
7
|
-
// add ${name} fields here
|
|
8
|
-
createdAt: string;
|
|
9
|
-
updatedAt: string;
|
|
10
|
-
}
|
|
11
|
-
`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function makeTypes(feature: string, name: string): void {
|
|
15
|
-
const base = ensureFeatureExists(feature, 'types');
|
|
16
|
-
const filePath = path.join(base, `${name}.types.ts`);
|
|
17
|
-
|
|
18
|
-
if (fileExists(filePath)) {
|
|
19
|
-
console.warn(`⚠️ Skipping "${name}.types.ts" — already exists`);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
writeFileSafe(filePath, renderTypes(name));
|
|
24
|
-
console.log(`✅ Types for "${name}" created at ${filePath}`);
|
|
25
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { makeFeature } from './commands/feature';
|
|
4
|
-
import { makeComponent } from './commands/component';
|
|
5
|
-
import { makeHook } from './commands/hook';
|
|
6
|
-
import { makeService } from './commands/service';
|
|
7
|
-
import { makeSchema } from './commands/schema';
|
|
8
|
-
import { makeRepository } from './commands/repository';
|
|
9
|
-
import { makeContainer } from './commands/container';
|
|
10
|
-
import { makeTypes } from './commands/types';
|
|
11
|
-
|
|
12
|
-
const program = new Command();
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.name('domain-driver')
|
|
16
|
-
.description('CLI scaffolding tool for domain-driven development in Next.js')
|
|
17
|
-
.version('0.1.0');
|
|
18
|
-
|
|
19
|
-
program
|
|
20
|
-
.command('make:feature <name>')
|
|
21
|
-
.description('Scaffold a full feature folder structure')
|
|
22
|
-
.option('-a, --all', 'Scaffold all files inside each folder')
|
|
23
|
-
.action(async (name: string, options: { all?: boolean }) => {
|
|
24
|
-
try {
|
|
25
|
-
await makeFeature(name, options.all ?? false);
|
|
26
|
-
} catch (error: unknown) {
|
|
27
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
28
|
-
console.error(`❌ ${message}`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
program
|
|
34
|
-
.command('make:component <feature> <name>')
|
|
35
|
-
.description('Scaffold a component inside an existing feature')
|
|
36
|
-
.argument('[type]', 'Component type: client or server', 'client')
|
|
37
|
-
.action((feature: string, name: string, type: string) => {
|
|
38
|
-
try {
|
|
39
|
-
const componentType = type === 'server' ? 'server' : 'client';
|
|
40
|
-
makeComponent(feature, name, componentType);
|
|
41
|
-
} catch (error: unknown) {
|
|
42
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
43
|
-
console.error(`❌ ${message}`);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
program
|
|
49
|
-
.command('make:container <feature> <name>')
|
|
50
|
-
.description('Scaffold a smart container component inside an existing feature')
|
|
51
|
-
.action((feature: string, name: string) => {
|
|
52
|
-
try {
|
|
53
|
-
makeContainer(feature, name);
|
|
54
|
-
} catch (error: unknown) {
|
|
55
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
56
|
-
console.error(`❌ ${message}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
program
|
|
62
|
-
.command('make:hook <feature> <name>')
|
|
63
|
-
.description('Scaffold a custom hook inside an existing feature')
|
|
64
|
-
.action((feature: string, name: string) => {
|
|
65
|
-
try {
|
|
66
|
-
makeHook(feature, name);
|
|
67
|
-
} catch (error: unknown) {
|
|
68
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
69
|
-
console.error(`❌ ${message}`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
program
|
|
75
|
-
.command('make:service <feature> <name>')
|
|
76
|
-
.description('Scaffold single-responsibility service files inside an existing feature')
|
|
77
|
-
.action((feature: string, name: string) => {
|
|
78
|
-
try {
|
|
79
|
-
makeService(feature, name);
|
|
80
|
-
} catch (error: unknown) {
|
|
81
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
82
|
-
console.error(`❌ ${message}`);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
program
|
|
88
|
-
.command('make:repository <feature> <name>')
|
|
89
|
-
.description('Scaffold single-responsibility repository files inside an existing feature')
|
|
90
|
-
.action((feature: string, name: string) => {
|
|
91
|
-
try {
|
|
92
|
-
makeRepository(feature, name);
|
|
93
|
-
} catch (error: unknown) {
|
|
94
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
95
|
-
console.error(`❌ ${message}`);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
program
|
|
101
|
-
.command('make:schema <feature> <name>')
|
|
102
|
-
.description('Scaffold Zod schemas for create and update operations')
|
|
103
|
-
.action((feature: string, name: string) => {
|
|
104
|
-
try {
|
|
105
|
-
makeSchema(feature, name);
|
|
106
|
-
} catch (error: unknown) {
|
|
107
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
108
|
-
console.error(`❌ ${message}`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
program
|
|
114
|
-
.command('make:types <feature> <name>')
|
|
115
|
-
.description('Scaffold a types file inside an existing feature')
|
|
116
|
-
.action((feature: string, name: string) => {
|
|
117
|
-
try {
|
|
118
|
-
makeTypes(feature, name);
|
|
119
|
-
} catch (error: unknown) {
|
|
120
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
121
|
-
console.error(`❌ ${message}`);
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
program.parse();
|