@tsed/cli 7.0.0-alpha.1 → 7.0.0-alpha.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.
@@ -53,15 +53,11 @@ export class GenerateCmd {
53
53
  {
54
54
  title: `Generate ${ctx.type} file to '${symbolPath}.ts'`,
55
55
  skip: !this.templates.get(type),
56
- task: () => {
57
- this.projectService.createFromTemplate(type, ctx);
58
- }
56
+ task: () => this.projectService.createFromTemplate(type, ctx)
59
57
  },
60
58
  {
61
59
  title: "Transform generated files",
62
- task: () => {
63
- return this.projectService.transformFiles(ctx);
64
- }
60
+ task: () => this.projectService.transformFiles(ctx)
65
61
  }
66
62
  ];
67
63
  }
@@ -7,6 +7,7 @@ import { kebabCase } from "change-case";
7
7
  import { DEFAULT_TSED_TAGS, TEMPLATE_DIR } from "../../constants/index.js";
8
8
  import { exec } from "../../fn/exec.js";
9
9
  import { render } from "../../fn/render.js";
10
+ import { taskOutput } from "../../fn/taskOutput.js";
10
11
  import { ArchitectureConvention } from "../../interfaces/ArchitectureConvention.js";
11
12
  import { PlatformType } from "../../interfaces/index.js";
12
13
  import { ProjectConvention } from "../../interfaces/ProjectConvention.js";
