@tsed/cli 7.2.1 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import * as os from "node:os";
1
2
  import { basename, join } from "node:path";
2
3
  import { CliExeca, CliFs, CliLoadFile, cliPackageJson, CliPlugins, command, Configuration, inject, PackageManagersModule, ProjectPackageJson } from "@tsed/cli-core";
3
4
  import { tasks } from "@tsed/cli-tasks";
@@ -14,10 +15,12 @@ import { RuntimesModule } from "../../runtimes/RuntimesModule.js";
14
15
  import { BunRuntime } from "../../runtimes/supports/BunRuntime.js";
15
16
  import { NodeRuntime } from "../../runtimes/supports/NodeRuntime.js";
16
17
  import { CliProjectService } from "../../services/CliProjectService.js";
18
+ import { anonymizePaths } from "../../services/mappers/anonymizePaths.js";
17
19
  import { FeaturesMap, FeatureType } from "./config/FeaturesPrompt.js";
18
20
  import { InitSchema } from "./config/InitSchema.js";
19
21
  import { mapToContext } from "./mappers/mapToContext.js";
20
22
  import { getFeaturesPrompt } from "./prompts/getFeaturesPrompt.js";
23
+ const ISSUE_URL = "https://github.com/tsedio/tsed-cli/issues/new";
21
24
  export class InitCmd {
22
25
  constructor() {
23
26
  this.configuration = inject(Configuration);
@@ -320,6 +323,55 @@ export class InitCmd {
320
323
  await Promise.all(promises);
321
324
  taskOutput(`Plugins files rendered (${Date.now() - startTime}ms)`);
322
325
  }
326
+ $onFinish(data, er) {
327
+ if (data.commandName !== "init" || !er) {
328
+ return;
329
+ }
330
+ console.error(["", `Init failed. Please open an issue: ${ISSUE_URL}`, "", "Copy/paste this report:", this.buildIssueReport(data, er)].join("\n"));
331
+ }
332
+ buildIssueReport(data, er) {
333
+ const sanitizedMessage = anonymizePaths(er.message || "");
334
+ const sanitizedStack = anonymizePaths(er.stack || "");
335
+ const command = ["tsed", "init", ...(data.rawArgs || [])].join(" ");
336
+ const convention = this.packageJson.preferences.convention === "conv_default" ? "tsed" : "angular";
337
+ const style = this.packageJson.preferences.architecture === "arc_default" ? "tsed" : "feature";
338
+ const platform = this.packageJson.preferences.platform || "";
339
+ const packageManager = this.packageJson.preferences.packageManager || "";
340
+ const runtime = this.packageJson.preferences.runtime || "";
341
+ const tsedVersion = this.packageJson.dependencies["@tsed/platform-http"] || "";
342
+ const channel = "cli";
343
+ const cliVersion = this.cliPackageJson.version || "";
344
+ const osType = os.type();
345
+ const features = data.features || [];
346
+ return [
347
+ "## Environment",
348
+ `- @tsed/cli version: ${this.cliPackageJson.version}`,
349
+ `- Node.js version: ${process.version}`,
350
+ `- Platform: ${process.platform} ${process.arch}`,
351
+ `- Command: \`${command}\``,
352
+ "",
353
+ "## Init Stats",
354
+ `- tsed_version: ${tsedVersion}`,
355
+ `- platform: ${platform}`,
356
+ `- convention: ${convention}`,
357
+ `- style: ${style}`,
358
+ `- package_manager: ${packageManager}`,
359
+ `- runtime: ${runtime}`,
360
+ `- features: ${features.join(", ")}`,
361
+ `- channel: ${channel}`,
362
+ `- cli_version: ${cliVersion}`,
363
+ `- os: ${osType}`,
364
+ "",
365
+ "## Error",
366
+ `- Name: ${er.name || "Error"}`,
367
+ `- Message: ${sanitizedMessage || "(empty)"}`,
368
+ "",
369
+ "## Stack",
370
+ "```txt",
371
+ sanitizedStack || "(empty)",
372
+ "```"
373
+ ].join("\n");
374
+ }
323
375
  }
324
376
  command({
325
377
  token: InitCmd,
@@ -1,6 +1,7 @@
1
1
  import { CliHttpClient, ProjectPackageJson } from "@tsed/cli-core";
2
2
  import { constant, inject, injectable } from "@tsed/di";
3
3
  import * as os from "os";
4
+ import { anonymizePaths } from "./mappers/anonymizePaths.js";
4
5
  export class CliStats extends CliHttpClient {
5
6
  constructor() {
6
7
  super(...arguments);
@@ -16,7 +17,7 @@ export class CliStats extends CliHttpClient {
16
17
  ...opts,
17
18
  os: os.type(),
18
19
  convention: this.projectPackage.preferences.convention === "conv_default" ? "tsed" : "angular",
19
- style: this.projectPackage.preferences.architecture === "arc_default" ? "tsed" : "angular",
20
+ style: this.projectPackage.preferences.architecture === "arc_default" ? "tsed" : "feature",
20
21
  platform: this.projectPackage.preferences.platform,
21
22
  package_manager: this.projectPackage.preferences.packageManager,
22
23
  runtime: this.projectPackage.preferences.runtime,
@@ -33,11 +34,13 @@ export class CliStats extends CliHttpClient {
33
34
  }
34
35
  $onFinish(data, er) {
35
36
  if (data.commandName === "init") {
37
+ const sanitizedMessage = anonymizePaths(er?.message || "");
38
+ const sanitizedStack = anonymizePaths(er?.stack || "");
36
39
  return this.sendInit({
37
40
  channel: "cli",
38
41
  is_success: !er,
39
42
  error_name: er?.name || "",
40
- error_message: er?.message,
43
+ error_message: [sanitizedMessage, sanitizedStack].filter(Boolean).join(" "),
41
44
  features: data.features
42
45
  });
43
46
  }
@@ -0,0 +1,73 @@
1
+ import { homedir } from "node:os";
2
+ import { basename, relative, resolve } from "node:path";
3
+ const GLOBAL_MODULE_MARKER = "/node_modules/";
4
+ function toPosixPath(pathname) {
5
+ return pathname.replace(/\\/g, "/");
6
+ }
7
+ function normalizePath(pathname) {
8
+ return toPosixPath(resolve(pathname));
9
+ }
10
+ function isSubPath(pathname, parentPath) {
11
+ return pathname === parentPath || pathname.startsWith(`${parentPath}/`);
12
+ }
13
+ function splitPathAndLocation(pathname) {
14
+ const match = pathname.match(/^(.*?)(:\d+(?::\d+)?)?$/);
15
+ return {
16
+ path: match?.[1] || pathname,
17
+ location: match?.[2] || ""
18
+ };
19
+ }
20
+ function getGlobalModulePath(absolutePath, absolutePathForCompare) {
21
+ const index = absolutePathForCompare.indexOf(GLOBAL_MODULE_MARKER);
22
+ if (index === -1) {
23
+ return;
24
+ }
25
+ const pathAfterNodeModules = absolutePath.slice(index + GLOBAL_MODULE_MARKER.length);
26
+ const segments = pathAfterNodeModules.split("/").filter(Boolean);
27
+ if (!segments.length) {
28
+ return;
29
+ }
30
+ const isScoped = segments[0].startsWith("@");
31
+ const moduleName = isScoped ? segments.slice(0, 2).join("/") : segments[0];
32
+ if (!moduleName || (isScoped && segments.length < 2)) {
33
+ return;
34
+ }
35
+ const suffixSegments = segments.slice(isScoped ? 2 : 1);
36
+ const suffix = suffixSegments.length ? `/${suffixSegments.join("/")}` : "";
37
+ return `<global>/${moduleName}${suffix}`;
38
+ }
39
+ function anonymizeAbsolutePath(pathname, cwd, home) {
40
+ const absolutePath = normalizePath(pathname);
41
+ const absolutePathForCompare = absolutePath.toLowerCase();
42
+ const cwdForCompare = cwd.toLowerCase();
43
+ const homeForCompare = home.toLowerCase();
44
+ if (isSubPath(absolutePathForCompare, cwdForCompare)) {
45
+ const relativePath = toPosixPath(relative(cwd, absolutePath));
46
+ return relativePath ? `<cwd>/${relativePath}` : "<cwd>";
47
+ }
48
+ const globalModulePath = getGlobalModulePath(absolutePath, absolutePathForCompare);
49
+ if (globalModulePath) {
50
+ return globalModulePath;
51
+ }
52
+ if (isSubPath(absolutePathForCompare, homeForCompare)) {
53
+ return `~/${toPosixPath(relative(home, absolutePath))}`;
54
+ }
55
+ return `<external>/${basename(absolutePath)}`;
56
+ }
57
+ function anonymizeTokenWithContext(token, cwd, home) {
58
+ const { path, location } = splitPathAndLocation(token);
59
+ return `${anonymizeAbsolutePath(path, cwd, home)}${location}`;
60
+ }
61
+ function toPathFromFileUrl(token) {
62
+ const decodedPath = decodeURIComponent(token.replace(/^file:\/\//, ""));
63
+ return decodedPath.replace(/^\/([A-Za-z]:[\\/])/, "$1");
64
+ }
65
+ export function anonymizePaths(stack, options = {}) {
66
+ const cwd = normalizePath(options.cwd || process.cwd());
67
+ const home = normalizePath(options.home || homedir());
68
+ return stack
69
+ .replace(/file:\/\/\/[^\s)]+/g, (token) => anonymizeTokenWithContext(toPathFromFileUrl(token), cwd, home))
70
+ .replace(/(^|[\s(])((?:[A-Za-z]:\\|\/)[^\s)]+)/g, (_full, prefix, token) => {
71
+ return `${prefix}${anonymizeTokenWithContext(token, cwd, home)}`;
72
+ });
73
+ }