@tsed/cli 7.0.0-beta.2 → 7.0.0-beta.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/lib/esm/bin/tsed-mcp.js +42 -0
- package/lib/esm/commands/init/InitCmd.js +7 -7
- package/lib/esm/commands/init/config/InitSchema.js +27 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/mcp/resources/index.js +3 -0
- package/lib/esm/mcp/resources/projectInfoResource.js +43 -0
- package/lib/esm/mcp/resources/serverInfoResource.js +40 -0
- package/lib/esm/mcp/schema/ProjectPreferencesSchema.js +16 -0
- package/lib/esm/mcp/tools/generateTool.js +105 -0
- package/lib/esm/mcp/tools/getTemplateTool.js +47 -0
- package/lib/esm/mcp/tools/index.js +6 -0
- package/lib/esm/mcp/tools/listTemplatesTool.js +37 -0
- package/lib/esm/mcp/tools/setWorkspaceTool.js +64 -0
- package/lib/esm/services/CliProjectService.js +4 -2
- package/lib/esm/services/CliStats.js +45 -0
- package/lib/esm/services/CliTemplatesService.js +10 -3
- package/lib/esm/templates/asyncFactory.template.js +4 -3
- package/lib/esm/templates/barrels.template.js +0 -3
- package/lib/esm/templates/command.template.js +1 -0
- package/lib/esm/templates/controller.template.js +7 -0
- package/lib/esm/templates/decorator.template.js +22 -11
- package/lib/esm/templates/dockerfile.template.js +5 -0
- package/lib/esm/templates/exception-filter.template.js +1 -0
- package/lib/esm/templates/factory.template.js +4 -3
- package/lib/esm/templates/index.logger.template.js +0 -1
- package/lib/esm/templates/interceptor.template.js +1 -0
- package/lib/esm/templates/interface.template.js +1 -0
- package/lib/esm/templates/middleware.template.js +14 -4
- package/lib/esm/templates/model.template.js +1 -0
- package/lib/esm/templates/module.template.js +1 -0
- package/lib/esm/templates/pipe.template.js +1 -0
- package/lib/esm/templates/prisma.service.template.js +1 -0
- package/lib/esm/templates/repository.template.js +1 -0
- package/lib/esm/templates/response-filter.template.js +1 -0
- package/lib/esm/templates/service.template.js +1 -0
- package/lib/esm/templates/value.template.js +1 -0
- package/lib/esm/utils/resolveSchema.js +17 -0
- package/lib/esm/utils/summarizeSchema.js +24 -0
- package/lib/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/types/bin/tsed-mcp.d.ts +2 -0
- package/lib/types/commands/generate/GenerateCmd.d.ts +38 -1
- package/lib/types/commands/init/InitCmd.d.ts +1 -1
- package/lib/types/commands/init/config/InitSchema.d.ts +13 -0
- package/lib/types/index.d.ts +3 -0
- package/lib/types/mcp/resources/index.d.ts +2 -0
- package/lib/types/mcp/resources/projectInfoResource.d.ts +1 -0
- package/lib/types/mcp/resources/serverInfoResource.d.ts +1 -0
- package/lib/types/mcp/schema/ProjectPreferencesSchema.d.ts +9 -0
- package/lib/types/mcp/tools/generateTool.d.ts +88 -0
- package/lib/types/mcp/tools/getTemplateTool.d.ts +86 -0
- package/lib/types/mcp/tools/index.d.ts +2 -0
- package/lib/types/mcp/tools/listTemplatesTool.d.ts +86 -0
- package/lib/types/mcp/tools/setWorkspaceTool.d.ts +91 -0
- package/lib/types/pipes/SymbolNamePipe.d.ts +1 -1
- package/lib/types/services/CliStats.d.ts +36 -0
- package/lib/types/services/CliTemplatesService.d.ts +5 -4
- package/lib/types/templates/asyncFactory.template.d.ts +1 -0
- package/lib/types/templates/barrels.template.d.ts +1 -0
- package/lib/types/templates/command.template.d.ts +1 -0
- package/lib/types/templates/config.template.d.ts +1 -0
- package/lib/types/templates/controller.template.d.ts +1 -0
- package/lib/types/templates/decorator.template.d.ts +1 -0
- package/lib/types/templates/docker-compose.template.d.ts +1 -0
- package/lib/types/templates/exception-filter.template.d.ts +1 -0
- package/lib/types/templates/factory.template.d.ts +1 -0
- package/lib/types/templates/index.command.template.d.ts +1 -0
- package/lib/types/templates/index.config.utils.template.d.ts +1 -0
- package/lib/types/templates/index.controller.template.d.ts +1 -0
- package/lib/types/templates/index.logger.template.d.ts +3 -2
- package/lib/types/templates/index.template.d.ts +1 -0
- package/lib/types/templates/interceptor.template.d.ts +1 -0
- package/lib/types/templates/interface.template.d.ts +1 -0
- package/lib/types/templates/middleware.template.d.ts +1 -0
- package/lib/types/templates/model.template.d.ts +1 -0
- package/lib/types/templates/module.template.d.ts +1 -0
- package/lib/types/templates/pipe.template.d.ts +1 -0
- package/lib/types/templates/prisma.service.template.d.ts +1 -0
- package/lib/types/templates/readme.template.d.ts +1 -0
- package/lib/types/templates/repository.template.d.ts +1 -0
- package/lib/types/templates/response-filter.template.d.ts +1 -0
- package/lib/types/templates/server.template.d.ts +1 -0
- package/lib/types/templates/service.template.d.ts +1 -0
- package/lib/types/templates/tsconfig.spec.template.d.ts +1 -0
- package/lib/types/templates/value.template.d.ts +1 -0
- package/lib/types/utils/defineTemplate.d.ts +11 -0
- package/lib/types/utils/resolveSchema.d.ts +2 -0
- package/lib/types/utils/summarizeSchema.d.ts +8 -0
- package/package.json +5 -3
- package/lib/esm/commands/init/config/InitFileSchema.js +0 -49
- package/lib/types/commands/init/config/InitFileSchema.d.ts +0 -49
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "@swc-node/register/esm-register";
|
|
3
|
+
import { register } from "node:module";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { CLIMCPServer } from "@tsed/cli-mcp";
|
|
7
|
+
const EXT = process.env.CLI_MODE === "ts" ? "ts" : "js";
|
|
8
|
+
register(pathToFileURL(join(import.meta.dirname, `../loaders/alias.hook.${EXT}`)), {
|
|
9
|
+
parentURL: import.meta.dirname,
|
|
10
|
+
data: {
|
|
11
|
+
"@tsed/core": import.meta.resolve("@tsed/core"),
|
|
12
|
+
"@tsed/di": import.meta.resolve("@tsed/di"),
|
|
13
|
+
"@tsed/schema": import.meta.resolve("@tsed/schema"),
|
|
14
|
+
"@tsed/cli-core": import.meta.resolve("@tsed/cli-core"),
|
|
15
|
+
"@tsed/cli": import.meta.resolve("@tsed/cli")
|
|
16
|
+
},
|
|
17
|
+
transferList: []
|
|
18
|
+
});
|
|
19
|
+
const { tools, resources, PKG, TEMPLATE_DIR, ArchitectureConvention, ProjectConvention } = await import("../index.js");
|
|
20
|
+
CLIMCPServer.bootstrap({
|
|
21
|
+
name: "tsed",
|
|
22
|
+
version: PKG.version,
|
|
23
|
+
pkg: PKG,
|
|
24
|
+
templateDir: TEMPLATE_DIR,
|
|
25
|
+
tools,
|
|
26
|
+
resources,
|
|
27
|
+
mcp: {
|
|
28
|
+
mode: process.env.args?.includes("--http") || process.env.USE_MCP_HTTP ? "streamable-http" : "stdio"
|
|
29
|
+
},
|
|
30
|
+
defaultProjectPreferences() {
|
|
31
|
+
return {
|
|
32
|
+
convention: ProjectConvention.DEFAULT,
|
|
33
|
+
architecture: ArchitectureConvention.DEFAULT
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
project: {
|
|
37
|
+
reinstallAfterRun: true
|
|
38
|
+
}
|
|
39
|
+
}).catch((error) => {
|
|
40
|
+
console.error(error);
|
|
41
|
+
process.exit(-1);
|
|
42
|
+
});
|
|
@@ -17,7 +17,7 @@ import { BunRuntime } from "../../runtimes/supports/BunRuntime.js";
|
|
|
17
17
|
import { NodeRuntime } from "../../runtimes/supports/NodeRuntime.js";
|
|
18
18
|
import { CliProjectService } from "../../services/CliProjectService.js";
|
|
19
19
|
import { FeaturesMap, FeatureType } from "./config/FeaturesPrompt.js";
|
|
20
|
-
import {
|
|
20
|
+
import { InitSchema } from "./config/InitSchema.js";
|
|
21
21
|
import { mapToContext } from "./mappers/mapToContext.js";
|
|
22
22
|
import { getFeaturesPrompt } from "./prompts/getFeaturesPrompt.js";
|
|
23
23
|
export class InitCmd {
|
|
@@ -64,10 +64,10 @@ export class InitCmd {
|
|
|
64
64
|
}
|
|
65
65
|
async $beforePrompt(initialOptions) {
|
|
66
66
|
if (initialOptions.file) {
|
|
67
|
-
const file = join(this.packageJson.
|
|
67
|
+
const file = join(this.packageJson.cwd, initialOptions.file);
|
|
68
68
|
return {
|
|
69
69
|
...initialOptions,
|
|
70
|
-
...(await this.cliLoadFile.loadFile(file,
|
|
70
|
+
...(await this.cliLoadFile.loadFile(file, InitSchema))
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
return initialOptions;
|
|
@@ -109,7 +109,7 @@ export class InitCmd {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
async $beforeExec(ctx) {
|
|
112
|
-
this.fs.ensureDirSync(this.packageJson.
|
|
112
|
+
this.fs.ensureDirSync(this.packageJson.cwd);
|
|
113
113
|
ctx.projectName && (this.packageJson.name = ctx.projectName);
|
|
114
114
|
ctx.packageManager && this.packageJson.setPreference("packageManager", ctx.packageManager);
|
|
115
115
|
ctx.runtime && this.packageJson.setPreference("runtime", ctx.runtime);
|
|
@@ -213,15 +213,15 @@ export class InitCmd {
|
|
|
213
213
|
];
|
|
214
214
|
}
|
|
215
215
|
resolveRootDir(ctx) {
|
|
216
|
-
const rootDirName = kebabCase(ctx.projectName || basename(this.packageJson.
|
|
217
|
-
if (this.packageJson.
|
|
216
|
+
const rootDirName = kebabCase(ctx.projectName || basename(this.packageJson.cwd));
|
|
217
|
+
if (this.packageJson.cwd.endsWith(rootDirName)) {
|
|
218
218
|
ctx.projectName = ctx.projectName || rootDirName;
|
|
219
219
|
ctx.root = ".";
|
|
220
220
|
return;
|
|
221
221
|
}
|
|
222
222
|
ctx.projectName = ctx.projectName || rootDirName;
|
|
223
223
|
if (ctx.root && ctx.root !== ".") {
|
|
224
|
-
this.packageJson.
|
|
224
|
+
this.packageJson.setCWD(join(this.packageJson.cwd, rootDirName));
|
|
225
225
|
ctx.root = ".";
|
|
226
226
|
}
|
|
227
227
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PackageManager } from "@tsed/cli-core";
|
|
2
|
+
import { s } from "@tsed/schema";
|
|
3
|
+
import { ArchitectureConvention, PlatformType, ProjectConvention } from "../../../interfaces/index.js";
|
|
4
|
+
import { FeatureType } from "./FeaturesPrompt.js";
|
|
5
|
+
export const InitSchema = s
|
|
6
|
+
.object({
|
|
7
|
+
tsedVersion: s.string().optional().description("The CLI will use the given tsed version to generate the project"),
|
|
8
|
+
projectName: s.string().optional().description("The project name"),
|
|
9
|
+
platform: s
|
|
10
|
+
.string()
|
|
11
|
+
.enum(PlatformType)
|
|
12
|
+
.default(PlatformType.EXPRESS)
|
|
13
|
+
.description("Node.js framework used to run server (Express, Koa, Fastify)"),
|
|
14
|
+
convention: s
|
|
15
|
+
.string()
|
|
16
|
+
.enum(ProjectConvention)
|
|
17
|
+
.default(ProjectConvention.DEFAULT)
|
|
18
|
+
.description("Project convention (Ts.ED or Angular style)"),
|
|
19
|
+
packageManager: s.string().enum(PackageManager).default(PackageManager.NPM).description("Used project manager to install dependencies"),
|
|
20
|
+
runtime: s
|
|
21
|
+
.string()
|
|
22
|
+
.enum("node", "babel", "swc", "webpack", "bun")
|
|
23
|
+
.description("The javascript runtime used to start application (node, node + webpack, node + swc, node + babel, bun)"),
|
|
24
|
+
features: s.array().items(s.string().enum(FeatureType)).required().minItems(1).description("List of feature to create the projet"),
|
|
25
|
+
skipPrompt: s.boolean().default(false).description("Skip the prompt")
|
|
26
|
+
})
|
|
27
|
+
.unknown();
|
package/lib/esm/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "@tsed/logger-std";
|
|
2
2
|
import "./templates/index.js";
|
|
3
|
+
import "./services/CliStats.js";
|
|
3
4
|
export * from "./commands/add/AddCmd.js";
|
|
4
5
|
export * from "./commands/generate/GenerateCmd.js";
|
|
5
6
|
export { default as commands } from "./commands/index.js";
|
|
@@ -12,6 +13,8 @@ export * from "./constants/index.js";
|
|
|
12
13
|
export * from "./fn/exec.js";
|
|
13
14
|
export * from "./fn/render.js";
|
|
14
15
|
export * from "./interfaces/index.js";
|
|
16
|
+
export { default as resources } from "./mcp/resources/index.js";
|
|
17
|
+
export { default as tools } from "./mcp/tools/index.js";
|
|
15
18
|
export * from "./pipes/index.js";
|
|
16
19
|
export * from "./runtimes/index.js";
|
|
17
20
|
export * from "./services/CliProjectService.js";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, normalize, resolve } from "node:path";
|
|
3
|
+
import { ProjectPackageJson } from "@tsed/cli-core";
|
|
4
|
+
import { defineResource, defineTool } from "@tsed/cli-mcp";
|
|
5
|
+
import { constant, inject } from "@tsed/di";
|
|
6
|
+
import { array, object, s, string } from "@tsed/schema";
|
|
7
|
+
import { ProjectPreferenceSchema } from "../schema/ProjectPreferencesSchema.js";
|
|
8
|
+
function resolveCwd(cwd) {
|
|
9
|
+
const projectPackage = inject(ProjectPackageJson);
|
|
10
|
+
const base = cwd || projectPackage.cwd || process.cwd();
|
|
11
|
+
const abs = isAbsolute(base) ? base : resolve(process.cwd(), base);
|
|
12
|
+
return normalize(abs);
|
|
13
|
+
}
|
|
14
|
+
export const projectInfoResource = defineResource({
|
|
15
|
+
name: "project-info",
|
|
16
|
+
uri: "tsed://project/info",
|
|
17
|
+
title: "Inspect project information",
|
|
18
|
+
description: "Read project information like cwd, package.json, preferences and if it's an initialised Ts.ED",
|
|
19
|
+
mimeType: "application/json",
|
|
20
|
+
async handler(uri) {
|
|
21
|
+
const projectPackage = inject(ProjectPackageJson);
|
|
22
|
+
const info = {
|
|
23
|
+
cwd: projectPackage.cwd,
|
|
24
|
+
pkg: projectPackage.toJSON(),
|
|
25
|
+
isInitialized: !!projectPackage.preferences?.packageManager,
|
|
26
|
+
preferences: {
|
|
27
|
+
convention: projectPackage.preferences.convention,
|
|
28
|
+
packageManager: projectPackage.preferences.packageManager,
|
|
29
|
+
platform: projectPackage.preferences.platform,
|
|
30
|
+
runtime: projectPackage.preferences.runtime
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
contents: [
|
|
35
|
+
{
|
|
36
|
+
uri: uri.href,
|
|
37
|
+
mimeType: "application/json",
|
|
38
|
+
text: JSON.stringify(info, null, 2)
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ProjectPackageJson } from "@tsed/cli-core";
|
|
2
|
+
import { defineResource } from "@tsed/cli-mcp";
|
|
3
|
+
import { constant, inject } from "@tsed/di";
|
|
4
|
+
async function getMcpVersion() {
|
|
5
|
+
try {
|
|
6
|
+
// best effort: resolve MCP SDK version via package.json resolution
|
|
7
|
+
const mcpPkg = await import("@modelcontextprotocol/sdk/package.json", { with: { type: "json" } }).catch(() => undefined);
|
|
8
|
+
return mcpPkg?.version;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// ignore
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export const serverInfoResource = defineResource({
|
|
15
|
+
name: "server-info",
|
|
16
|
+
uri: "tsed://server/info",
|
|
17
|
+
title: "Server information",
|
|
18
|
+
description: "Process and environment information for the Ts.ED MCP server.",
|
|
19
|
+
mimeType: "application/json",
|
|
20
|
+
async handler(uri) {
|
|
21
|
+
const projectPackage = inject(ProjectPackageJson);
|
|
22
|
+
const info = {
|
|
23
|
+
pid: process.pid,
|
|
24
|
+
serverCwd: process.cwd(),
|
|
25
|
+
projectCwd: inject(ProjectPackageJson).cwd,
|
|
26
|
+
tsedCliVersion: constant("version"),
|
|
27
|
+
mcpSdkVersion: await getMcpVersion(),
|
|
28
|
+
projectExists: !!projectPackage.preferences?.packageManager
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
contents: [
|
|
32
|
+
{
|
|
33
|
+
uri: uri.href,
|
|
34
|
+
mimeType: "application/json",
|
|
35
|
+
text: JSON.stringify(info, null, 2)
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PackageManager } from "@tsed/cli-core";
|
|
2
|
+
import { s } from "@tsed/schema";
|
|
3
|
+
import { PlatformType } from "../../interfaces/PlatformType.js";
|
|
4
|
+
import { ProjectConvention } from "../../interfaces/ProjectConvention.js";
|
|
5
|
+
export const ProjectPreferenceSchema = s
|
|
6
|
+
.object({
|
|
7
|
+
convention: s.string().enum(ProjectConvention).description("Project convention (Ts.ED or Angular style)"),
|
|
8
|
+
packageManager: s.string().enum(PackageManager).description("Used project manager to install dependencies"),
|
|
9
|
+
runtime: s
|
|
10
|
+
.string()
|
|
11
|
+
.enum("node", "babel", "swc", "webpack", "bun")
|
|
12
|
+
.description("The javascript runtime used to start application (node, node + webpack, node + swc, node + babel, bun)"),
|
|
13
|
+
platform: s.string().enum(PlatformType).description("Node.js framework used to run server (Express, Koa, Fastify)")
|
|
14
|
+
})
|
|
15
|
+
.optional()
|
|
16
|
+
.description("Resolved project preferences");
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ProjectPackageJson, validate } from "@tsed/cli-core";
|
|
2
|
+
import { defineTool } from "@tsed/cli-mcp";
|
|
3
|
+
import { inject } from "@tsed/di";
|
|
4
|
+
import { array, number, object, string } from "@tsed/schema";
|
|
5
|
+
import { CliProjectService } from "../../services/CliProjectService.js";
|
|
6
|
+
import { CliTemplatesService } from "../../services/CliTemplatesService.js";
|
|
7
|
+
import { mapDefaultTemplateOptions } from "../../services/mappers/mapDefaultTemplateOptions.js";
|
|
8
|
+
export const generateTool = defineTool({
|
|
9
|
+
name: "generate-file",
|
|
10
|
+
title: "Generate file",
|
|
11
|
+
description: "Generate a new Ts.ED provider class depending on the given parameters.",
|
|
12
|
+
inputSchema: object({
|
|
13
|
+
id: string().required().description("The template id of the file to generate (e.g., 'controller', 'service', 'model')."),
|
|
14
|
+
name: string().required().description("The name of the class to generate."),
|
|
15
|
+
properties: object()
|
|
16
|
+
.unknown()
|
|
17
|
+
.optional()
|
|
18
|
+
.description("Additional properties to generate file depending on the given template id. For example controller accept extra properties. Run 'list-templates' and/or 'get-template' to discover extra properties available for the template.")
|
|
19
|
+
.default({})
|
|
20
|
+
}),
|
|
21
|
+
outputSchema: object({
|
|
22
|
+
files: array().items(string()).description("List of generated files."),
|
|
23
|
+
count: number().description("Number of files generated."),
|
|
24
|
+
symbolPath: string().optional().description("Main generated symbol path"),
|
|
25
|
+
logs: array().items(string()).optional().description("Execution logs"),
|
|
26
|
+
warnings: array().items(string()).optional().description("Non blocking warnings")
|
|
27
|
+
}),
|
|
28
|
+
async handler({ id, name, properties }) {
|
|
29
|
+
const projectService = inject(CliProjectService);
|
|
30
|
+
const templates = inject(CliTemplatesService);
|
|
31
|
+
const projectPackage = inject(ProjectPackageJson);
|
|
32
|
+
// Pre-checks
|
|
33
|
+
const warnings = [];
|
|
34
|
+
if (!projectPackage.cwd) {
|
|
35
|
+
return {
|
|
36
|
+
content: [],
|
|
37
|
+
isError: true,
|
|
38
|
+
structuredContent: {
|
|
39
|
+
code: "E_CWD_NOT_SET",
|
|
40
|
+
message: "Workspace is not set. Call set-workspace first to define the project root.",
|
|
41
|
+
suggestion: "Call 'set-workspace' with the project directory, then retry 'generate-file'."
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
if (!projectPackage.preferences?.packageManager) {
|
|
46
|
+
return {
|
|
47
|
+
content: [],
|
|
48
|
+
isError: true,
|
|
49
|
+
structuredContent: {
|
|
50
|
+
code: "E_PROJECT_NOT_INITIALIZED",
|
|
51
|
+
message: "The target directory does not appear to be an initialized Ts.ED project.",
|
|
52
|
+
suggestion: "Run 'init-project' to initialize a Ts.ED project in this workspace before generating files, or use 'set-workspace' to configure the tool to point to the appropriate working directory."
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const ctx = mapDefaultTemplateOptions({ ...properties, type: id, name });
|
|
57
|
+
const { type } = ctx;
|
|
58
|
+
// Validate template exists
|
|
59
|
+
const template = inject(CliTemplatesService).get(type);
|
|
60
|
+
if (!template) {
|
|
61
|
+
return {
|
|
62
|
+
content: [],
|
|
63
|
+
isError: true,
|
|
64
|
+
structuredContent: {
|
|
65
|
+
code: "E_TEMPLATE_UNKNOWN",
|
|
66
|
+
message: `Unknown template type '${type}'.`,
|
|
67
|
+
suggestion: "Use 'list-templates' to discover available template types."
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// validate the schema of the resolved template
|
|
72
|
+
if (template.schema && properties) {
|
|
73
|
+
const { isValid, errors } = validate(properties, template.schema);
|
|
74
|
+
if (!isValid) {
|
|
75
|
+
return {
|
|
76
|
+
content: [],
|
|
77
|
+
isError: true,
|
|
78
|
+
structuredContent: {
|
|
79
|
+
code: "E_ARGS_INVALID",
|
|
80
|
+
message: `Invalid arguments for template '${type}'.`,
|
|
81
|
+
suggestion: "Call 'get-template' to inspect required fields and formats, then retry.",
|
|
82
|
+
details: { errors }
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await projectService.createFromTemplate(type, ctx);
|
|
88
|
+
await projectService.transformFiles(ctx);
|
|
89
|
+
const rendered = templates.getRenderedFiles();
|
|
90
|
+
const files = rendered.map((f) => f.outputPath);
|
|
91
|
+
const count = files.length;
|
|
92
|
+
const symbolPath = ctx.symbolPath || rendered[0]?.symbolPath || rendered[0]?.outputPath;
|
|
93
|
+
const logs = [`createFromTemplate:${type}`, `transformFiles:done`, `files:${count}`];
|
|
94
|
+
return {
|
|
95
|
+
content: [],
|
|
96
|
+
structuredContent: {
|
|
97
|
+
files,
|
|
98
|
+
count,
|
|
99
|
+
symbolPath,
|
|
100
|
+
logs,
|
|
101
|
+
warnings: warnings.length ? warnings : undefined
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { defineTool } from "@tsed/cli-mcp";
|
|
2
|
+
import { inject } from "@tsed/di";
|
|
3
|
+
import { s } from "@tsed/schema";
|
|
4
|
+
import { CliTemplatesService } from "../../services/CliTemplatesService.js";
|
|
5
|
+
import { resolveSchema } from "../../utils/resolveSchema.js";
|
|
6
|
+
export const getTemplateTool = defineTool({
|
|
7
|
+
name: "get-template",
|
|
8
|
+
title: "Get template",
|
|
9
|
+
description: "Return full information for a template including its JSON schema (if provided).",
|
|
10
|
+
inputSchema: s.object({
|
|
11
|
+
id: s.string().required().description("Template id (e.g., 'controller', 'service')")
|
|
12
|
+
}),
|
|
13
|
+
outputSchema: s.object({
|
|
14
|
+
id: s.string().description("Template id"),
|
|
15
|
+
label: s.string().description("Template label"),
|
|
16
|
+
description: s.string().optional().description("Template description"),
|
|
17
|
+
schema: s
|
|
18
|
+
.object()
|
|
19
|
+
.optional()
|
|
20
|
+
.description("Additional properties to provide to generate-file tool for this template (beyond the global 'id' and 'name' fields)")
|
|
21
|
+
}),
|
|
22
|
+
async handler(args) {
|
|
23
|
+
const { id } = args;
|
|
24
|
+
const templates = inject(CliTemplatesService);
|
|
25
|
+
const tpl = templates.get(id);
|
|
26
|
+
if (!tpl) {
|
|
27
|
+
return {
|
|
28
|
+
content: [],
|
|
29
|
+
isError: true,
|
|
30
|
+
structuredContent: {
|
|
31
|
+
code: "E_TEMPLATE_UNKNOWN",
|
|
32
|
+
message: `Unknown template id '${id}'.`,
|
|
33
|
+
suggestion: "Use 'list-templates' to discover available template types."
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
content: [],
|
|
39
|
+
structuredContent: {
|
|
40
|
+
id: tpl.id,
|
|
41
|
+
label: tpl.label,
|
|
42
|
+
description: tpl.description,
|
|
43
|
+
schema: await resolveSchema(tpl.schema)
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { projectInfoResource } from "../resources/projectInfoResource.js";
|
|
2
|
+
import { generateTool } from "./generateTool.js";
|
|
3
|
+
import { getTemplateTool } from "./getTemplateTool.js";
|
|
4
|
+
import { listTemplatesTool } from "./listTemplatesTool.js";
|
|
5
|
+
import { setWorkspaceTool } from "./setWorkspaceTool.js";
|
|
6
|
+
export default [setWorkspaceTool, projectInfoResource, listTemplatesTool, getTemplateTool, generateTool];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineTool } from "@tsed/cli-mcp";
|
|
2
|
+
import { inject } from "@tsed/di";
|
|
3
|
+
import { array, object, string } from "@tsed/schema";
|
|
4
|
+
import { CliTemplatesService } from "../../services/CliTemplatesService.js";
|
|
5
|
+
import { summarizeSchema } from "../../utils/summarizeSchema.js";
|
|
6
|
+
export const listTemplatesTool = defineTool({
|
|
7
|
+
name: "list-templates",
|
|
8
|
+
title: "List templates",
|
|
9
|
+
description: "List available Ts.ED templates for generation (tsed generate). Optionally filter by type substring.",
|
|
10
|
+
inputSchema: object({
|
|
11
|
+
search: string().description("Filter by template id/label substring (case-insensitive).")
|
|
12
|
+
}),
|
|
13
|
+
outputSchema: object({
|
|
14
|
+
items: array().items(object({
|
|
15
|
+
id: string(),
|
|
16
|
+
label: string().optional(),
|
|
17
|
+
description: string().optional(),
|
|
18
|
+
required: array().items(string()).optional(),
|
|
19
|
+
properties: object().optional()
|
|
20
|
+
}))
|
|
21
|
+
}),
|
|
22
|
+
async handler(args) {
|
|
23
|
+
const { search } = args || {};
|
|
24
|
+
const templates = inject(CliTemplatesService);
|
|
25
|
+
return {
|
|
26
|
+
content: [],
|
|
27
|
+
structuredContent: {
|
|
28
|
+
items: templates.find(search).map((tpl) => ({
|
|
29
|
+
id: tpl.id,
|
|
30
|
+
label: tpl.label,
|
|
31
|
+
description: tpl.description,
|
|
32
|
+
...summarizeSchema(tpl)
|
|
33
|
+
}))
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, normalize, resolve } from "node:path";
|
|
3
|
+
import { CliFs, PackageManager, ProjectPackageJson } from "@tsed/cli-core";
|
|
4
|
+
import { defineTool } from "@tsed/cli-mcp";
|
|
5
|
+
import { inject } from "@tsed/di";
|
|
6
|
+
import { s } from "@tsed/schema";
|
|
7
|
+
import { ProjectPreferenceSchema } from "../schema/ProjectPreferencesSchema.js";
|
|
8
|
+
function normalizeCwd(input) {
|
|
9
|
+
const abs = isAbsolute(input) ? input : resolve(process.cwd(), input);
|
|
10
|
+
return normalize(abs);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Simplified behavior per MVP:
|
|
14
|
+
* - If the provided cwd exists: set it, resolve the nearest package.json root via ProjectPackageJson, return that resolved cwd.
|
|
15
|
+
* - If the provided cwd does not exist: do not create anything; return an error suggesting to confirm creation and run init-project.
|
|
16
|
+
*/
|
|
17
|
+
export const setWorkspaceTool = defineTool({
|
|
18
|
+
name: "set-workspace",
|
|
19
|
+
title: "Set workspace",
|
|
20
|
+
description: "Define the workspace (current working directory) for subsequent operations. Mirrors the -r option of tsed CLI. If the path exists, returns the resolved project root (nearest package.json). If it doesn't, asks to confirm creation and use init-project.",
|
|
21
|
+
inputSchema: s.object({
|
|
22
|
+
cwd: s.string().required().description("Absolute or relative path to use as workspace root.")
|
|
23
|
+
}),
|
|
24
|
+
outputSchema: s.object({
|
|
25
|
+
cwd: s.string().description("Resolved project root (nearest package.json directory or the provided path)."),
|
|
26
|
+
packageJson: s.object().optional().description("Resolved package json"),
|
|
27
|
+
preferences: ProjectPreferenceSchema
|
|
28
|
+
}),
|
|
29
|
+
async handler(args) {
|
|
30
|
+
const { cwd: raw } = args;
|
|
31
|
+
const cwd = normalizeCwd(raw);
|
|
32
|
+
const fs = inject(CliFs);
|
|
33
|
+
const projectPackage = inject(ProjectPackageJson);
|
|
34
|
+
if (fs.exists(cwd)) {
|
|
35
|
+
// Set the base CWD for the CLI services (this resolves to nearest package.json)
|
|
36
|
+
projectPackage.setCWD(cwd);
|
|
37
|
+
// Resolved project root
|
|
38
|
+
const resolved = projectPackage.cwd;
|
|
39
|
+
return {
|
|
40
|
+
content: [],
|
|
41
|
+
structuredContent: {
|
|
42
|
+
cwd: resolved,
|
|
43
|
+
pkg: projectPackage.toJSON(),
|
|
44
|
+
preferences: {
|
|
45
|
+
convention: projectPackage.preferences.convention,
|
|
46
|
+
packageManager: projectPackage.preferences.packageManager,
|
|
47
|
+
platform: projectPackage.preferences.platform,
|
|
48
|
+
runtime: projectPackage.preferences.runtime
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
content: [],
|
|
55
|
+
isError: true,
|
|
56
|
+
structuredContent: {
|
|
57
|
+
cwd,
|
|
58
|
+
code: "E_CWD_NOT_FOUND",
|
|
59
|
+
message: `Directory '${cwd}' does not exist.`,
|
|
60
|
+
suggestion: "Confirm you want to create this folder, then call 'init-project' with { cwd: '<path>', ... } to scaffold a new Ts.ED project."
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
@@ -34,8 +34,10 @@ export class CliProjectService {
|
|
|
34
34
|
this.project = new ProjectClient({
|
|
35
35
|
rootDir: this.rootDir
|
|
36
36
|
});
|
|
37
|
-
const files = fs.globSync([
|
|
38
|
-
files
|
|
37
|
+
const files = fs.globSync([join(this.rootDir, "**/*.ts"), "!node_modules", "!node_modules/**", "!**/node_modules/**"]);
|
|
38
|
+
files
|
|
39
|
+
.filter((file) => !file.includes("node_modules"))
|
|
40
|
+
.forEach((file) => {
|
|
39
41
|
this.project.createSourceFile(file, fs.readFileSync(file, "utf8"), {
|
|
40
42
|
overwrite: true
|
|
41
43
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { CliHttpClient, ProjectPackageJson } from "@tsed/cli-core";
|
|
2
|
+
import { constant, inject, injectable } from "@tsed/di";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
export class CliStats extends CliHttpClient {
|
|
5
|
+
constructor() {
|
|
6
|
+
super(...arguments);
|
|
7
|
+
this.disabled = constant("stats.disabled", false);
|
|
8
|
+
this.host = constant("stats.url", "https://api.tsed.dev");
|
|
9
|
+
this.projectPackage = inject(ProjectPackageJson);
|
|
10
|
+
}
|
|
11
|
+
sendInit(opts) {
|
|
12
|
+
if (!this.disabled) {
|
|
13
|
+
const data = {
|
|
14
|
+
features: [],
|
|
15
|
+
is_success: true,
|
|
16
|
+
...opts,
|
|
17
|
+
os: os.type(),
|
|
18
|
+
convention: this.projectPackage.preferences.convention === "conv_default" ? "tsed" : "angular",
|
|
19
|
+
platform: this.projectPackage.preferences.platform,
|
|
20
|
+
package_manager: this.projectPackage.preferences.packageManager,
|
|
21
|
+
runtime: this.projectPackage.preferences.runtime,
|
|
22
|
+
channel: opts.channel || "cli",
|
|
23
|
+
cli_version: constant("version", ""),
|
|
24
|
+
tsed_version: this.projectPackage.dependencies["@tsed/platform-http"]
|
|
25
|
+
};
|
|
26
|
+
return this.post("/rest/cli/stats", {
|
|
27
|
+
data
|
|
28
|
+
}).catch((er) => {
|
|
29
|
+
return null;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
$onFinish(data, er) {
|
|
34
|
+
if (data.commandName === "init") {
|
|
35
|
+
return this.sendInit({
|
|
36
|
+
channel: "cli",
|
|
37
|
+
is_success: !er,
|
|
38
|
+
features: data.features
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
onSuccess(options) { }
|
|
43
|
+
onError(error, options) { }
|
|
44
|
+
}
|
|
45
|
+
injectable(CliStats);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { CliFs } from "@tsed/cli-core";
|
|
3
|
-
import { constant, inject, injectable, injectMany, logger } from "@tsed/di";
|
|
3
|
+
import { constant, context, inject, injectable, injectMany, logger } from "@tsed/di";
|
|
4
4
|
import { globby } from "globby";
|
|
5
5
|
import { TEMPLATE_DIR } from "../constants/index.js";
|
|
6
6
|
import { mapDefaultTemplateOptions } from "./mappers/mapDefaultTemplateOptions.js";
|
|
7
7
|
export class CliTemplatesService {
|
|
8
8
|
constructor() {
|
|
9
9
|
this.fs = inject(CliFs);
|
|
10
|
-
this.renderedFiles = [];
|
|
11
10
|
}
|
|
11
|
+
// readonly renderedFiles: TemplateRenderReturnType[] = [];
|
|
12
12
|
#customTemplates;
|
|
13
13
|
get rootDir() {
|
|
14
14
|
return constant("project.rootDir", "");
|
|
@@ -113,8 +113,15 @@ export class CliTemplatesService {
|
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
+
getRenderedFiles() {
|
|
117
|
+
const $ctx = context();
|
|
118
|
+
if (!$ctx?.has("renderedFiles")) {
|
|
119
|
+
$ctx?.set("renderedFiles", []);
|
|
120
|
+
}
|
|
121
|
+
return $ctx?.get("renderedFiles") || [];
|
|
122
|
+
}
|
|
116
123
|
pushRenderResult(renderedFile) {
|
|
117
|
-
this.
|
|
124
|
+
this.getRenderedFiles().push(renderedFile);
|
|
118
125
|
return renderedFile;
|
|
119
126
|
}
|
|
120
127
|
}
|
|
@@ -3,12 +3,13 @@ import { camelCase, pascalCase } from "change-case";
|
|
|
3
3
|
export default defineTemplate({
|
|
4
4
|
id: "async.factory",
|
|
5
5
|
label: "Async Factory",
|
|
6
|
+
description: "Generate an async factory token for Ts.ED DI in src/services.",
|
|
6
7
|
fileName: "{{symbolName}}.factory?",
|
|
7
8
|
outputDir: "{{srcDir}}/services",
|
|
8
9
|
render(symbolName) {
|
|
9
10
|
const camelName = camelCase(symbolName);
|
|
10
11
|
const optName = pascalCase(symbolName + "Options");
|
|
11
|
-
return `import {injectable} from "@tsed/di";
|
|
12
|
+
return `import {injectable, constant} from "@tsed/di";
|
|
12
13
|
|
|
13
14
|
interface ${optName} {
|
|
14
15
|
|
|
@@ -17,7 +18,7 @@ interface ${optName} {
|
|
|
17
18
|
declare global {
|
|
18
19
|
namespace TsED {
|
|
19
20
|
interface Configuration extends Record<string, any> {
|
|
20
|
-
${camelName}:
|
|
21
|
+
${camelName}: ${optName};
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -33,6 +34,6 @@ export const ${symbolName} = injectable(Symbol.for("${symbolName}"))
|
|
|
33
34
|
})
|
|
34
35
|
.token();
|
|
35
36
|
|
|
36
|
-
export type {
|
|
37
|
+
export type ${symbolName} = typeof ${symbolName};`;
|
|
37
38
|
}
|
|
38
39
|
});
|
|
@@ -3,6 +3,7 @@ import { kebabCase } from "change-case";
|
|
|
3
3
|
export default defineTemplate({
|
|
4
4
|
id: "command",
|
|
5
5
|
label: "Command",
|
|
6
|
+
description: "Create a CLI command provider with prompts, context mapping and tasks in src/bin/commands.",
|
|
6
7
|
fileName: "{{symbolName}}.command",
|
|
7
8
|
outputDir: "{{srcDir}}/bin/commands",
|
|
8
9
|
render(symbolName) {
|