@@ -160,13 +161,11 @@ export class InitCmd {
160
161
  return [
161
162
  {
162
163
  title: "Render base files",
163
- task: async () => {
164
- return this.renderFiles(ctx);
165
- }
164
+ task: () => this.renderFiles(ctx)
166
165
  },
167
166
  {
168
167
  title: "Alter package json",
169
- task: async () => {
168
+ task: () => {
170
169
  return $asyncAlter("$alterPackageJson", this.packageJson, [ctx]);
171
170
  }
172
171
  },
@@ -175,7 +174,7 @@ export class InitCmd {
175
174
  task: createSubTasks(async () => {
176
175
  const subTasks = [
177
176
  ...(await exec("generate", {
178
- ...ctx,
177
+ //...ctx,
179
178
  type: "controller",
180
179
  route: "rest",
181
180
  name: "HelloWorld",
@@ -183,7 +182,7 @@ export class InitCmd {
183
182
  })),
184
183
  ...(ctx.commands
185
184
  ? await exec("generate", {
186
- ...ctx,
185
+ //...ctx,
187
186
  type: "command",
188
187
  route: "hello",
189
188
  name: "hello"
@@ -241,6 +240,7 @@ export class InitCmd {
241
240
  "@tsed/core": ctx.tsedVersion,
242
241
  "@tsed/di": ctx.tsedVersion,
243
242
  "@tsed/ajv": ctx.tsedVersion,
243
+ "@tsed/config": ctx.tsedVersion,
244
244
  "@tsed/exceptions": ctx.tsedVersion,
245
245
  "@tsed/schema": ctx.tsedVersion,
246
246
  "@tsed/json-mapper": ctx.tsedVersion,
@@ -259,9 +259,6 @@ export class InitCmd {
259
259
  "@tsed/barrels": "latest",
260
260
  ajv: "latest",
261
261
  "cross-env": "latest",
262
- dotenv: "latest",
263
- "dotenv-expand": "latest",
264
- "dotenv-flow": "latest",
265
262
  ...this.runtimes.get().dependencies(),
266
263
  ...this.platforms.get(ctx.platform).dependencies(ctx)
267
264
  });
@@ -326,8 +323,11 @@ export class InitCmd {
326
323
  }
327
324
  async renderFiles(ctx) {
328
325
  // base files
326
+ let startTime = Date.now();
329
327
  await this.baseFiles(ctx);
328
+ taskOutput(`Base files rendered (${Date.now() - startTime}ms)`);
330
329
  const files = await $asyncAlter("$alterRenderFiles", [], [ctx]);
330
+ startTime = Date.now();
331
331
  const promises = files.map((option) => {
332
332
  if (!option) {
333
333
  return;
@@ -350,6 +350,7 @@ export class InitCmd {
350
350
  }
351
351
  });
352
352
  await Promise.all(promises);
353
+ taskOutput(`Plugins files rendered (${Date.now() - startTime}ms)`);
353
354
  }
354
355
  }
355
356
  command(InitCmd, {
@@ -16,6 +16,7 @@ export var FeatureType;
16
16
  FeatureType["CONFIG_DOTENV"] = "config:dotenv";
17
17
  FeatureType["CONFIG_JSON"] = "config:json";
18
18
  FeatureType["CONFIG_YAML"] = "config:yaml";
19
+ FeatureType["CONFIG_AWS_SECRETS"] = "config:aws_secrets";
19
20
  FeatureType["CONFIG_IOREDIS"] = "config:ioredis";
20
21
  FeatureType["CONFIG_MONGO"] = "config:mongo";
21
22
  FeatureType["CONFIG_VAULT"] = "config:vault";
@@ -158,39 +159,59 @@ export const FeaturesMap = {
158
159
  name: "Envs"
159
160
  },
160
161
  [FeatureType.CONFIG_DOTENV]: {
161
- name: "Dotenv"
162
+ name: "Dotenv",
163
+ dependencies: {
164
+ dotenv: "latest",
165
+ "dotenv-expand": "latest",
166
+ "dotenv-flow": "latest"
167
+ }
162
168
  },
163
169
  [FeatureType.CONFIG_JSON]: {
164
170
  name: "JSON"
165
171
  },
166
172
  [FeatureType.CONFIG_YAML]: {
167
- name: "YAML"
173
+ name: "YAML",
174
+ dependencies: {
175
+ "js-yaml": "latest"
176
+ }
177
+ },
178
+ [FeatureType.CONFIG_AWS_SECRETS]: {
179
+ name: "AWS Secrets Manager (Premium)",
180
+ dependencies: {
181
+ "@tsedio/config-source-aws-secrets": "latest",
182
+ "@aws-sdk/client-secrets-manager": "latest"
183
+ }
168
184
  },
169
185
  [FeatureType.CONFIG_IOREDIS]: {
170
186
  name: "IORedis (Premium)",
171
187
  dependencies: {
172
- ioredis: "latest",
173
- "@tsedio/config-ioredis": "{{tsedVersion}}"
188
+ "@tsedio/config-ioredis": "{{tsedVersion}}",
189
+ "@tsed/ioredis": "{{tsedVersion}}",
190
+ ioredis: "latest"
191
+ },
192
+ devDependencies: {
193
+ "@tsedio/testcontainers-redis": "latest"
174
194
  }
175
195
  },
176
196
  [FeatureType.CONFIG_MONGO]: {
177
197
  name: "MongoDB (Premium)",
178
198
  dependencies: {
179
199
  mongodb: "latest",
180
- "@tsedio/config-mongo": "{{tsedVersion}}"
200
+ "@tsedio/config-mongo": "latest"
181
201
  }
182
202
  },
183
203
  [FeatureType.CONFIG_VAULT]: {
184
204
  name: "Vault (Premium)",
185
205
  dependencies: {
186
- "@tsedio/config-vault": "{{tsedVersion}}"
206
+ "@tsedio/config-vault": "latest",
207
+ "node-vault": "latest"
187
208
  }
188
209
  },
189
210
  [FeatureType.CONFIG_POSTGRES]: {
190
211
  name: "Postgres (Premium)",
191
212
  dependencies: {
192
213
  pg: "latest",
193
- "@tsedio/config-postgres": "{{tsedVersion}}"
214
+ "@tsedio/config-postgres": "latest"
194
215
  }
195
216
  },
196
217
  /// TYPEORM
@@ -389,6 +410,7 @@ export const FeaturesPrompt = (availableRuntimes, availablePackageManagers) => [
389
410
  FeatureType.CONFIG_DOTENV,
390
411
  FeatureType.CONFIG_JSON,
391
412
  FeatureType.CONFIG_YAML,
413
+ FeatureType.CONFIG_AWS_SECRETS,
392
414
  FeatureType.CONFIG_IOREDIS,
393
415
  FeatureType.CONFIG_MONGO,
394
416
  FeatureType.CONFIG_VAULT,
@@ -0,0 +1,7 @@
1
+ import { context } from "@tsed/di";
2
+ export function taskOutput(output) {
3
+ const task = context().get("currentTask");
4
+ if (task) {
5
+ task.output = output;
6
+ }
7
+ }
@@ -1,26 +1,105 @@
1
- import { SyntaxKind } from "ts-morph";
2
1
  export function transformConfigFile(project, data) {
3
- const sourceFile = project.configSourceFile;
4
- const options = project.findConfiguration("config");
5
- if (!options) {
6
- return;
2
+ if (!data.config && data.commandName === "init") {
3
+ project.addConfigSource("EnvsConfigSource", {
4
+ moduleSpecifier: "@tsedio/config-envs/envs"
5
+ });
7
6
  }
8
7
  if (data.config) {
9
- // const extendsConfig = project.getPropertyAssignment(options, {
10
- // name: "extends",
11
- // kind: SyntaxKind.ObjectLiteralExpression,
12
- // initializer: "[]"
13
- // });
14
- //
15
- // if (data.configEnvs) {
16
- // sourceFile.addImportDeclaration({
17
- // moduleSpecifier: "@tsed/config/envs",
18
- // namedImports: [{name: "EnvsConfigSource"}]
19
- // });
20
- // }
8
+ if (data.configDotenv) {
9
+ project.addConfigSource("DotenvConfigSource", {
10
+ moduleSpecifier: "@tsedio/config-envs/dotenv"
11
+ });
12
+ }
13
+ else if (data.configEnvs) {
14
+ project.addConfigSource("EnvsConfigSource", {
15
+ moduleSpecifier: "@tsedio/config-envs/envs"
16
+ });
17
+ }
18
+ if (data.configJson) {
19
+ project.addConfigSource("JsonConfigSource", {
20
+ moduleSpecifier: "@tsedio/config-envs/json",
21
+ content: `withOptions(JsonConfigSource, {
22
+ path: "./config.json"
23
+ })`
24
+ });
25
+ }
26
+ if (data.configYaml) {
27
+ project.addConfigSource("YamlConfigSource", {
28
+ moduleSpecifier: "@tsedio/config-envs/yaml",
29
+ content: `withOptions(YamlConfigSource, {
30
+ path: "./config.yaml"
31
+ })`
32
+ });
33
+ }
34
+ if (data.configAwsSecrets) {
35
+ project.addConfigSource("AwsSecretsConfigSource", {
36
+ moduleSpecifier: "@tsedio/config-source-aws-secrets",
37
+ content: `withOptions(AwsSecretsConfigSource, {
38
+ name: "aws",
39
+ path: "/my-app/config", // Path prefix in Secrets Manager
40
+ region: "us-east-1", // AWS region
41
+ watch: true // Enable secrets watching
42
+ // validationSchema: object({}) // Optional: add a validation schema
43
+ // maxConcurrency: 10
44
+ })`
45
+ });
46
+ }
47
+ if (data.configVault) {
48
+ project.addConfigSource("VaultConfigSource", {
49
+ moduleSpecifier: "@tsedio/config-vault",
50
+ content: ` withOptions(VaultConfigSource, {
51
+ name: "vault",
52
+ endpoint: "http://localhost:8200", // Vault server URL
53
+ token: "your-vault-token", // Your Vault token
54
+ secretPath: "secret/data/myapp", // Path to your secret (KV v2 or v1, see below)
55
+ refreshInterval: 10000 // ⏱️ Polling interval in ms (default: 10s)
56
+ // Additional node-vault options
57
+
58
+ // validationSchema: object({}) // Optional: add a validation schema
59
+ })`
60
+ });
61
+ }
62
+ if (data.configIoredis) {
63
+ project.addConfigSource("IoredisConfigSource", {
64
+ moduleSpecifier: "@tsedio/config-ioredis",
65
+ content: `withOptions(IoredisConfigSource, {
66
+ name: "redis",
67
+ prefixKey: "my-config", // Optional: All config keys will be prefixed
68
+ url: "redis://localhost:6379" // Or use any Redis/Cluster options
69
+ // validationSchema: object({}) // Optional: add a validation schema
70
+ })`
71
+ });
72
+ }
73
+ if (data.configMongo) {
74
+ project.addConfigSource("MongoConfigSource", {
75
+ moduleSpecifier: "@tsedio/config-mongo",
76
+ content: `withOptions(MongoConfigSource, {
77
+ name: "mongo",
78
+ url: "mongodb://localhost:27017", // MongoDB connection URL
79
+ database: "my_database", // Database name
80
+ collection: "config" // Collection used for config storage
81
+
82
+ // Additional MongoDB client options can be provided here
83
+
84
+ // ConfigSource options
85
+ // validationSchema: object({}) // Optional: add a validation schema
86
+ })`
87
+ });
88
+ }
89
+ if (data.configPostgres) {
90
+ project.addConfigSource("PostgresConfigSource", {
91
+ moduleSpecifier: "@tsedio/config-postgres",
92
+ content: `withOptions(PostgresConfigSource, {
93
+ name: "postgres",
94
+ connectionString: "postgresql://postgres:postgres@localhost:5432/my_database", // PostgreSQL connection string
95
+ table: "config" // Table used for config storage
96
+
97
+ // Additional PostgresSQL client options can be provided here
98
+
99
+ // ConfigSource options
100
+ // validationSchema: object({}) // Optional: add a validation schema
101
+ })`
102
+ });
103
+ }
21
104
  }
22
- sourceFile.organizeImports();
23
- sourceFile.formatText({
24
- indentSize: 2
25
- });
26
105
  }
@@ -12,7 +12,6 @@ export function transformIndexFile(project, data) {
12
12
  moduleSpecifier: "@tsed/platform-" + data.platform.toLowerCase(),
13
13
  namedImports: [platformName]
14
14
  });
15
- // replace PlatformBuilder with Platform<PlatformType>
16
15
  sourceFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue() === "@tsed/platform-http")?.remove();
17
16
  sourceFile.getDescendantsOfKind(SyntaxKind.Identifier).map((identifier) => {
18
17
  if (identifier.getText() === "PlatformBuilder") {
@@ -21,8 +20,4 @@ export function transformIndexFile(project, data) {
21
20
  }
22
21
  return identifier;
23
22
  });
24
- sourceFile.organizeImports();
25
- sourceFile.formatText({
26
- indentSize: 2
27
- });
28
23
  }
@@ -57,8 +57,4 @@ export function transformServerFile(project, data) {
57
57
  project.addMountPath("/", "...Object.values(pages)");
58
58
  }
59
59
  }
60
- sourceFile.organizeImports();
61
- sourceFile.formatText({
62
- indentSize: 2
63
- });
64
60
  }
@@ -4,10 +4,8 @@ import { constant, injectable } from "@tsed/di";
4
4
  import { $asyncAlter } from "@tsed/hooks";
5
5
  import { normalizePath } from "@tsed/normalize-path";
6
6
  import { globbySync } from "globby";
7
+ import { taskOutput } from "../fn/taskOutput.js";
7
8
  import { PlatformsModule } from "../platforms/PlatformsModule.js";
8
- import { InitExpressPlatform } from "../platforms/supports/InitExpressPlatform.js";
9
- import { InitFastifyPlatform } from "../platforms/supports/InitFastifyPlatform.js";
10
- import { InitKoaPlatform } from "../platforms/supports/InitKoaPlatform.js";
11
9
  import { transformBinFile } from "../processors/transformBinFile.js";
12
10
  import { transformConfigFile } from "../processors/transformConfigFile.js";
13
11
  import { transformIndexFile } from "../processors/transformIndexFile.js";
@@ -28,13 +26,10 @@ export class CliProjectService {
28
26
  getServerFileName() {
29
27
  return this.get().getSource("Server.ts") ? "Server" : "server";
30
28
  }
31
- get() {
32
- if (this.project) {
33
- return this.project;
34
- }
29
+ create() {
30
+ taskOutput("Create typescript project");
35
31
  const fs = inject(CliFs);
36
32
  this.project = new ProjectClient({
37
- // tsConfigFilePath: join(this.rootDir, "tsconfig.json"),
38
33
  rootDir: this.rootDir
39
34
  });
40
35
  const files = fs.globSync([join(constant("project.rootDir", process.cwd()), "**/*.ts")]);
@@ -43,6 +38,11 @@ export class CliProjectService {
43
38
  overwrite: true
44
39
  });
45
40
  });
41
+ }
42
+ get() {
43
+ if (!this.project) {
44
+ this.create();
45
+ }
46
46
  return this.project;
47
47
  }
48
48
  async transformFiles(data) {
@@ -60,11 +60,16 @@ export class CliProjectService {
60
60
  await $asyncAlter(`$alterProjectFiles`, project, [data]);
61
61
  await Promise.all(project.getSourceFiles().map((sourceFile) => {
62
62
  sourceFile.organizeImports();
63
+ sourceFile.formatText({
64
+ indentSize: 2
65
+ });
63
66
  return sourceFile.save();
64
67
  }));
65
68
  }
66
69
  async createFromTemplate(templateId, ctx) {
70
+ const startTime = Date.now();
67
71
  const obj = await this.templates.render(templateId, ctx);
72
+ taskOutput(`Template ${templateId} rendered in ${Date.now() - startTime}ms`);
68
73
  const project = this.get();
69
74
  if (obj) {
70
75
  const sourceFile = await project.createSource(obj.outputPath, obj.content, {
@@ -88,4 +93,4 @@ export class CliProjectService {
88
93
  return [...set];
89
94
  }
90
95
  }
91
- injectable(PlatformsModule).imports([InitExpressPlatform, InitKoaPlatform, InitFastifyPlatform]);
96
+ injectable(CliProjectService);
@@ -54,10 +54,11 @@ export class CliTemplatesService {
54
54
  else {
55
55
  const from = data.from || TEMPLATE_DIR;
56
56
  const fromPath = join(from, templateId.replace("{{srcDir}}", "src"));
57
- if (this.fs.fileExistsSync(fromPath)) {
57
+ if (await this.fs.fileExists(fromPath)) {
58
+ const content = await inject(CliFs).readFile(fromPath);
58
59
  return {
59
60
  templateId,
60
- content: await inject(CliFs).readFile(fromPath),
61
+ content,
61
62
  outputPath: templateId.replace("{{srcDir}}", constant("project.srcDir", ""))
62
63
  };
63
64
  }
@@ -1,4 +1,4 @@
1
- import { join } from "node:path";
1
+ import { dirname, join } from "node:path";
2
2
  import { CliDockerComposeYaml, CliFs, ProjectPackageJson } from "@tsed/cli-core";
3
3
  import { isString } from "@tsed/core";
4
4
  import { constant, inject } from "@tsed/di";
@@ -45,6 +45,7 @@ export class ProjectClient extends Project {
45
45
  await source.save();
46
46
  return source;
47
47
  }
48
+ await this.fs.ensureDir(dirname(path));
48
49
  await this.fs.writeFile(path, sourceFileText, { encoding: "utf-8" });
49
50
  }
50
51
  findClassDecorator(sourceFile, name) {
@@ -136,4 +137,26 @@ export class ProjectClient extends Project {
136
137
  }
137
138
  return undefined;
138
139
  }
140
+ addConfigSource(name, { content = name, moduleSpecifier }) {
141
+ const sourceFile = this.configSourceFile;
142
+ const options = this.findConfiguration("config");
143
+ if (!options) {
144
+ return;
145
+ }
146
+ const extendsConfig = this.getPropertyAssignment(options, {
147
+ name: "extends",
148
+ kind: SyntaxKind.ArrayLiteralExpression,
149
+ initializer: "[]"
150
+ });
151
+ const has = extendsConfig.getElements().some((expression) => {
152
+ return expression.getText().includes(name);
153
+ });
154
+ if (!has) {
155
+ sourceFile.addImportDeclaration({
156
+ moduleSpecifier,
157
+ namedImports: [{ name: name }]
158
+ });
159
+ extendsConfig.addElement("\n" + content);
160
+ }
161
+ }
139
162
  }