@expressots/cli 4.0.0-preview.2 → 4.0.0-preview.3
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/bin/cicd/cli.d.ts +1 -1
- package/bin/cicd/cli.js +3 -1
- package/bin/cicd/form.js +5 -4
- package/bin/cli.d.ts +1 -5
- package/bin/cli.js +56 -6
- package/bin/commands/project.commands.js +233 -26
- package/bin/containerize/cli.d.ts +1 -1
- package/bin/containerize/cli.js +1 -1
- package/bin/containerize/form.js +49 -51
- package/bin/containerize/generators/ci-generator.js +16 -12
- package/bin/containerize/generators/docker-compose-generator.js +3 -2
- package/bin/containerize/generators/dockerfile-generator.js +50 -28
- package/bin/containerize/generators/kubernetes-generator.js +5 -4
- package/bin/costs/cli.d.ts +1 -1
- package/bin/costs/cli.js +4 -2
- package/bin/dev/cli.d.ts +1 -1
- package/bin/dev/cli.js +3 -1
- package/bin/generate/cli.d.ts +1 -1
- package/bin/generate/templates/nonopinionated/config.tpl +12 -12
- package/bin/generate/templates/nonopinionated/event.tpl +10 -10
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -18
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -12
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -27
- package/bin/generate/templates/opinionated/config.tpl +47 -47
- package/bin/generate/templates/opinionated/event.tpl +15 -15
- package/bin/generate/templates/opinionated/guard.tpl +41 -41
- package/bin/generate/templates/opinionated/handler.tpl +23 -23
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -50
- package/bin/generate/utils/command-utils.d.ts +13 -2
- package/bin/generate/utils/command-utils.js +50 -17
- package/bin/generate/utils/opinionated-cmd.js +19 -12
- package/bin/help/cli.d.ts +1 -1
- package/bin/help/command-help-registry.d.ts +23 -0
- package/bin/help/command-help-registry.js +303 -0
- package/bin/help/command-help.d.ts +36 -0
- package/bin/help/command-help.js +56 -0
- package/bin/help/form.js +127 -30
- package/bin/help/main-help.d.ts +8 -0
- package/bin/help/main-help.js +126 -0
- package/bin/help/render.d.ts +32 -0
- package/bin/help/render.js +46 -0
- package/bin/info/cli.d.ts +1 -1
- package/bin/info/form.d.ts +1 -1
- package/bin/info/form.js +11 -11
- package/bin/migrate/cli.d.ts +1 -1
- package/bin/migrate/cli.js +3 -1
- package/bin/migrate/form.js +4 -3
- package/bin/new/cli.d.ts +5 -1
- package/bin/new/cli.js +62 -14
- package/bin/new/form.d.ts +3 -1
- package/bin/new/form.js +338 -23
- package/bin/profile/cli.d.ts +1 -1
- package/bin/profile/cli.js +3 -1
- package/bin/profile/form.js +5 -4
- package/bin/providers/create/form.js +53 -4
- package/bin/studio/cli.js +9 -3
- package/bin/templates/cli.js +7 -5
- package/bin/utils/add-module-to-container.d.ts +14 -3
- package/bin/utils/add-module-to-container.js +330 -111
- package/bin/utils/cli-ui.d.ts +20 -1
- package/bin/utils/cli-ui.js +41 -3
- package/bin/utils/update-tsconfig-paths.js +73 -33
- package/package.json +22 -13
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { provide, OnEvent, IEventHandler } from "@expressots/core";
|
|
2
|
-
import { {{{eventName}}} } from "{{{eventPath}}}";
|
|
3
|
-
|
|
4
|
-
@provide({{className}}Handler)
|
|
5
|
-
@OnEvent({{eventName}}, { priority: {{priority}} })
|
|
6
|
-
export class {{className}}Handler implements IEventHandler<{{eventName}}> {
|
|
7
|
-
async handle(event: {{eventName}}): Promise<void> {
|
|
8
|
-
// TODO: Implement handler logic
|
|
9
|
-
console.log(`Handling ${event.constructor.name}`);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
1
|
+
import { provide, OnEvent, IEventHandler } from "@expressots/core";
|
|
2
|
+
import { {{{eventName}}} } from "{{{eventPath}}}";
|
|
3
|
+
|
|
4
|
+
@provide({{className}}Handler)
|
|
5
|
+
@OnEvent({{eventName}}, { priority: {{priority}} })
|
|
6
|
+
export class {{className}}Handler implements IEventHandler<{{eventName}}> {
|
|
7
|
+
async handle(event: {{eventName}}): Promise<void> {
|
|
8
|
+
// TODO: Implement handler logic
|
|
9
|
+
console.log(`Handling ${event.constructor.name}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IInterceptor,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
CallHandler,
|
|
5
|
-
Interceptor,
|
|
6
|
-
provide,
|
|
7
|
-
} from "@expressots/core";
|
|
8
|
-
|
|
9
|
-
@Interceptor({ priority: {{priority}} })
|
|
10
|
-
@provide({{className}}Interceptor)
|
|
11
|
-
export class {{className}}Interceptor implements IInterceptor {
|
|
12
|
-
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
13
|
-
const request = context.getRequest();
|
|
14
|
-
|
|
15
|
-
// Pre-processing
|
|
16
|
-
const startTime = Date.now();
|
|
17
|
-
|
|
18
|
-
const result = await next.handle();
|
|
19
|
-
|
|
20
|
-
// Post-processing
|
|
21
|
-
const duration = Date.now() - startTime;
|
|
22
|
-
console.log(`[{{className}}] ${request.method} ${request.path} - ${duration}ms`);
|
|
23
|
-
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
1
|
+
import {
|
|
2
|
+
IInterceptor,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
CallHandler,
|
|
5
|
+
Interceptor,
|
|
6
|
+
provide,
|
|
7
|
+
} from "@expressots/core";
|
|
8
|
+
|
|
9
|
+
@Interceptor({ priority: {{priority}} })
|
|
10
|
+
@provide({{className}}Interceptor)
|
|
11
|
+
export class {{className}}Interceptor implements IInterceptor {
|
|
12
|
+
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
13
|
+
const request = context.getRequest();
|
|
14
|
+
|
|
15
|
+
// Pre-processing
|
|
16
|
+
const startTime = Date.now();
|
|
17
|
+
|
|
18
|
+
const result = await next.handle();
|
|
19
|
+
|
|
20
|
+
// Post-processing
|
|
21
|
+
const duration = Date.now() - startTime;
|
|
22
|
+
console.log(`[{{className}}] ${request.method} ${request.path} - ${duration}ms`);
|
|
23
|
+
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { defineConfig, Env, loadEnvSync } from "@expressots/core";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* {{className}} Configuration
|
|
5
|
-
*
|
|
6
|
-
* Type-safe configuration with full TypeScript inference.
|
|
7
|
-
* Features:
|
|
8
|
-
* - Multi-environment defaults
|
|
9
|
-
* - Secret management with auto-redaction
|
|
10
|
-
* - Validation with helpful errors
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Load environment files before config resolution
|
|
14
|
-
const envFiles = {
|
|
15
|
-
development: ".env.local",
|
|
16
|
-
production: ".env.prod",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
loadEnvSync({ files: envFiles });
|
|
20
|
-
|
|
21
|
-
export const {{moduleName}}Config = defineConfig({
|
|
22
|
-
// Add your configuration schema here
|
|
23
|
-
enabled: Env.boolean("{{envPrefix}}_ENABLED", {
|
|
24
|
-
default: true,
|
|
25
|
-
description: "Enable/disable {{className}} feature",
|
|
26
|
-
}),
|
|
27
|
-
// Example settings - customize as needed
|
|
28
|
-
setting1: Env.string("{{envPrefix}}_SETTING1", {
|
|
29
|
-
default: "default-value",
|
|
30
|
-
}),
|
|
31
|
-
setting2: Env.number("{{envPrefix}}_SETTING2", {
|
|
32
|
-
default: 100,
|
|
33
|
-
min: 0,
|
|
34
|
-
max: 1000,
|
|
35
|
-
}),
|
|
36
|
-
bootstrap: {
|
|
37
|
-
envFileConfig: {
|
|
38
|
-
autoCreateTemplate: true,
|
|
39
|
-
files: envFiles,
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// Export typed config values
|
|
45
|
-
export const config = {{moduleName}}Config.values;
|
|
46
|
-
export type {{className}}Config = typeof config;
|
|
47
|
-
|
|
1
|
+
import { defineConfig, Env, loadEnvSync } from "@expressots/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* {{className}} Configuration
|
|
5
|
+
*
|
|
6
|
+
* Type-safe configuration with full TypeScript inference.
|
|
7
|
+
* Features:
|
|
8
|
+
* - Multi-environment defaults
|
|
9
|
+
* - Secret management with auto-redaction
|
|
10
|
+
* - Validation with helpful errors
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Load environment files before config resolution
|
|
14
|
+
const envFiles = {
|
|
15
|
+
development: ".env.local",
|
|
16
|
+
production: ".env.prod",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
loadEnvSync({ files: envFiles });
|
|
20
|
+
|
|
21
|
+
export const {{moduleName}}Config = defineConfig({
|
|
22
|
+
// Add your configuration schema here
|
|
23
|
+
enabled: Env.boolean("{{envPrefix}}_ENABLED", {
|
|
24
|
+
default: true,
|
|
25
|
+
description: "Enable/disable {{className}} feature",
|
|
26
|
+
}),
|
|
27
|
+
// Example settings - customize as needed
|
|
28
|
+
setting1: Env.string("{{envPrefix}}_SETTING1", {
|
|
29
|
+
default: "default-value",
|
|
30
|
+
}),
|
|
31
|
+
setting2: Env.number("{{envPrefix}}_SETTING2", {
|
|
32
|
+
default: 100,
|
|
33
|
+
min: 0,
|
|
34
|
+
max: 1000,
|
|
35
|
+
}),
|
|
36
|
+
bootstrap: {
|
|
37
|
+
envFileConfig: {
|
|
38
|
+
autoCreateTemplate: true,
|
|
39
|
+
files: envFiles,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Export typed config values
|
|
45
|
+
export const config = {{moduleName}}Config.values;
|
|
46
|
+
export type {{className}}Config = typeof config;
|
|
47
|
+
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{className}} Event
|
|
3
|
-
*
|
|
4
|
-
* Type-safe event - no strings!
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* await this.eventEmitter.emit(new {{className}}Event(data));
|
|
8
|
-
*/
|
|
9
|
-
export class {{className}}Event {
|
|
10
|
-
constructor(
|
|
11
|
-
public readonly data: Record<string, unknown>,
|
|
12
|
-
public readonly timestamp: Date = new Date(),
|
|
13
|
-
) {}
|
|
14
|
-
}
|
|
15
|
-
|
|
1
|
+
/**
|
|
2
|
+
* {{className}} Event
|
|
3
|
+
*
|
|
4
|
+
* Type-safe event - no strings!
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* await this.eventEmitter.emit(new {{className}}Event(data));
|
|
8
|
+
*/
|
|
9
|
+
export class {{className}}Event {
|
|
10
|
+
constructor(
|
|
11
|
+
public readonly data: Record<string, unknown>,
|
|
12
|
+
public readonly timestamp: Date = new Date(),
|
|
13
|
+
) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import { provide } from "@expressots/core";
|
|
2
|
-
import { Request, Response, NextFunction } from "express";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* {{className}} Guard
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* @controller("/protected")
|
|
9
|
-
* @Use({{className}}Guard)
|
|
10
|
-
* export class ProtectedController { }
|
|
11
|
-
*/
|
|
12
|
-
@provide({{className}}Guard)
|
|
13
|
-
export class {{className}}Guard {
|
|
14
|
-
/**
|
|
15
|
-
* Check if the request is authorized
|
|
16
|
-
*/
|
|
17
|
-
canActivate(req: Request, res: Response, next: NextFunction): void {
|
|
18
|
-
// TODO: Implement authorization logic
|
|
19
|
-
const isAuthorized = this.checkAuthorization(req);
|
|
20
|
-
|
|
21
|
-
if (!isAuthorized) {
|
|
22
|
-
res.status(401).json({ message: "Unauthorized" });
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
next();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
private checkAuthorization(req: Request): boolean {
|
|
30
|
-
// Example: Check for authorization header
|
|
31
|
-
const authHeader = req.headers.authorization;
|
|
32
|
-
|
|
33
|
-
if (!authHeader) {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// TODO: Validate token/credentials
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
1
|
+
import { provide } from "@expressots/core";
|
|
2
|
+
import { Request, Response, NextFunction } from "express";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* {{className}} Guard
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* @controller("/protected")
|
|
9
|
+
* @Use({{className}}Guard)
|
|
10
|
+
* export class ProtectedController { }
|
|
11
|
+
*/
|
|
12
|
+
@provide({{className}}Guard)
|
|
13
|
+
export class {{className}}Guard {
|
|
14
|
+
/**
|
|
15
|
+
* Check if the request is authorized
|
|
16
|
+
*/
|
|
17
|
+
canActivate(req: Request, res: Response, next: NextFunction): void {
|
|
18
|
+
// TODO: Implement authorization logic
|
|
19
|
+
const isAuthorized = this.checkAuthorization(req);
|
|
20
|
+
|
|
21
|
+
if (!isAuthorized) {
|
|
22
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
next();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private checkAuthorization(req: Request): boolean {
|
|
30
|
+
// Example: Check for authorization header
|
|
31
|
+
const authHeader = req.headers.authorization;
|
|
32
|
+
|
|
33
|
+
if (!authHeader) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO: Validate token/credentials
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { provide, OnEvent, IEventHandler } from "@expressots/core";
|
|
2
|
-
import { {{{eventName}}} } from "{{{eventPath}}}";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Handler for {{eventName}}
|
|
6
|
-
*
|
|
7
|
-
* Features:
|
|
8
|
-
* - Auto-discovered
|
|
9
|
-
* - Priority: {{priority}}
|
|
10
|
-
* - Full type safety
|
|
11
|
-
*/
|
|
12
|
-
@provide({{className}}Handler)
|
|
13
|
-
@OnEvent({{eventName}}, { priority: {{priority}} })
|
|
14
|
-
export class {{className}}Handler implements IEventHandler<{{eventName}}> {
|
|
15
|
-
async handle(event: {{eventName}}): Promise<void> {
|
|
16
|
-
console.log(`Handling ${event.constructor.name}`, {
|
|
17
|
-
timestamp: event.timestamp,
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
// TODO: Implement handler logic
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
1
|
+
import { provide, OnEvent, IEventHandler } from "@expressots/core";
|
|
2
|
+
import { {{{eventName}}} } from "{{{eventPath}}}";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Handler for {{eventName}}
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Auto-discovered
|
|
9
|
+
* - Priority: {{priority}}
|
|
10
|
+
* - Full type safety
|
|
11
|
+
*/
|
|
12
|
+
@provide({{className}}Handler)
|
|
13
|
+
@OnEvent({{eventName}}, { priority: {{priority}} })
|
|
14
|
+
export class {{className}}Handler implements IEventHandler<{{eventName}}> {
|
|
15
|
+
async handle(event: {{eventName}}): Promise<void> {
|
|
16
|
+
console.log(`Handling ${event.constructor.name}`, {
|
|
17
|
+
timestamp: event.timestamp,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// TODO: Implement handler logic
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IInterceptor,
|
|
3
|
-
ExecutionContext,
|
|
4
|
-
CallHandler,
|
|
5
|
-
Interceptor,
|
|
6
|
-
provide,
|
|
7
|
-
} from "@expressots/core";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* {{className}} Interceptor
|
|
11
|
-
*
|
|
12
|
-
* Priority: {{priority}} (lower = earlier execution)
|
|
13
|
-
*
|
|
14
|
-
* Usage:
|
|
15
|
-
* @UseInterceptors({{className}}Interceptor)
|
|
16
|
-
* @controller("/route")
|
|
17
|
-
* export class MyController { }
|
|
18
|
-
*/
|
|
19
|
-
@Interceptor({ priority: {{priority}} })
|
|
20
|
-
@provide({{className}}Interceptor)
|
|
21
|
-
export class {{className}}Interceptor implements IInterceptor {
|
|
22
|
-
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
23
|
-
const request = context.getRequest();
|
|
24
|
-
|
|
25
|
-
// Pre-processing logic
|
|
26
|
-
console.log(`[{{className}}] Before handler`, {
|
|
27
|
-
method: request.method,
|
|
28
|
-
path: request.path,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const startTime = Date.now();
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
// Execute next interceptor or handler
|
|
35
|
-
const result = await next.handle();
|
|
36
|
-
|
|
37
|
-
// Post-processing logic
|
|
38
|
-
const duration = Date.now() - startTime;
|
|
39
|
-
console.log(`[{{className}}] After handler`, {
|
|
40
|
-
duration: `${duration}ms`,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return result;
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.error(`[{{className}}] Handler error`, error);
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
1
|
+
import {
|
|
2
|
+
IInterceptor,
|
|
3
|
+
ExecutionContext,
|
|
4
|
+
CallHandler,
|
|
5
|
+
Interceptor,
|
|
6
|
+
provide,
|
|
7
|
+
} from "@expressots/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* {{className}} Interceptor
|
|
11
|
+
*
|
|
12
|
+
* Priority: {{priority}} (lower = earlier execution)
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* @UseInterceptors({{className}}Interceptor)
|
|
16
|
+
* @controller("/route")
|
|
17
|
+
* export class MyController { }
|
|
18
|
+
*/
|
|
19
|
+
@Interceptor({ priority: {{priority}} })
|
|
20
|
+
@provide({{className}}Interceptor)
|
|
21
|
+
export class {{className}}Interceptor implements IInterceptor {
|
|
22
|
+
async intercept(context: ExecutionContext, next: CallHandler) {
|
|
23
|
+
const request = context.getRequest();
|
|
24
|
+
|
|
25
|
+
// Pre-processing logic
|
|
26
|
+
console.log(`[{{className}}] Before handler`, {
|
|
27
|
+
method: request.method,
|
|
28
|
+
path: request.path,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const startTime = Date.now();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Execute next interceptor or handler
|
|
35
|
+
const result = await next.handle();
|
|
36
|
+
|
|
37
|
+
// Post-processing logic
|
|
38
|
+
const duration = Date.now() - startTime;
|
|
39
|
+
console.log(`[{{className}}] After handler`, {
|
|
40
|
+
duration: `${duration}ms`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return result;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error(`[{{className}}] Handler error`, error);
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -69,9 +69,10 @@ export declare function getFileNameWithoutExtension(filePath: string): string;
|
|
|
69
69
|
* @param schematic
|
|
70
70
|
* @returns the split target
|
|
71
71
|
*/
|
|
72
|
-
export declare const splitTarget: ({ target, schematic, }: {
|
|
72
|
+
export declare const splitTarget: ({ target, schematic, opinionated, }: {
|
|
73
73
|
target: string;
|
|
74
74
|
schematic: string;
|
|
75
|
+
opinionated?: boolean;
|
|
75
76
|
}) => Promise<{
|
|
76
77
|
path: string;
|
|
77
78
|
file: string;
|
|
@@ -120,7 +121,17 @@ export declare const getNameWithScaffoldPattern: (name: string) => Promise<strin
|
|
|
120
121
|
*/
|
|
121
122
|
export declare function extractFirstWord(file: string): Promise<string>;
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* Determine the path style for a generate target.
|
|
125
|
+
*
|
|
126
|
+
* - `Nested`: contains an explicit separator (`billing/invoice`) → grouped
|
|
127
|
+
* under the parent folder.
|
|
128
|
+
* - `Sugar`: a single segment that normalizes to more than one word
|
|
129
|
+
* (`userCreate`, `user-create`, `user_create`, `UserCreate`) → grouped under
|
|
130
|
+
* its first word as a shared module (e.g. `UserModule`). camelCase and
|
|
131
|
+
* kebab-case forms of the same name therefore produce identical output.
|
|
132
|
+
* - `Single`: a true single-word target (`user`) → self-contained module in its
|
|
133
|
+
* own folder.
|
|
134
|
+
*
|
|
124
135
|
* @param path
|
|
125
136
|
* @returns the path style
|
|
126
137
|
*/
|
|
@@ -36,6 +36,7 @@ const verify_file_exists_1 = require("../../utils/verify-file-exists");
|
|
|
36
36
|
const compiler_1 = __importDefault(require("../../utils/compiler"));
|
|
37
37
|
const update_tsconfig_paths_1 = require("../../utils/update-tsconfig-paths");
|
|
38
38
|
const input_validation_1 = require("../../utils/input-validation");
|
|
39
|
+
const shared_1 = require("@expressots/shared");
|
|
39
40
|
/**
|
|
40
41
|
* Reject generate targets that would resolve outside the project's
|
|
41
42
|
* source root. We only inspect the user-supplied `rawTarget` for
|
|
@@ -79,6 +80,7 @@ async function validateAndPrepareFile(fp) {
|
|
|
79
80
|
const { path, file, className, moduleName, modulePath } = await (0, exports.splitTarget)({
|
|
80
81
|
target: fp.target,
|
|
81
82
|
schematic: fp.schematic,
|
|
83
|
+
opinionated: true,
|
|
82
84
|
});
|
|
83
85
|
ensureWithinSourceRoot(folderToScaffold, `${path}/${file}`, fp.target);
|
|
84
86
|
const outputPath = `${folderToScaffold}/${path}/${file}`;
|
|
@@ -142,7 +144,7 @@ exports.getFileNameWithoutExtension = getFileNameWithoutExtension;
|
|
|
142
144
|
* @param schematic
|
|
143
145
|
* @returns the split target
|
|
144
146
|
*/
|
|
145
|
-
const splitTarget = async ({ target, schematic, }) => {
|
|
147
|
+
const splitTarget = async ({ target, schematic, opinionated = false, }) => {
|
|
146
148
|
const pathContent = target
|
|
147
149
|
.split("/")
|
|
148
150
|
.filter((item) => item !== "");
|
|
@@ -213,6 +215,30 @@ const splitTarget = async ({ target, schematic, }) => {
|
|
|
213
215
|
const firstWord = name
|
|
214
216
|
.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex)[0]
|
|
215
217
|
.toLowerCase();
|
|
218
|
+
// Opinionated "syntactic sugar": decompose a compound name into a
|
|
219
|
+
// nested feature/use-case layout so every use-case of a feature is
|
|
220
|
+
// grouped under one module at the feature root. For example
|
|
221
|
+
// `userLogin` -> `user/login/login.{controller,usecase,dto}.ts` with
|
|
222
|
+
// the shared module at `user/user.module.ts`. A later `userLogout`
|
|
223
|
+
// adds `user/logout/...` and joins the same `UserModule`.
|
|
224
|
+
//
|
|
225
|
+
// Only applies to grouped schematics in opinionated mode; standalone
|
|
226
|
+
// schematics and non-opinionated mode keep the flat kebab folder so
|
|
227
|
+
// the developer retains full control over structure.
|
|
228
|
+
if (opinionated && shouldCreateFolder) {
|
|
229
|
+
const words = folderName.split("-").filter(Boolean);
|
|
230
|
+
if (words.length > 1) {
|
|
231
|
+
const feature = words[0];
|
|
232
|
+
const useCase = words.slice(1).join("-");
|
|
233
|
+
return {
|
|
234
|
+
path: `${feature}/${useCase}`,
|
|
235
|
+
file: `${await (0, exports.getNameWithScaffoldPattern)(useCase)}.${schematic}.ts`,
|
|
236
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(useCase),
|
|
237
|
+
moduleName: feature,
|
|
238
|
+
modulePath: feature,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
216
242
|
// For standalone schematics (entity, provider, middleware, etc.),
|
|
217
243
|
// only create folder if explicit path is provided
|
|
218
244
|
const computedPath = shouldCreateFolder
|
|
@@ -316,13 +342,13 @@ exports.schematicFolder = schematicFolder;
|
|
|
316
342
|
const getNameWithScaffoldPattern = async (name) => {
|
|
317
343
|
const configObject = await compiler_1.default.loadConfig();
|
|
318
344
|
switch (configObject.scaffoldPattern) {
|
|
319
|
-
case
|
|
345
|
+
case shared_1.Pattern.LOWER_CASE:
|
|
320
346
|
return (0, string_utils_1.anyCaseToLowerCase)(name);
|
|
321
|
-
case
|
|
347
|
+
case shared_1.Pattern.KEBAB_CASE:
|
|
322
348
|
return (0, string_utils_1.anyCaseToKebabCase)(name);
|
|
323
|
-
case
|
|
349
|
+
case shared_1.Pattern.PASCAL_CASE:
|
|
324
350
|
return (0, string_utils_1.anyCaseToPascalCase)(name);
|
|
325
|
-
case
|
|
351
|
+
case shared_1.Pattern.CAMEL_CASE:
|
|
326
352
|
return (0, string_utils_1.anyCaseToCamelCase)(name);
|
|
327
353
|
}
|
|
328
354
|
};
|
|
@@ -346,33 +372,40 @@ async function extractFirstWord(file) {
|
|
|
346
372
|
const firstWord = f.split(regex)[0];
|
|
347
373
|
const config = await compiler_1.default.loadConfig();
|
|
348
374
|
switch (config.scaffoldPattern) {
|
|
349
|
-
case
|
|
375
|
+
case shared_1.Pattern.LOWER_CASE:
|
|
350
376
|
return (0, string_utils_1.anyCaseToLowerCase)(firstWord);
|
|
351
|
-
case
|
|
377
|
+
case shared_1.Pattern.KEBAB_CASE:
|
|
352
378
|
return (0, string_utils_1.anyCaseToKebabCase)(firstWord);
|
|
353
|
-
case
|
|
379
|
+
case shared_1.Pattern.PASCAL_CASE:
|
|
354
380
|
return (0, string_utils_1.anyCaseToPascalCase)(firstWord);
|
|
355
|
-
case
|
|
381
|
+
case shared_1.Pattern.CAMEL_CASE:
|
|
356
382
|
return (0, string_utils_1.anyCaseToCamelCase)(firstWord);
|
|
357
383
|
}
|
|
358
384
|
}
|
|
359
385
|
exports.extractFirstWord = extractFirstWord;
|
|
360
386
|
/**
|
|
361
|
-
*
|
|
387
|
+
* Determine the path style for a generate target.
|
|
388
|
+
*
|
|
389
|
+
* - `Nested`: contains an explicit separator (`billing/invoice`) → grouped
|
|
390
|
+
* under the parent folder.
|
|
391
|
+
* - `Sugar`: a single segment that normalizes to more than one word
|
|
392
|
+
* (`userCreate`, `user-create`, `user_create`, `UserCreate`) → grouped under
|
|
393
|
+
* its first word as a shared module (e.g. `UserModule`). camelCase and
|
|
394
|
+
* kebab-case forms of the same name therefore produce identical output.
|
|
395
|
+
* - `Single`: a true single-word target (`user`) → self-contained module in its
|
|
396
|
+
* own folder.
|
|
397
|
+
*
|
|
362
398
|
* @param path
|
|
363
399
|
* @returns the path style
|
|
364
400
|
*/
|
|
365
401
|
const checkPathStyle = (path) => {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
if (singleOrNestedPathRegex.test(path)) {
|
|
402
|
+
const nestedPathRegex = /\/|\\/;
|
|
403
|
+
if (nestedPathRegex.test(path)) {
|
|
369
404
|
return "nested" /* PathStyle.Nested */;
|
|
370
405
|
}
|
|
371
|
-
|
|
406
|
+
if ((0, string_utils_1.anyCaseToKebabCase)(path).includes("-")) {
|
|
372
407
|
return "sugar" /* PathStyle.Sugar */;
|
|
373
408
|
}
|
|
374
|
-
|
|
375
|
-
return "single" /* PathStyle.Single */;
|
|
376
|
-
}
|
|
409
|
+
return "single" /* PathStyle.Single */;
|
|
377
410
|
};
|
|
378
411
|
exports.checkPathStyle = checkPathStyle;
|