@expressots/cli 3.0.0-beta.4 → 4.0.0-preview.2
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 +41 -95
- package/bin/cicd/cli.d.ts +6 -0
- package/bin/cicd/cli.js +126 -0
- package/bin/cicd/form.d.ts +29 -0
- package/bin/cicd/form.js +345 -0
- package/bin/cicd/generators/azure-devops.d.ts +2 -0
- package/bin/cicd/generators/azure-devops.js +370 -0
- package/bin/cicd/generators/bitbucket.d.ts +2 -0
- package/bin/cicd/generators/bitbucket.js +217 -0
- package/bin/cicd/generators/circleci.d.ts +2 -0
- package/bin/cicd/generators/circleci.js +274 -0
- package/bin/cicd/generators/github-actions.d.ts +14 -0
- package/bin/cicd/generators/github-actions.js +426 -0
- package/bin/cicd/generators/gitlab-ci.d.ts +2 -0
- package/bin/cicd/generators/gitlab-ci.js +237 -0
- package/bin/cicd/generators/index.d.ts +6 -0
- package/bin/cicd/generators/index.js +15 -0
- package/bin/cicd/generators/jenkins.d.ts +2 -0
- package/bin/cicd/generators/jenkins.js +248 -0
- package/bin/cicd/generators/template-loader.d.ts +17 -0
- package/bin/cicd/generators/template-loader.js +128 -0
- package/bin/cicd/index.d.ts +1 -0
- package/bin/cicd/index.js +5 -0
- package/bin/cli.d.ts +1 -1
- package/bin/cli.js +18 -3
- package/bin/commands/project.commands.d.ts +19 -6
- package/bin/commands/project.commands.js +390 -61
- package/bin/config/index.d.ts +5 -0
- package/bin/config/index.js +10 -0
- package/bin/config/manager.d.ts +98 -0
- package/bin/config/manager.js +222 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.d.ts +46 -0
- package/bin/containerize/analyzers/bootstrap-analyzer.js +187 -0
- package/bin/containerize/analyzers/project-analyzer.d.ts +20 -0
- package/bin/containerize/analyzers/project-analyzer.js +150 -0
- package/bin/containerize/cli.d.ts +4 -0
- package/bin/containerize/cli.js +113 -0
- package/bin/containerize/form.d.ts +15 -0
- package/bin/containerize/form.js +154 -0
- package/bin/containerize/generators/ci-generator.d.ts +31 -0
- package/bin/containerize/generators/ci-generator.js +936 -0
- package/bin/containerize/generators/docker-compose-generator.d.ts +8 -0
- package/bin/containerize/generators/docker-compose-generator.js +186 -0
- package/bin/containerize/generators/dockerfile-generator.d.ts +8 -0
- package/bin/containerize/generators/dockerfile-generator.js +635 -0
- package/bin/containerize/generators/kubernetes-generator.d.ts +8 -0
- package/bin/containerize/generators/kubernetes-generator.js +133 -0
- package/bin/containerize/generators/template-loader.d.ts +36 -0
- package/bin/containerize/generators/template-loader.js +129 -0
- package/bin/containerize/index.d.ts +4 -0
- package/bin/containerize/index.js +13 -0
- package/bin/containerize/presets/preset-registry.d.ts +20 -0
- package/bin/containerize/presets/preset-registry.js +102 -0
- package/bin/costs/cli.d.ts +5 -0
- package/bin/costs/cli.js +183 -0
- package/bin/costs/form.d.ts +44 -0
- package/bin/costs/form.js +412 -0
- package/bin/costs/index.d.ts +4 -0
- package/bin/costs/index.js +25 -0
- package/bin/costs/pricing-manager.d.ts +84 -0
- package/bin/costs/pricing-manager.js +342 -0
- package/bin/costs/providers/index.d.ts +32 -0
- package/bin/costs/providers/index.js +153 -0
- package/bin/costs/sources/api-source.d.ts +10 -0
- package/bin/costs/sources/api-source.js +32 -0
- package/bin/costs/sources/index.d.ts +6 -0
- package/bin/costs/sources/index.js +15 -0
- package/bin/costs/sources/local-json-source.d.ts +23 -0
- package/bin/costs/sources/local-json-source.js +59 -0
- package/bin/costs/sources/remote-json-source.d.ts +11 -0
- package/bin/costs/sources/remote-json-source.js +53 -0
- package/bin/costs/types.d.ts +53 -0
- package/bin/costs/types.js +5 -0
- package/bin/dev/cli.d.ts +4 -0
- package/bin/dev/cli.js +134 -0
- package/bin/dev/form.d.ts +36 -0
- package/bin/dev/form.js +254 -0
- package/bin/dev/index.d.ts +1 -0
- package/bin/dev/index.js +5 -0
- package/bin/generate/cli.js +29 -2
- package/bin/generate/form.d.ts +5 -1
- package/bin/generate/form.js +3 -3
- package/bin/generate/templates/nonopinionated/config.tpl +12 -0
- package/bin/generate/templates/nonopinionated/event.tpl +10 -0
- package/bin/generate/templates/nonopinionated/guard.tpl +18 -0
- package/bin/generate/templates/nonopinionated/handler.tpl +12 -0
- package/bin/generate/templates/nonopinionated/interceptor.tpl +27 -0
- package/bin/generate/templates/opinionated/config.tpl +47 -0
- package/bin/generate/templates/opinionated/entity.tpl +1 -8
- package/bin/generate/templates/opinionated/event.tpl +15 -0
- package/bin/generate/templates/opinionated/guard.tpl +41 -0
- package/bin/generate/templates/opinionated/handler.tpl +23 -0
- package/bin/generate/templates/opinionated/interceptor.tpl +50 -0
- package/bin/generate/utils/command-utils.d.ts +7 -3
- package/bin/generate/utils/command-utils.js +95 -31
- package/bin/generate/utils/nonopininated-cmd.d.ts +10 -1
- package/bin/generate/utils/nonopininated-cmd.js +100 -1
- package/bin/generate/utils/opinionated-cmd.d.ts +10 -1
- package/bin/generate/utils/opinionated-cmd.js +112 -7
- package/bin/generate/utils/string-utils.d.ts +6 -0
- package/bin/generate/utils/string-utils.js +13 -1
- package/bin/help/form.js +11 -3
- package/bin/migrate/analyzers/platform-detector.d.ts +14 -0
- package/bin/migrate/analyzers/platform-detector.js +116 -0
- package/bin/migrate/cli.d.ts +6 -0
- package/bin/migrate/cli.js +96 -0
- package/bin/migrate/form.d.ts +25 -0
- package/bin/migrate/form.js +347 -0
- package/bin/migrate/generators/compose-to-k8s.d.ts +2 -0
- package/bin/migrate/generators/compose-to-k8s.js +324 -0
- package/bin/migrate/generators/compose-to-railway.d.ts +2 -0
- package/bin/migrate/generators/compose-to-railway.js +138 -0
- package/bin/migrate/generators/compose-to-render.d.ts +2 -0
- package/bin/migrate/generators/compose-to-render.js +148 -0
- package/bin/migrate/generators/generic-migration.d.ts +9 -0
- package/bin/migrate/generators/generic-migration.js +221 -0
- package/bin/migrate/generators/heroku-to-fly.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-fly.js +291 -0
- package/bin/migrate/generators/heroku-to-railway.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-railway.js +283 -0
- package/bin/migrate/generators/heroku-to-render.d.ts +2 -0
- package/bin/migrate/generators/heroku-to-render.js +148 -0
- package/bin/migrate/generators/index.d.ts +7 -0
- package/bin/migrate/generators/index.js +17 -0
- package/bin/migrate/generators/template-loader.d.ts +21 -0
- package/bin/migrate/generators/template-loader.js +59 -0
- package/bin/migrate/index.d.ts +1 -0
- package/bin/migrate/index.js +5 -0
- package/bin/new/cli.js +21 -6
- package/bin/new/form.d.ts +25 -4
- package/bin/new/form.js +285 -70
- package/bin/profile/analyzers/dockerfile-analyzer.d.ts +27 -0
- package/bin/profile/analyzers/dockerfile-analyzer.js +122 -0
- package/bin/profile/analyzers/image-analyzer.d.ts +19 -0
- package/bin/profile/analyzers/image-analyzer.js +85 -0
- package/bin/profile/cli.d.ts +4 -0
- package/bin/profile/cli.js +92 -0
- package/bin/profile/form.d.ts +56 -0
- package/bin/profile/form.js +400 -0
- package/bin/profile/index.d.ts +1 -0
- package/bin/profile/index.js +5 -0
- package/bin/profile/optimizers/index.d.ts +19 -0
- package/bin/profile/optimizers/index.js +137 -0
- package/bin/providers/add/form.d.ts +1 -1
- package/bin/providers/add/form.js +27 -6
- package/bin/providers/create/form.js +2 -1
- package/bin/scripts/form.js +27 -5
- package/bin/studio/cli.d.ts +15 -0
- package/bin/studio/cli.js +166 -0
- package/bin/studio/index.d.ts +5 -0
- package/bin/studio/index.js +9 -0
- package/bin/templates/cache.d.ts +54 -0
- package/bin/templates/cache.js +180 -0
- package/bin/templates/cli.d.ts +8 -0
- package/bin/templates/cli.js +292 -0
- package/bin/templates/fetcher.d.ts +49 -0
- package/bin/templates/fetcher.js +208 -0
- package/bin/templates/index.d.ts +11 -0
- package/bin/templates/index.js +37 -0
- package/bin/templates/manager.d.ts +116 -0
- package/bin/templates/manager.js +323 -0
- package/bin/templates/renderer.d.ts +49 -0
- package/bin/templates/renderer.js +204 -0
- package/bin/templates/types.d.ts +51 -0
- package/bin/templates/types.js +5 -0
- package/bin/utils/add-module-to-container.d.ts +2 -2
- package/bin/utils/add-module-to-container.js +15 -5
- package/bin/utils/cli-ui.d.ts +30 -3
- package/bin/utils/cli-ui.js +95 -13
- package/bin/utils/index.d.ts +4 -0
- package/bin/utils/index.js +4 -0
- package/bin/utils/input-validation.d.ts +50 -0
- package/bin/utils/input-validation.js +143 -0
- package/bin/utils/package-manager-commands.d.ts +24 -0
- package/bin/utils/package-manager-commands.js +50 -0
- package/bin/utils/safe-spawn.d.ts +35 -0
- package/bin/utils/safe-spawn.js +51 -0
- package/bin/utils/update-tsconfig-paths.d.ts +35 -0
- package/bin/utils/update-tsconfig-paths.js +286 -0
- package/package.json +154 -154
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { provide } from "@expressots/core";
|
|
2
|
+
import { Request, Response, NextFunction } from "express";
|
|
3
|
+
|
|
4
|
+
@provide({{className}}Guard)
|
|
5
|
+
export class {{className}}Guard {
|
|
6
|
+
canActivate(req: Request, res: Response, next: NextFunction): void {
|
|
7
|
+
const authHeader = req.headers.authorization;
|
|
8
|
+
|
|
9
|
+
if (!authHeader) {
|
|
10
|
+
res.status(401).json({ message: "Unauthorized" });
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// TODO: Validate token
|
|
15
|
+
next();
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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,11 +1,4 @@
|
|
|
1
1
|
import { provide } from "@expressots/core";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
2
|
|
|
4
3
|
@provide({{className}}Entity)
|
|
5
|
-
export class {{className}}Entity {
|
|
6
|
-
id: string;
|
|
7
|
-
|
|
8
|
-
constructor() {
|
|
9
|
-
this.id = randomUUID();
|
|
10
|
-
}
|
|
11
|
-
}
|
|
4
|
+
export class {{className}}Entity {}
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -0,0 +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
|
+
|
|
@@ -99,10 +99,14 @@ export declare const writeTemplate: ({ outputPath, template: { path, data }, }:
|
|
|
99
99
|
};
|
|
100
100
|
}) => void;
|
|
101
101
|
/**
|
|
102
|
-
* Returns the folder where the schematic should be placed
|
|
103
|
-
*
|
|
102
|
+
* Returns the folder where the schematic should be placed.
|
|
103
|
+
* Uses scaffoldSchematics from config if defined, otherwise falls back to defaults.
|
|
104
|
+
*
|
|
105
|
+
* @param schematic - The schematic type (usecase, controller, etc.)
|
|
106
|
+
* @param scaffoldSchematics - Custom folder mappings from expressots.config.ts
|
|
107
|
+
* @returns The folder path for the schematic
|
|
104
108
|
*/
|
|
105
|
-
export declare const schematicFolder: (schematic: string) => string | undefined;
|
|
109
|
+
export declare const schematicFolder: (schematic: string, scaffoldSchematics?: ExpressoConfig["scaffoldSchematics"]) => string | undefined;
|
|
106
110
|
/**
|
|
107
111
|
* Get the name with the scaffold pattern
|
|
108
112
|
* @param name
|
|
@@ -34,6 +34,34 @@ const string_utils_1 = require("./string-utils");
|
|
|
34
34
|
const cli_ui_1 = require("../../utils/cli-ui");
|
|
35
35
|
const verify_file_exists_1 = require("../../utils/verify-file-exists");
|
|
36
36
|
const compiler_1 = __importDefault(require("../../utils/compiler"));
|
|
37
|
+
const update_tsconfig_paths_1 = require("../../utils/update-tsconfig-paths");
|
|
38
|
+
const input_validation_1 = require("../../utils/input-validation");
|
|
39
|
+
/**
|
|
40
|
+
* Reject generate targets that would resolve outside the project's
|
|
41
|
+
* source root. We only inspect the user-supplied `rawTarget` for
|
|
42
|
+
* absolute-path escape (`/etc/...`, `C:\Windows\...`); the
|
|
43
|
+
* `relativePath` is built internally and may legitimately start with
|
|
44
|
+
* a leading `/` when the schematic doesn't introduce its own folder.
|
|
45
|
+
*
|
|
46
|
+
* Aborts via `process.exit(1)` after `printError` so the caller
|
|
47
|
+
* surfaces a friendly message and writes nothing.
|
|
48
|
+
*/
|
|
49
|
+
function ensureWithinSourceRoot(folderToScaffold, relativePath, rawTarget) {
|
|
50
|
+
if (nodePath.isAbsolute(rawTarget)) {
|
|
51
|
+
(0, cli_ui_1.printError)("Absolute paths are not allowed for generate targets", rawTarget);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
// Strip leading slashes from the synthesized relative path before
|
|
55
|
+
// resolution; the leading slash is a benign artifact of empty
|
|
56
|
+
// `path` segments, not an escape attempt.
|
|
57
|
+
const stripped = relativePath.replace(/^[\\/]+/, "");
|
|
58
|
+
const baseAbs = nodePath.resolve(process.cwd(), folderToScaffold);
|
|
59
|
+
const safe = (0, input_validation_1.safeResolveWithin)(baseAbs, stripped);
|
|
60
|
+
if (safe === null) {
|
|
61
|
+
(0, cli_ui_1.printError)(`Path traversal detected. Targets must stay inside ${folderToScaffold}`, rawTarget);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
37
65
|
/**
|
|
38
66
|
* Create a template based on the schematic
|
|
39
67
|
* @param fp
|
|
@@ -46,15 +74,20 @@ async function validateAndPrepareFile(fp) {
|
|
|
46
74
|
process.exit(1);
|
|
47
75
|
}
|
|
48
76
|
if (opinionated) {
|
|
49
|
-
const folderSchematic = (0, exports.schematicFolder)(fp.schematic);
|
|
77
|
+
const folderSchematic = (0, exports.schematicFolder)(fp.schematic, scaffoldSchematics);
|
|
50
78
|
const folderToScaffold = `${sourceRoot}/${folderSchematic}`;
|
|
51
79
|
const { path, file, className, moduleName, modulePath } = await (0, exports.splitTarget)({
|
|
52
80
|
target: fp.target,
|
|
53
81
|
schematic: fp.schematic,
|
|
54
82
|
});
|
|
83
|
+
ensureWithinSourceRoot(folderToScaffold, `${path}/${file}`, fp.target);
|
|
55
84
|
const outputPath = `${folderToScaffold}/${path}/${file}`;
|
|
56
85
|
await (0, verify_file_exists_1.verifyIfFileExists)(outputPath, fp.schematic);
|
|
57
86
|
(0, node_fs_1.mkdirSync)(`${folderToScaffold}/${path}`, { recursive: true });
|
|
87
|
+
// Update tsconfig paths dynamically (handles both default and custom folder names)
|
|
88
|
+
if (folderSchematic) {
|
|
89
|
+
await (0, update_tsconfig_paths_1.updateTsconfigPaths)(folderSchematic, sourceRoot);
|
|
90
|
+
}
|
|
58
91
|
return {
|
|
59
92
|
path,
|
|
60
93
|
file,
|
|
@@ -77,6 +110,7 @@ async function validateAndPrepareFile(fp) {
|
|
|
77
110
|
const validateFileSchema = fileBaseSchema !== undefined
|
|
78
111
|
? file.replace(fp.schematic, fileBaseSchema)
|
|
79
112
|
: file;
|
|
113
|
+
ensureWithinSourceRoot(folderToScaffold, `${path}/${validateFileSchema}`, fp.target);
|
|
80
114
|
const outputPath = `${folderToScaffold}/${path}/${validateFileSchema}`;
|
|
81
115
|
await (0, verify_file_exists_1.verifyIfFileExists)(outputPath, fp.schematic);
|
|
82
116
|
(0, node_fs_1.mkdirSync)(`${folderToScaffold}/${path}`, { recursive: true });
|
|
@@ -158,28 +192,45 @@ const splitTarget = async ({ target, schematic, }) => {
|
|
|
158
192
|
schematic = "controller";
|
|
159
193
|
// 1. Extract the name (first part of the target)
|
|
160
194
|
const [name, ...remainingPath] = target.split("/");
|
|
161
|
-
// 2. Check if the name is camelCase or kebab-case
|
|
195
|
+
// 2. Check if the name is camelCase or kebab-case (compound word)
|
|
162
196
|
const camelCaseRegex = /[A-Z]/;
|
|
163
197
|
const kebabCaseRegex = /[_\-\s]+/;
|
|
164
198
|
const isCamelCase = camelCaseRegex.test(name);
|
|
165
199
|
const isKebabCase = kebabCaseRegex.test(name);
|
|
200
|
+
// Schematics that should create their own subfolder (grouped resources)
|
|
201
|
+
const groupedSchematics = [
|
|
202
|
+
"usecase",
|
|
203
|
+
"controller",
|
|
204
|
+
"service",
|
|
205
|
+
"dto",
|
|
206
|
+
"module",
|
|
207
|
+
];
|
|
208
|
+
const shouldCreateFolder = groupedSchematics.includes(schematic);
|
|
166
209
|
if (isCamelCase || isKebabCase) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
: []
|
|
210
|
+
// Convert compound name to kebab-case for folder path (e.g., confirmLogin -> confirm-login)
|
|
211
|
+
const folderName = (0, string_utils_1.anyCaseToKebabCase)(name);
|
|
212
|
+
// Extract first word for module name
|
|
213
|
+
const firstWord = name
|
|
214
|
+
.split(isCamelCase ? /(?=[A-Z])/ : kebabCaseRegex)[0]
|
|
215
|
+
.toLowerCase();
|
|
216
|
+
// For standalone schematics (entity, provider, middleware, etc.),
|
|
217
|
+
// only create folder if explicit path is provided
|
|
218
|
+
const computedPath = shouldCreateFolder
|
|
219
|
+
? `${folderName}${pathEdgeCase(remainingPath)}`
|
|
220
|
+
: remainingPath.length > 0
|
|
221
|
+
? `${folderName}${pathEdgeCase(remainingPath)}`
|
|
222
|
+
: "";
|
|
172
223
|
return {
|
|
173
|
-
path:
|
|
224
|
+
path: computedPath,
|
|
174
225
|
file: `${await (0, exports.getNameWithScaffoldPattern)(name)}.${schematic}.ts`,
|
|
175
226
|
className: (0, string_utils_1.anyCaseToPascalCase)(name),
|
|
176
|
-
moduleName:
|
|
227
|
+
moduleName: firstWord,
|
|
177
228
|
modulePath: pathContent[0].split("-")[1],
|
|
178
229
|
};
|
|
179
230
|
}
|
|
180
231
|
// 3. Return the base case
|
|
181
232
|
return {
|
|
182
|
-
path: "",
|
|
233
|
+
path: shouldCreateFolder ? name : "",
|
|
183
234
|
file: `${await (0, exports.getNameWithScaffoldPattern)(name)}.${schematic}.ts`,
|
|
184
235
|
className: (0, string_utils_1.anyCaseToPascalCase)(name),
|
|
185
236
|
moduleName: name,
|
|
@@ -219,29 +270,42 @@ const writeTemplate = ({ outputPath, template: { path, data }, }) => {
|
|
|
219
270
|
};
|
|
220
271
|
exports.writeTemplate = writeTemplate;
|
|
221
272
|
/**
|
|
222
|
-
*
|
|
223
|
-
|
|
273
|
+
* Default folder mappings for opinionated scaffolding
|
|
274
|
+
*/
|
|
275
|
+
const DEFAULT_SCHEMATIC_FOLDERS = {
|
|
276
|
+
usecase: "useCases",
|
|
277
|
+
controller: "useCases",
|
|
278
|
+
dto: "useCases",
|
|
279
|
+
service: "useCases",
|
|
280
|
+
provider: "providers",
|
|
281
|
+
entity: "entities",
|
|
282
|
+
middleware: "middleware",
|
|
283
|
+
module: "useCases",
|
|
284
|
+
// NEW v4.0 schematics
|
|
285
|
+
interceptor: "interceptors",
|
|
286
|
+
event: "events",
|
|
287
|
+
handler: "events",
|
|
288
|
+
guard: "guards",
|
|
289
|
+
config: "config",
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Returns the folder where the schematic should be placed.
|
|
293
|
+
* Uses scaffoldSchematics from config if defined, otherwise falls back to defaults.
|
|
294
|
+
*
|
|
295
|
+
* @param schematic - The schematic type (usecase, controller, etc.)
|
|
296
|
+
* @param scaffoldSchematics - Custom folder mappings from expressots.config.ts
|
|
297
|
+
* @returns The folder path for the schematic
|
|
224
298
|
*/
|
|
225
|
-
const schematicFolder = (schematic) => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
return "useCases";
|
|
233
|
-
case "service":
|
|
234
|
-
return "useCases";
|
|
235
|
-
case "provider":
|
|
236
|
-
return "providers";
|
|
237
|
-
case "entity":
|
|
238
|
-
return "entities";
|
|
239
|
-
case "middleware":
|
|
240
|
-
return "providers/middlewares";
|
|
241
|
-
case "module":
|
|
242
|
-
return "useCases";
|
|
299
|
+
const schematicFolder = (schematic, scaffoldSchematics) => {
|
|
300
|
+
// Check if custom mapping is defined in config
|
|
301
|
+
if (scaffoldSchematics) {
|
|
302
|
+
const customFolder = scaffoldSchematics[schematic];
|
|
303
|
+
if (customFolder) {
|
|
304
|
+
return customFolder;
|
|
305
|
+
}
|
|
243
306
|
}
|
|
244
|
-
|
|
307
|
+
// Fall back to default mappings
|
|
308
|
+
return DEFAULT_SCHEMATIC_FOLDERS[schematic];
|
|
245
309
|
};
|
|
246
310
|
exports.schematicFolder = schematicFolder;
|
|
247
311
|
/**
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { ExpressoConfig } from "@expressots/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Additional options for v4.0 schematics
|
|
4
|
+
*/
|
|
5
|
+
type V4Options = {
|
|
6
|
+
event?: string;
|
|
7
|
+
priority?: number;
|
|
8
|
+
};
|
|
2
9
|
/**
|
|
3
10
|
* Process the non-opinionated command
|
|
4
11
|
* @param schematic - The schematic
|
|
5
12
|
* @param target - The target
|
|
6
13
|
* @param method - The method
|
|
7
14
|
* @param expressoConfig - The expresso config
|
|
15
|
+
* @param v4Options - Additional options for v4.0 schematics
|
|
8
16
|
*/
|
|
9
|
-
export declare function nonOpinionatedProcess(schematic: string, target: string, method: string, expressoConfig: ExpressoConfig): Promise<string>;
|
|
17
|
+
export declare function nonOpinionatedProcess(schematic: string, target: string, method: string, expressoConfig: ExpressoConfig, v4Options?: V4Options): Promise<string>;
|
|
18
|
+
export {};
|
|
@@ -10,8 +10,9 @@ const command_utils_1 = require("./command-utils");
|
|
|
10
10
|
* @param target - The target
|
|
11
11
|
* @param method - The method
|
|
12
12
|
* @param expressoConfig - The expresso config
|
|
13
|
+
* @param v4Options - Additional options for v4.0 schematics
|
|
13
14
|
*/
|
|
14
|
-
async function nonOpinionatedProcess(schematic, target, method, expressoConfig) {
|
|
15
|
+
async function nonOpinionatedProcess(schematic, target, method, expressoConfig, v4Options = {}) {
|
|
15
16
|
let f = await (0, command_utils_1.validateAndPrepareFile)({
|
|
16
17
|
schematic,
|
|
17
18
|
target,
|
|
@@ -81,6 +82,27 @@ async function nonOpinionatedProcess(schematic, target, method, expressoConfig)
|
|
|
81
82
|
await generateModule(f.outputPath, f.className, f.moduleName, f.path, f.schematic);
|
|
82
83
|
await (0, cli_ui_1.printGenerateSuccess)(f.schematic, f.file);
|
|
83
84
|
break;
|
|
85
|
+
// NEW v4.0 schematics
|
|
86
|
+
case "interceptor":
|
|
87
|
+
await generateInterceptor(f.outputPath, f.className, v4Options.priority ?? 10);
|
|
88
|
+
await (0, cli_ui_1.printGenerateSuccess)("interceptor", f.file);
|
|
89
|
+
break;
|
|
90
|
+
case "event":
|
|
91
|
+
await generateEvent(f.outputPath, f.className);
|
|
92
|
+
await (0, cli_ui_1.printGenerateSuccess)("event", f.file);
|
|
93
|
+
break;
|
|
94
|
+
case "handler":
|
|
95
|
+
await generateHandler(f.outputPath, f.className, v4Options.event ?? "MyEvent", v4Options.priority ?? 10);
|
|
96
|
+
await (0, cli_ui_1.printGenerateSuccess)("handler", f.file);
|
|
97
|
+
break;
|
|
98
|
+
case "guard":
|
|
99
|
+
await generateGuard(f.outputPath, f.className);
|
|
100
|
+
await (0, cli_ui_1.printGenerateSuccess)("guard", f.file);
|
|
101
|
+
break;
|
|
102
|
+
case "config":
|
|
103
|
+
await generateConfig(f.outputPath, f.className, f.moduleName);
|
|
104
|
+
await (0, cli_ui_1.printGenerateSuccess)("config", f.file);
|
|
105
|
+
break;
|
|
84
106
|
}
|
|
85
107
|
return f.file;
|
|
86
108
|
}
|
|
@@ -248,3 +270,80 @@ async function generateModule(outputPath, className, moduleName, path, schematic
|
|
|
248
270
|
},
|
|
249
271
|
});
|
|
250
272
|
}
|
|
273
|
+
// NEW v4.0 Schematic Generators
|
|
274
|
+
/**
|
|
275
|
+
* Generate an interceptor
|
|
276
|
+
*/
|
|
277
|
+
async function generateInterceptor(outputPath, className, priority) {
|
|
278
|
+
(0, command_utils_1.writeTemplate)({
|
|
279
|
+
outputPath,
|
|
280
|
+
template: {
|
|
281
|
+
path: "../templates/nonopinionated/interceptor.tpl",
|
|
282
|
+
data: {
|
|
283
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(className),
|
|
284
|
+
priority: priority.toString(),
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Generate an event
|
|
291
|
+
*/
|
|
292
|
+
async function generateEvent(outputPath, className) {
|
|
293
|
+
(0, command_utils_1.writeTemplate)({
|
|
294
|
+
outputPath,
|
|
295
|
+
template: {
|
|
296
|
+
path: "../templates/nonopinionated/event.tpl",
|
|
297
|
+
data: {
|
|
298
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(className),
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Generate an event handler
|
|
305
|
+
*/
|
|
306
|
+
async function generateHandler(outputPath, className, eventName, priority) {
|
|
307
|
+
(0, command_utils_1.writeTemplate)({
|
|
308
|
+
outputPath,
|
|
309
|
+
template: {
|
|
310
|
+
path: "../templates/nonopinionated/handler.tpl",
|
|
311
|
+
data: {
|
|
312
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(className),
|
|
313
|
+
eventName: (0, string_utils_1.anyCaseToPascalCase)(eventName),
|
|
314
|
+
eventPath: `./${(0, string_utils_1.anyCaseToKebabCase)(eventName)}.event`,
|
|
315
|
+
priority: priority.toString(),
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Generate a guard
|
|
322
|
+
*/
|
|
323
|
+
async function generateGuard(outputPath, className) {
|
|
324
|
+
(0, command_utils_1.writeTemplate)({
|
|
325
|
+
outputPath,
|
|
326
|
+
template: {
|
|
327
|
+
path: "../templates/nonopinionated/guard.tpl",
|
|
328
|
+
data: {
|
|
329
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(className),
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Generate a config module
|
|
336
|
+
*/
|
|
337
|
+
async function generateConfig(outputPath, className, moduleName) {
|
|
338
|
+
(0, command_utils_1.writeTemplate)({
|
|
339
|
+
outputPath,
|
|
340
|
+
template: {
|
|
341
|
+
path: "../templates/nonopinionated/config.tpl",
|
|
342
|
+
data: {
|
|
343
|
+
className: (0, string_utils_1.anyCaseToPascalCase)(className),
|
|
344
|
+
moduleName: (0, string_utils_1.anyCaseToCamelCase)(moduleName || className),
|
|
345
|
+
envPrefix: (0, string_utils_1.anyCaseToUpperSnakeCase)(className),
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { ExpressoConfig } from "@expressots/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Additional options for v4.0 schematics
|
|
4
|
+
*/
|
|
5
|
+
type V4Options = {
|
|
6
|
+
event?: string;
|
|
7
|
+
priority?: number;
|
|
8
|
+
};
|
|
2
9
|
/**
|
|
3
10
|
* Process commands for opinionated service scaffolding
|
|
4
11
|
* @param schematic - Resource to scaffold
|
|
@@ -6,6 +13,8 @@ import { ExpressoConfig } from "@expressots/shared";
|
|
|
6
13
|
* @param method - HTTP method
|
|
7
14
|
* @param expressoConfig - Expresso configuration [expressots.config.ts]
|
|
8
15
|
* @param pathStyle - Path command style [sugar, nested, single]
|
|
16
|
+
* @param v4Options - Additional options for v4.0 schematics
|
|
9
17
|
* @returns
|
|
10
18
|
*/
|
|
11
|
-
export declare function opinionatedProcess(schematic: string, target: string, method: string, expressoConfig: ExpressoConfig, pathStyle: string): Promise<string>;
|
|
19
|
+
export declare function opinionatedProcess(schematic: string, target: string, method: string, expressoConfig: ExpressoConfig, pathStyle: string, v4Options?: V4Options): Promise<string>;
|
|
20
|
+
export {};
|