domain-driver 0.1.0 → 0.2.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 +138 -146
- 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/write.js +66 -0
- package/dist/index.js +109 -94
- package/dist/init/content.js +73 -0
- package/dist/init/init.js +60 -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/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
package/dist/commands/service.js
CHANGED
|
@@ -1,119 +1,21 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.makeService = makeService;
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
export class List${name}Service {
|
|
52
|
-
async handle(): Promise<${name}[]> {
|
|
53
|
-
return repository.handle();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
`;
|
|
57
|
-
case 'Show':
|
|
58
|
-
return `import { ${name} } from '${typePath}';
|
|
59
|
-
import { Show${name}Repository } from '${repoPath('Show')}';
|
|
60
|
-
|
|
61
|
-
const repository = new Show${name}Repository();
|
|
62
|
-
|
|
63
|
-
export class Show${name}Service {
|
|
64
|
-
async handle(id: string): Promise<${name}> {
|
|
65
|
-
return repository.handle(id);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
`;
|
|
69
|
-
case 'Create':
|
|
70
|
-
return `import { ${name} } from '${typePath}';
|
|
71
|
-
import { Create${name} } from '${schemaPath('Create')}';
|
|
72
|
-
import { Create${name}Repository } from '${repoPath('Create')}';
|
|
73
|
-
|
|
74
|
-
const repository = new Create${name}Repository();
|
|
75
|
-
|
|
76
|
-
export class Create${name}Service {
|
|
77
|
-
async handle(data: Create${name}): Promise<${name}> {
|
|
78
|
-
return repository.handle(data);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
`;
|
|
82
|
-
case 'Update':
|
|
83
|
-
return `import { ${name} } from '${typePath}';
|
|
84
|
-
import { Update${name} } from '${schemaPath('Update')}';
|
|
85
|
-
import { Update${name}Repository } from '${repoPath('Update')}';
|
|
86
|
-
|
|
87
|
-
const repository = new Update${name}Repository();
|
|
88
|
-
|
|
89
|
-
export class Update${name}Service {
|
|
90
|
-
async handle(id: string, data: Update${name}): Promise<${name}> {
|
|
91
|
-
return repository.handle(id, data);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
`;
|
|
95
|
-
case 'Delete':
|
|
96
|
-
return `import { Delete${name}Repository } from '${repoPath('Delete')}';
|
|
97
|
-
|
|
98
|
-
const repository = new Delete${name}Repository();
|
|
99
|
-
|
|
100
|
-
export class Delete${name}Service {
|
|
101
|
-
async handle(id: string): Promise<void> {
|
|
102
|
-
return repository.handle(id);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
`;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function makeService(feature, name) {
|
|
109
|
-
const base = (0, utils_1.ensureFeatureExists)(feature, 'services');
|
|
110
|
-
for (const action of SERVICE_ACTIONS) {
|
|
111
|
-
const filePath = path.join(base, `${action}${name}.service.ts`);
|
|
112
|
-
if ((0, utils_1.fileExists)(filePath)) {
|
|
113
|
-
console.warn(`⚠️ Skipping "${action}${name}.service.ts" — already exists`);
|
|
114
|
-
continue;
|
|
4
|
+
const actions_1 = require("../templates/actions");
|
|
5
|
+
const service_1 = require("../templates/service");
|
|
6
|
+
const resolve_1 = require("./resolve");
|
|
7
|
+
const sides_1 = require("./sides");
|
|
8
|
+
const write_1 = require("./write");
|
|
9
|
+
function makeService(feature, name, side = 'both') {
|
|
10
|
+
const ctx = (0, resolve_1.requireFeature)(feature);
|
|
11
|
+
let wroteAny = false;
|
|
12
|
+
for (const current of (0, sides_1.resolveSides)(ctx.profile, side, sides_1.SERVICE_SIDES)) {
|
|
13
|
+
const dir = (0, resolve_1.ensureLayerDir)(ctx, sides_1.SERVICE_SIDES[current]);
|
|
14
|
+
const written = (0, write_1.writeSpecFiles)(dir, (0, actions_1.standardActions)(name), 'service', (spec, filePath) => (0, service_1.renderService)(ctx, spec, name, filePath, current));
|
|
15
|
+
if (written > 0) {
|
|
16
|
+
console.log(`✅ Services (${current}) for "${name}" created at ${dir}`);
|
|
17
|
+
wroteAny = true;
|
|
115
18
|
}
|
|
116
|
-
(0, utils_1.writeFileSafe)(filePath, renderService(action, name, feature));
|
|
117
19
|
}
|
|
118
|
-
|
|
20
|
+
return wroteAny;
|
|
119
21
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REPOSITORY_SIDES = exports.SERVICE_SIDES = void 0;
|
|
4
|
+
exports.parseSide = parseSide;
|
|
5
|
+
exports.resolveSides = resolveSides;
|
|
6
|
+
const registry_1 = require("../stack/registry");
|
|
7
|
+
exports.SERVICE_SIDES = Object.freeze({
|
|
8
|
+
client: 'clientService',
|
|
9
|
+
server: 'serverService',
|
|
10
|
+
command: 'make:service',
|
|
11
|
+
noun: 'services',
|
|
12
|
+
});
|
|
13
|
+
exports.REPOSITORY_SIDES = Object.freeze({
|
|
14
|
+
client: 'clientRepository',
|
|
15
|
+
server: 'serverRepository',
|
|
16
|
+
command: 'make:repository',
|
|
17
|
+
noun: 'repositories',
|
|
18
|
+
});
|
|
19
|
+
const SIDES = ['client', 'server'];
|
|
20
|
+
function parseSide(value) {
|
|
21
|
+
if (value === 'client' || value === 'server' || value === 'both')
|
|
22
|
+
return value;
|
|
23
|
+
throw new Error(`Invalid --side "${value}". Use client, server, or both.`);
|
|
24
|
+
}
|
|
25
|
+
function resolveSides(profile, option, spec) {
|
|
26
|
+
const available = SIDES.filter((side) => (0, registry_1.hasLayer)(profile, spec[side]));
|
|
27
|
+
if (available.length === 0)
|
|
28
|
+
(0, registry_1.assertLayer)(profile, spec.server, spec.command);
|
|
29
|
+
if (option === 'both')
|
|
30
|
+
return available;
|
|
31
|
+
if (available.includes(option))
|
|
32
|
+
return [option];
|
|
33
|
+
throw new Error(`The ${profile.name} stack has no ${option}-side ${spec.noun}.`);
|
|
34
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTarget = parseTarget;
|
|
4
|
+
exports.parseFeatureTarget = parseFeatureTarget;
|
|
5
|
+
const naming_1 = require("../utils/naming");
|
|
6
|
+
const NAME_PATTERN = /^[A-Za-z][A-Za-z0-9]*$/;
|
|
7
|
+
function validateName(name) {
|
|
8
|
+
if (!NAME_PATTERN.test(name)) {
|
|
9
|
+
throw new Error(`Name "${name}" must be letters and digits only, for example User.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function parseTarget(value) {
|
|
13
|
+
const parts = value.split('/');
|
|
14
|
+
if (parts.length !== 2) {
|
|
15
|
+
throw new Error(`Target "${value}" must be <feature>/<Name>, for example users/User.`);
|
|
16
|
+
}
|
|
17
|
+
const [feature, name] = parts;
|
|
18
|
+
(0, naming_1.validateFeatureName)(feature);
|
|
19
|
+
validateName(name);
|
|
20
|
+
return Object.freeze({ feature, name });
|
|
21
|
+
}
|
|
22
|
+
function parseFeatureTarget(value) {
|
|
23
|
+
const parts = value.split('/');
|
|
24
|
+
if (parts.length > 2) {
|
|
25
|
+
throw new Error(`Target "${value}" must be <feature> or <feature>/<Entity>, for example users or users/User.`);
|
|
26
|
+
}
|
|
27
|
+
const [feature, entity] = parts;
|
|
28
|
+
(0, naming_1.validateFeatureName)(feature);
|
|
29
|
+
if (entity !== undefined)
|
|
30
|
+
validateName(entity);
|
|
31
|
+
return Object.freeze({ feature, entity: entity ?? null });
|
|
32
|
+
}
|
package/dist/commands/types.js
CHANGED
|
@@ -35,23 +35,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.makeTypes = makeTypes;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// add ${name} fields here
|
|
43
|
-
createdAt: string;
|
|
44
|
-
updatedAt: string;
|
|
45
|
-
}
|
|
46
|
-
`;
|
|
47
|
-
}
|
|
38
|
+
const registry_1 = require("../stack/registry");
|
|
39
|
+
const types_1 = require("../templates/shared/types");
|
|
40
|
+
const resolve_1 = require("./resolve");
|
|
41
|
+
const write_1 = require("./write");
|
|
48
42
|
function makeTypes(feature, name) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
const ctx = (0, resolve_1.requireFeature)(feature);
|
|
44
|
+
(0, registry_1.assertLayer)(ctx.profile, 'types', 'make:types');
|
|
45
|
+
const filePath = path.join((0, resolve_1.ensureLayerDir)(ctx, 'types'), `${name}.types.ts`);
|
|
46
|
+
if ((0, write_1.writeIfAbsent)(filePath, () => (0, types_1.renderTypes)(name))) {
|
|
47
|
+
console.log(`✅ Types for "${name}" created at ${filePath}`);
|
|
54
48
|
}
|
|
55
|
-
(0, utils_1.writeFileSafe)(filePath, renderTypes(name));
|
|
56
|
-
console.log(`✅ Types for "${name}" created at ${filePath}`);
|
|
57
49
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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.writeIfAbsent = writeIfAbsent;
|
|
37
|
+
exports.writeActionFiles = writeActionFiles;
|
|
38
|
+
exports.writeSpecFiles = writeSpecFiles;
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs_1 = require("../utils/fs");
|
|
41
|
+
function writeIfAbsent(filePath, render) {
|
|
42
|
+
if ((0, fs_1.fileExists)(filePath)) {
|
|
43
|
+
console.warn(`⚠️ Skipping "${path.basename(filePath)}" — already exists`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
(0, fs_1.writeFileSafe)(filePath, render());
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
function writeActionFiles(dir, entity, suffix, actions, render) {
|
|
50
|
+
let written = 0;
|
|
51
|
+
for (const action of actions) {
|
|
52
|
+
const filePath = path.join(dir, `${action}${entity}.${suffix}.ts`);
|
|
53
|
+
if (writeIfAbsent(filePath, () => render(action, filePath)))
|
|
54
|
+
written += 1;
|
|
55
|
+
}
|
|
56
|
+
return written;
|
|
57
|
+
}
|
|
58
|
+
function writeSpecFiles(dir, specs, suffix, render) {
|
|
59
|
+
let written = 0;
|
|
60
|
+
for (const spec of specs) {
|
|
61
|
+
const filePath = path.join(dir, `${spec.name}.${suffix}.ts`);
|
|
62
|
+
if (writeIfAbsent(filePath, () => render(spec, filePath)))
|
|
63
|
+
written += 1;
|
|
64
|
+
}
|
|
65
|
+
return written;
|
|
66
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,125 +1,140 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
// src/index.ts
|
|
4
5
|
const commander_1 = require("commander");
|
|
5
|
-
const
|
|
6
|
+
const action_1 = require("./commands/action");
|
|
6
7
|
const component_1 = require("./commands/component");
|
|
8
|
+
const container_1 = require("./commands/container");
|
|
9
|
+
const controller_1 = require("./commands/controller");
|
|
10
|
+
const feature_1 = require("./commands/feature");
|
|
11
|
+
const hints_1 = require("./commands/hints");
|
|
7
12
|
const hook_1 = require("./commands/hook");
|
|
8
|
-
const service_1 = require("./commands/service");
|
|
9
|
-
const schema_1 = require("./commands/schema");
|
|
10
13
|
const repository_1 = require("./commands/repository");
|
|
11
|
-
const
|
|
14
|
+
const schema_1 = require("./commands/schema");
|
|
15
|
+
const service_1 = require("./commands/service");
|
|
16
|
+
const sides_1 = require("./commands/sides");
|
|
17
|
+
const target_1 = require("./commands/target");
|
|
12
18
|
const types_1 = require("./commands/types");
|
|
19
|
+
const init_1 = require("./init/init");
|
|
20
|
+
const detect_1 = require("./stack/detect");
|
|
21
|
+
const types_2 = require("./stack/types");
|
|
22
|
+
const actions_1 = require("./templates/actions");
|
|
13
23
|
const program = new commander_1.Command();
|
|
14
24
|
program
|
|
15
25
|
.name('domain-driver')
|
|
16
|
-
.description('CLI scaffolding tool for domain-driven
|
|
17
|
-
.version('0.
|
|
26
|
+
.description('CLI scaffolding tool for domain-driven feature folders in Next.js, React, Node, and NestJS projects')
|
|
27
|
+
.version('0.2.0')
|
|
28
|
+
.option('--stack <name>', `Override stack detection (${types_2.STACK_NAMES.join(', ')})`);
|
|
29
|
+
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
30
|
+
if (actionCommand.name() === 'init')
|
|
31
|
+
return;
|
|
32
|
+
const { stack } = program.opts();
|
|
33
|
+
console.log((0, detect_1.describeStack)((0, detect_1.detectStack)(stack)));
|
|
34
|
+
});
|
|
18
35
|
program
|
|
19
|
-
.command('make:feature <
|
|
20
|
-
.description('Scaffold a
|
|
36
|
+
.command('make:feature <target>')
|
|
37
|
+
.description('Scaffold a feature folder for the detected stack (<feature> or <feature>/<Entity>)')
|
|
21
38
|
.option('-a, --all', 'Scaffold all files inside each folder')
|
|
22
|
-
.action(async (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
28
|
-
console.error(`❌ ${message}`);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
39
|
+
.action(async (target, options) => {
|
|
40
|
+
const { feature, entity } = (0, target_1.parseFeatureTarget)(target);
|
|
41
|
+
await (0, feature_1.makeFeature)(feature, options.all ?? false, entity ?? undefined);
|
|
31
42
|
});
|
|
32
43
|
program
|
|
33
|
-
.command('make:component <
|
|
34
|
-
.description('Scaffold a component inside an existing feature')
|
|
44
|
+
.command('make:component <target>')
|
|
45
|
+
.description('Scaffold a component inside an existing feature (<feature>/<Name>)')
|
|
35
46
|
.argument('[type]', 'Component type: client or server', 'client')
|
|
36
|
-
.action((
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
(0, component_1.makeComponent)(feature, name, componentType);
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
43
|
-
console.error(`❌ ${message}`);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
47
|
+
.action((target, type) => {
|
|
48
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
49
|
+
(0, component_1.makeComponent)(feature, name, (0, component_1.parseComponentType)(type));
|
|
46
50
|
});
|
|
47
51
|
program
|
|
48
|
-
.command('make:container <
|
|
49
|
-
.description('Scaffold a smart container component inside an existing feature')
|
|
50
|
-
.action((
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
56
|
-
console.error(`❌ ${message}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
52
|
+
.command('make:container <target>')
|
|
53
|
+
.description('Scaffold a smart container component inside an existing feature (<feature>/<Name>)')
|
|
54
|
+
.action((target) => {
|
|
55
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
56
|
+
(0, container_1.makeContainer)(feature, name);
|
|
59
57
|
});
|
|
60
58
|
program
|
|
61
|
-
.command('make:hook <
|
|
62
|
-
.description('Scaffold a custom hook inside an existing feature')
|
|
63
|
-
.action((
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
69
|
-
console.error(`❌ ${message}`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
59
|
+
.command('make:hook <target>')
|
|
60
|
+
.description('Scaffold a custom hook inside an existing feature (<feature>/<useName>)')
|
|
61
|
+
.action((target) => {
|
|
62
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
63
|
+
(0, hook_1.makeHook)(feature, name);
|
|
72
64
|
});
|
|
73
65
|
program
|
|
74
|
-
.command('make:service <
|
|
75
|
-
.description('Scaffold single-responsibility service files inside an existing feature')
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
console.error(`❌ ${message}`);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
66
|
+
.command('make:service <target>')
|
|
67
|
+
.description('Scaffold single-responsibility service files inside an existing feature (<feature>/<Entity>)')
|
|
68
|
+
.option('--side <side>', 'client, server, or both', 'both')
|
|
69
|
+
.action((target, options) => {
|
|
70
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
71
|
+
const wrote = (0, service_1.makeService)(feature, name, (0, sides_1.parseSide)(options.side));
|
|
72
|
+
if (wrote)
|
|
73
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Service'));
|
|
85
74
|
});
|
|
86
75
|
program
|
|
87
|
-
.command('make:repository <
|
|
88
|
-
.description('Scaffold single-responsibility repository files inside an existing feature')
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.error(`❌ ${message}`);
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
76
|
+
.command('make:repository <target>')
|
|
77
|
+
.description('Scaffold single-responsibility repository files inside an existing feature (<feature>/<Entity>)')
|
|
78
|
+
.option('--side <side>', 'client, server, or both', 'both')
|
|
79
|
+
.action((target, options) => {
|
|
80
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
81
|
+
const wrote = (0, repository_1.makeRepository)(feature, name, (0, sides_1.parseSide)(options.side));
|
|
82
|
+
if (wrote)
|
|
83
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Repository'));
|
|
98
84
|
});
|
|
99
85
|
program
|
|
100
|
-
.command('make:
|
|
101
|
-
.description('Scaffold
|
|
102
|
-
.action((
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
108
|
-
console.error(`❌ ${message}`);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
86
|
+
.command('make:controller <target>')
|
|
87
|
+
.description('Scaffold single-responsibility controllers or route handlers inside an existing feature (<feature>/<Entity>)')
|
|
88
|
+
.action((target) => {
|
|
89
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
90
|
+
const wrote = (0, controller_1.makeController)(feature, name);
|
|
91
|
+
if (wrote)
|
|
92
|
+
(0, hints_1.hintRegisterInModule)(feature, (0, hints_1.standardClassNames)(name, 'Controller'));
|
|
111
93
|
});
|
|
112
94
|
program
|
|
113
|
-
.command('make:
|
|
114
|
-
.description('Scaffold a
|
|
115
|
-
.
|
|
116
|
-
|
|
117
|
-
|
|
95
|
+
.command('make:action <target> <action>')
|
|
96
|
+
.description('Scaffold a bespoke action as its own service, repository, and controller (<feature>/<Entity> <actionName>)')
|
|
97
|
+
.option('--with-input', 'The action takes a request body validated by a Zod schema', false)
|
|
98
|
+
.option('--returns <kind>', 'list, one, or void', 'list')
|
|
99
|
+
.action((target, action, options) => {
|
|
100
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
101
|
+
const returns = (0, action_1.parseReturns)(options.returns);
|
|
102
|
+
const wrote = (0, action_1.makeAction)(feature, name, action, { withInput: options.withInput, returns });
|
|
103
|
+
if (wrote) {
|
|
104
|
+
const { pascal } = (0, actions_1.actionCase)(action);
|
|
105
|
+
(0, hints_1.hintRegisterInModule)(feature, [`${pascal}Controller`, `${pascal}Service`, `${pascal}Repository`], `list ${pascal}Controller before Show${name}Controller in controllers`);
|
|
118
106
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
107
|
+
});
|
|
108
|
+
program
|
|
109
|
+
.command('make:schema <target>')
|
|
110
|
+
.description('Scaffold Zod schemas (and Nest DTOs) for create and update operations (<feature>/<Entity>)')
|
|
111
|
+
.action((target) => {
|
|
112
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
113
|
+
(0, schema_1.makeSchema)(feature, name);
|
|
114
|
+
});
|
|
115
|
+
program
|
|
116
|
+
.command('make:types <target>')
|
|
117
|
+
.description('Scaffold a types file inside an existing feature (<feature>/<Entity>)')
|
|
118
|
+
.action((target) => {
|
|
119
|
+
const { feature, name } = (0, target_1.parseTarget)(target);
|
|
120
|
+
(0, types_1.makeTypes)(feature, name);
|
|
121
|
+
});
|
|
122
|
+
const INIT_ICONS = Object.freeze({
|
|
123
|
+
created: '✅',
|
|
124
|
+
updated: '✅',
|
|
125
|
+
unchanged: 'ℹ️ ',
|
|
126
|
+
});
|
|
127
|
+
program
|
|
128
|
+
.command('init')
|
|
129
|
+
.description('Write agent guidance into this project: AGENTS.md, CLAUDE.md, and .claude/skills/domain-driver/SKILL.md')
|
|
130
|
+
.action(() => {
|
|
131
|
+
for (const result of (0, init_1.runInit)(process.cwd())) {
|
|
132
|
+
console.log(`${INIT_ICONS[result.status]} ${result.file} ${result.status}`);
|
|
123
133
|
}
|
|
124
134
|
});
|
|
125
|
-
|
|
135
|
+
function fail(error) {
|
|
136
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
137
|
+
console.error(`❌ ${message}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
program.parseAsync().catch(fail);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILL_CONTENT = exports.AGENTS_SECTION = void 0;
|
|
4
|
+
const markers_1 = require("./markers");
|
|
5
|
+
const AGENTS_LINES = [
|
|
6
|
+
markers_1.START,
|
|
7
|
+
'## Scaffolding with domain-driver',
|
|
8
|
+
'',
|
|
9
|
+
'This project uses [domain-driver](https://github.com/IsaacHatilima/domain-driver) to scaffold feature folders. Scaffold first, then fill in the generated files. Do not hand-write a layer the tool can generate.',
|
|
10
|
+
'',
|
|
11
|
+
'- New feature: `npx domain-driver make:feature <feature>/<Entity> -a`',
|
|
12
|
+
'- One layer in an existing feature: `npx domain-driver make:<layer> <feature>/<Entity>` where layer is types, schema, repository, service, controller, component, container, or hook',
|
|
13
|
+
'- Any operation that is not List, Show, Create, Update, or Delete: `npx domain-driver make:action <feature>/<Entity> <actionName>`. Add `--with-input` when it takes a request body and `--returns one|void` when it does not return a list.',
|
|
14
|
+
'',
|
|
15
|
+
'Rules the generated code follows, and that new code must keep:',
|
|
16
|
+
'',
|
|
17
|
+
'- One file per action per layer. `findActiveUsers` gets `FindActiveUsers.service.ts`, `FindActiveUsers.repository.ts`, and `FindActiveUsers.controller.ts`. It never goes inside `ShowUser.service.ts` or `ListUser.service.ts`.',
|
|
18
|
+
'- The chain is controller or hook, then service, then repository. Business logic lives in services. Data access lives in repositories. Controllers validate input and call one service.',
|
|
19
|
+
'- Feature folders are kebab-case (`coffee-type`). Entity, action, and class names are PascalCase (`CoffeeType`, `FindActiveUsers`).',
|
|
20
|
+
'- Generated repositories throw until you wire them to your data source. Generated controllers on Node need a line in `<feature>.routes.ts`, and on Nest need registering in `<feature>.module.ts`; the tool prints the exact line.',
|
|
21
|
+
'',
|
|
22
|
+
'Full guidance: `.claude/skills/domain-driver/SKILL.md`. Re-run `npx domain-driver init` after upgrading domain-driver to refresh this section.',
|
|
23
|
+
markers_1.END,
|
|
24
|
+
];
|
|
25
|
+
const SKILL_LINES = [
|
|
26
|
+
'---',
|
|
27
|
+
'name: domain-driver',
|
|
28
|
+
'description: Scaffold domain-driven feature folders with the domain-driver CLI. Use when creating a feature, adding a layer (types, schema, repository, service, controller, component, container, hook) to an existing feature, or adding any operation beyond List, Show, Create, Update, and Delete.',
|
|
29
|
+
'---',
|
|
30
|
+
'',
|
|
31
|
+
'# domain-driver',
|
|
32
|
+
'',
|
|
33
|
+
'Every feature lives in one folder, and every action in every layer is one file. Use the CLI to create files; write the logic inside them by hand.',
|
|
34
|
+
'',
|
|
35
|
+
'## Detect the stack',
|
|
36
|
+
'',
|
|
37
|
+
'The tool reads `package.json` and prints `Stack: <stack> (detected)` before every command. Stacks: `next-fullstack`, `next-frontend`, `react`, `node` (Express, Fastify, Hono, or none), `nest`. Override with `--stack <name>` if detection is wrong.',
|
|
38
|
+
'',
|
|
39
|
+
'## Commands',
|
|
40
|
+
'',
|
|
41
|
+
'| Task | Command |',
|
|
42
|
+
'|---|---|',
|
|
43
|
+
'| New feature with every layer | `npx domain-driver make:feature users/User -a` |',
|
|
44
|
+
'| New feature, folders only | `npx domain-driver make:feature users` |',
|
|
45
|
+
'| Entity interface | `npx domain-driver make:types users/User` |',
|
|
46
|
+
'| Create and Update schemas (and Nest DTOs) | `npx domain-driver make:schema users/User` |',
|
|
47
|
+
'| Five repositories | `npx domain-driver make:repository users/User` |',
|
|
48
|
+
'| Five services | `npx domain-driver make:service users/User` |',
|
|
49
|
+
'| Five controllers or Next route handlers | `npx domain-driver make:controller users/User` |',
|
|
50
|
+
'| A bespoke operation | `npx domain-driver make:action users/User findActiveUsers` |',
|
|
51
|
+
'| Bespoke operation with a request body | `npx domain-driver make:action users/User archiveUser --with-input --returns one` |',
|
|
52
|
+
'| Component, container, hook | `npx domain-driver make:component users/UserCard`, `make:container users/UserContainer`, `make:hook users/useUser` |',
|
|
53
|
+
'| Refresh this guidance | `npx domain-driver init` |',
|
|
54
|
+
'',
|
|
55
|
+
'On `next-fullstack`, `make:service` and `make:repository` take `--side client|server|both` (default both).',
|
|
56
|
+
'',
|
|
57
|
+
'## Rules',
|
|
58
|
+
'',
|
|
59
|
+
'1. **Scaffold before writing.** If a file the tool can generate does not exist yet, generate it. Do not create `services/FindActiveUsers.service.ts` by hand.',
|
|
60
|
+
'2. **One action, one file, every layer.** A new operation is a `make:action`, never a new method on an existing action class and never a branch inside Show or List.',
|
|
61
|
+
'3. **Keep the chain.** Controller or hook calls one service. Service calls one repository. Repositories do data access only.',
|
|
62
|
+
'4. **Names.** Feature folders kebab-case. Entities, actions, and classes PascalCase. Action names include their noun: `archiveUser`, `findActiveUsers`.',
|
|
63
|
+
'5. **Do not widen the standard five.** List returns all, Show returns one by id, Create takes the create schema, Update takes id and the update schema, Delete takes id. Anything else is a bespoke action.',
|
|
64
|
+
'6. **Finish what the tool leaves open.** Fill the `TODO` in each repository. Add the printed line to `<feature>.routes.ts` on Node or register the printed classes in `<feature>.module.ts` on Nest. Add fields to the Zod schemas.',
|
|
65
|
+
'',
|
|
66
|
+
'## Layout by stack',
|
|
67
|
+
'',
|
|
68
|
+
'- Next.js: `app/<feature>` or `src/app/<feature>`; route handlers under `app/api/<feature>`.',
|
|
69
|
+
'- React and Node: `src/features/<feature>` or `features/<feature>`.',
|
|
70
|
+
'- Nest: `src/<feature>` with a `<feature>.module.ts`.',
|
|
71
|
+
];
|
|
72
|
+
exports.AGENTS_SECTION = AGENTS_LINES.join('\n');
|
|
73
|
+
exports.SKILL_CONTENT = `${SKILL_LINES.join('\n')}\n`;
|