@mittwald/cli 1.2.2 → 1.2.6

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.
@@ -21,7 +21,9 @@ export class Set extends BaseCommand {
21
21
  };
22
22
  async run() {
23
23
  const { flags } = await this.parse(Set);
24
- const ctx = new Context(this.apiClient, this.config);
24
+ const ctx = new Context(this.apiClient, this.config, {
25
+ onInitError() { },
26
+ });
25
27
  if (flags["project-id"]) {
26
28
  const projectId = await ctx.setProjectId(flags["project-id"]);
27
29
  this.log(`Set project ID to ${projectId}`);
@@ -1,22 +1,23 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { formatDistanceToNow } from "date-fns";
3
3
  import { RenderBaseCommand } from "../../lib/basecommands/RenderBaseCommand.js";
4
4
  import { SingleResult, SingleResultTable, } from "../../rendering/react/components/SingleResult.js";
5
5
  import useOwnAccount from "../../lib/resources/login/useOwnAccount.js";
6
+ import { Text } from "ink";
6
7
  export default class Status extends RenderBaseCommand {
7
8
  static description = "Checks your current authentication status";
8
9
  render() {
9
10
  const account = useOwnAccount(this.apiClient);
10
11
  const rows = {};
11
12
  rows["User identification"] = (_jsx(SingleResultTable, { rows: {
12
- Id: account.userId,
13
- Email: account.email,
13
+ Id: _jsx(Text, { children: account.userId }),
14
+ Email: _jsx(Text, { children: account.email }),
14
15
  } }));
15
16
  if (account.person) {
16
- rows["Name"] = `${account.person.firstName} ${account.person.lastName}`;
17
+ rows["Name"] = (_jsxs(Text, { children: [account.person.firstName, " ", account.person.lastName] }));
17
18
  }
18
19
  if (account.passwordUpdatedAt) {
19
- rows["Last password change"] = `${formatDistanceToNow(new Date(account.passwordUpdatedAt))} ago`;
20
+ rows["Last password change"] = (_jsxs(Text, { children: [formatDistanceToNow(new Date(account.passwordUpdatedAt)), " ago"] }));
20
21
  }
21
22
  return _jsx(SingleResult, { title: "Login status", rows: rows });
22
23
  }
@@ -40,7 +40,7 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
40
40
  owner?: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CustomerContact | undefined;
41
41
  projectCount: number;
42
42
  vatId?: string | undefined;
43
- vatIdValidationState?: ("valid" | "invalid" | "pending" | "unspecified") | undefined;
43
+ vatIdValidationState?: "valid" | "invalid" | "pending" | "unspecified" | undefined;
44
44
  };
45
45
  avatarRefId?: string;
46
46
  customerId: string;
@@ -40,7 +40,7 @@ export declare class ListOwn extends ListBaseCommand<typeof ListOwn, ResponseIte
40
40
  owner?: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CustomerContact | undefined;
41
41
  projectCount: number;
42
42
  vatId?: string | undefined;
43
- vatIdValidationState?: ("valid" | "invalid" | "pending" | "unspecified") | undefined;
43
+ vatIdValidationState?: "valid" | "invalid" | "pending" | "unspecified" | undefined;
44
44
  };
45
45
  customerId: string;
46
46
  email: string;
@@ -27,10 +27,14 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
27
27
  protected mapData(data: SuccessfulResponse<Response, 200>["data"]): Promise<(MittwaldAPIV2.Components.Schemas.MembershipCustomerMembership | {
28
28
  user: {
29
29
  avatarRef?: string | undefined;
30
+ customerMemberships?: {
31
+ [k: string]: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.UserCustomerMembership;
32
+ } | undefined;
30
33
  email?: string | undefined;
31
34
  employeeInformation?: {
32
35
  department: string;
33
36
  } | undefined;
37
+ isEmployee?: boolean | undefined;
34
38
  mfa?: {
35
39
  active: boolean;
36
40
  setup: boolean;
@@ -38,6 +42,9 @@ export declare class List extends ListBaseCommand<typeof List, ResponseItem, Res
38
42
  passwordUpdatedAt?: string | undefined;
39
43
  person: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.CommonsPerson;
40
44
  phoneNumber?: string | undefined;
45
+ projectMemberships?: {
46
+ [k: string]: import("node_modules/@mittwald/api-client/dist/types/generated/v2/types.js").MittwaldAPIV2.Components.Schemas.UserProjectMembership;
47
+ } | undefined;
41
48
  registeredAt?: string | undefined;
42
49
  userId: string;
43
50
  };
@@ -16,11 +16,15 @@ export type ContextValue = {
16
16
  export declare const contextIDNormalizers: {
17
17
  [k in ContextKey]?: (apiClient: MittwaldAPIV2Client, id: string) => Promise<string>;
18
18
  };
19
+ export interface ContextOptions {
20
+ onInitError: (err: unknown) => void;
21
+ }
19
22
  export default class Context {
20
23
  private readonly contextData;
21
24
  private readonly apiClient;
25
+ private readonly opts;
22
26
  readonly providers: ContextProvider[];
23
- constructor(apiClient: MittwaldAPIV2Client, config: Config);
27
+ constructor(apiClient: MittwaldAPIV2Client, config: Config, opts?: Partial<ContextOptions>);
24
28
  private initializeContextData;
25
29
  reset(): Promise<void>;
26
30
  private persist;
@@ -1,6 +1,7 @@
1
1
  import UserContextProvider from "./UserContextProvider.js";
2
2
  import TerraformContextProvider from "./TerraformContextProvider.js";
3
3
  import DDEVContextProvider from "./DDEVContextProvider.js";
4
+ import InvalidContextError from "../error/InvalidContextError.js";
4
5
  function isWritable(p) {
5
6
  return "update" in p;
6
7
  }
@@ -8,21 +9,35 @@ export const contextIDNormalizers = {};
8
9
  export default class Context {
9
10
  contextData;
10
11
  apiClient;
12
+ opts;
11
13
  providers;
12
- constructor(apiClient, config) {
14
+ constructor(apiClient, config, opts = {}) {
13
15
  this.apiClient = apiClient;
14
16
  this.providers = [
15
17
  new UserContextProvider(config),
16
18
  new TerraformContextProvider(),
17
19
  new DDEVContextProvider(apiClient),
18
20
  ];
21
+ this.opts = {
22
+ onInitError(err) {
23
+ throw err;
24
+ },
25
+ ...opts,
26
+ };
19
27
  this.contextData = this.initializeContextData();
20
28
  }
21
29
  async initializeContextData() {
22
30
  const contextData = {};
23
31
  for (const provider of this.providers) {
24
- const overrides = await provider.getOverrides();
25
- Object.assign(contextData, overrides);
32
+ try {
33
+ const overrides = await provider.getOverrides();
34
+ Object.assign(contextData, overrides);
35
+ }
36
+ catch (err) {
37
+ if (err instanceof InvalidContextError) {
38
+ this.opts.onInitError(err);
39
+ }
40
+ }
26
41
  }
27
42
  return contextData;
28
43
  }
@@ -4,6 +4,7 @@ import fs from "fs/promises";
4
4
  import yaml from "js-yaml";
5
5
  import { assertStatus } from "@mittwald/api-client";
6
6
  import { pathExists } from "../util/fs/pathExists.js";
7
+ import InvalidContextError from "../error/InvalidContextError.js";
7
8
  /**
8
9
  * DDEVContextProvider is a ContextProvider that reads context overrides from
9
10
  * local DDEV configuration files; it looks for a .ddev directory in the current
@@ -44,20 +45,25 @@ export default class DDEVContextProvider {
44
45
  return overrides;
45
46
  }
46
47
  async fillOverridesFromAppInstallationId(source, appInstallationId) {
47
- const response = await this.apiClient.app.getAppinstallation({
48
- appInstallationId,
49
- });
50
- assertStatus(response, 200);
51
- const out = {
52
- "installation-id": { value: response.data.id, source },
53
- };
54
- if (response.data.projectId) {
55
- out["project-id"] = {
56
- value: response.data.projectId,
57
- source,
48
+ try {
49
+ const response = await this.apiClient.app.getAppinstallation({
50
+ appInstallationId,
51
+ });
52
+ assertStatus(response, 200);
53
+ const out = {
54
+ "installation-id": { value: response.data.id, source },
58
55
  };
56
+ if (response.data.projectId) {
57
+ out["project-id"] = {
58
+ value: response.data.projectId,
59
+ source,
60
+ };
61
+ }
62
+ return out;
63
+ }
64
+ catch (err) {
65
+ throw new InvalidContextError(`error while getting app installation ${appInstallationId}: ${err}`, { cause: err });
59
66
  }
60
- return out;
61
67
  }
62
68
  /**
63
69
  * Find the .ddev directory in the current working directory or any of its
@@ -0,0 +1,2 @@
1
+ export default class InvalidContextError extends Error {
2
+ }
@@ -0,0 +1,2 @@
1
+ export default class InvalidContextError extends Error {
2
+ }
@@ -159,7 +159,7 @@ export function provideSupportedFlags(requestedFlagNames, appName) {
159
159
  return flagsToReturn;
160
160
  }
161
161
  export async function autofillFlags(apiClient, process, necessaryFlags, flags, projectId, appName, defaults) {
162
- const ownUser = await apiClient.user.getOwnAccount();
162
+ const ownUser = await apiClient.user.getUser({ userId: "self" });
163
163
  assertStatus(ownUser, 200);
164
164
  // Version
165
165
  if (necessaryFlags.includes("version") && !flags.version) {
@@ -1,7 +1,7 @@
1
1
  import { assertStatus } from "@mittwald/api-client-commons";
2
2
  export async function getUser(apiClient, p) {
3
3
  return await p.runStep("fetching user", async () => {
4
- const r = await apiClient.user.getOwnAccount();
4
+ const r = await apiClient.user.getUser({ userId: "self" });
5
5
  assertStatus(r, 200);
6
6
  return r.data;
7
7
  });
@@ -2,6 +2,16 @@ import type { MittwaldAPIV2 } from "@mittwald/api-client";
2
2
  import { MittwaldAPIV2Client } from "@mittwald/api-client";
3
3
  import { ProcessRenderer } from "../../../../rendering/process/process.js";
4
4
  type DatabaseMySqlUser = MittwaldAPIV2.Components.Schemas.DatabaseMySqlUser;
5
+ /**
6
+ * Generates a random password that fulfills the requirements for [mysql
7
+ * passwords][mysql].
8
+ *
9
+ * [mysql]: https://developer.mittwald.de/docs/v2/api/security/passwords#mysql
10
+ *
11
+ * @param length Desired length in characters
12
+ * @returns A randomly generated password
13
+ */
14
+ export declare function generateRandomPassword(length?: number): string;
5
15
  /**
6
16
  * Runs a callback function with a temporary user for a database operation.
7
17
  *
@@ -1,6 +1,6 @@
1
1
  import { assertStatus } from "@mittwald/api-client";
2
- import { randomBytes } from "crypto";
3
2
  import assertSuccess from "../../../apiutil/assert_success.js";
3
+ import { randomInt } from "crypto";
4
4
  async function createTemporaryUser(apiClient, databaseId, password) {
5
5
  const createResponse = await apiClient.database.createMysqlUser({
6
6
  mysqlDatabaseId: databaseId,
@@ -22,8 +22,39 @@ async function retrieveTemporaryUser(apiClient, mysqlUserId) {
22
22
  assertStatus(userResponse, 200);
23
23
  return userResponse.data;
24
24
  }
25
- function generateRandomPassword() {
26
- return randomBytes(32).toString("base64");
25
+ /**
26
+ * Generates a random password that fulfills the requirements for [mysql
27
+ * passwords][mysql].
28
+ *
29
+ * [mysql]: https://developer.mittwald.de/docs/v2/api/security/passwords#mysql
30
+ *
31
+ * @param length Desired length in characters
32
+ * @returns A randomly generated password
33
+ */
34
+ export function generateRandomPassword(length = 32) {
35
+ // Character pools
36
+ const lowercase = "abcdefghijklmnopqrstuvwxyz";
37
+ const uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
38
+ const digits = "0123456789";
39
+ const specialChars = "#!~%^*_+-=?{}()<>|.,;";
40
+ const allChars = lowercase + uppercase + digits + specialChars;
41
+ // Ensure the password includes at least one of each required character type
42
+ const passwordArray = [
43
+ lowercase[randomInt(0, lowercase.length)],
44
+ uppercase[randomInt(0, uppercase.length)],
45
+ digits[randomInt(0, digits.length)],
46
+ specialChars[randomInt(0, specialChars.length)],
47
+ ];
48
+ // Fill the remaining characters randomly
49
+ for (let i = passwordArray.length; i < length; i++) {
50
+ passwordArray.push(allChars[randomInt(0, allChars.length)]);
51
+ }
52
+ // Shuffle the array to avoid predictable patterns
53
+ for (let i = passwordArray.length - 1; i > 0; i--) {
54
+ const j = randomInt(0, i + 1);
55
+ [passwordArray[i], passwordArray[j]] = [passwordArray[j], passwordArray[i]];
56
+ }
57
+ return passwordArray.join("");
27
58
  }
28
59
  /**
29
60
  * Runs a callback function with a temporary user for a database operation.
@@ -0,0 +1,40 @@
1
+ import { describe, it, expect } from "@jest/globals";
2
+ import { generateRandomPassword } from "./temp_user.js";
3
+ describe("generateRandomPassword", () => {
4
+ it("should generate a password of the specified length", () => {
5
+ const length = 12;
6
+ const password = generateRandomPassword(length);
7
+ expect(password.length).toBe(length);
8
+ });
9
+ it("should include at least one lowercase character", () => {
10
+ const password = generateRandomPassword(12);
11
+ expect(/[a-z]/.test(password)).toBe(true);
12
+ });
13
+ it("should include at least one uppercase character", () => {
14
+ const password = generateRandomPassword(12);
15
+ expect(/[A-Z]/.test(password)).toBe(true);
16
+ });
17
+ it("should include at least one digit", () => {
18
+ const password = generateRandomPassword(12);
19
+ expect(/[0-9]/.test(password)).toBe(true);
20
+ });
21
+ it("should include at least one special character", () => {
22
+ const specialChars = "#!~%^*_+-=?{}()<>|.,;";
23
+ const password = generateRandomPassword(12);
24
+ expect(new RegExp(`[${specialChars.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&")}]`).test(password)).toBe(true);
25
+ });
26
+ it("should generate passwords that are unique", () => {
27
+ const password1 = generateRandomPassword(12);
28
+ const password2 = generateRandomPassword(12);
29
+ expect(password1).not.toBe(password2);
30
+ });
31
+ it("should work for passwords with length greater than 8", () => {
32
+ const password = generateRandomPassword(20);
33
+ expect(password.length).toBe(20);
34
+ expect(/[a-z]/.test(password)).toBe(true);
35
+ expect(/[A-Z]/.test(password)).toBe(true);
36
+ expect(/[0-9]/.test(password)).toBe(true);
37
+ const specialChars = "#!~%^*_+-=?{}()<>|.,;";
38
+ expect(new RegExp(`[${specialChars.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&")}]`).test(password)).toBe(true);
39
+ });
40
+ });
@@ -1,11 +1,24 @@
1
1
  import { MittwaldAPIV2Client } from "@mittwald/api-client";
2
2
  export default function useOwnAccount(client: MittwaldAPIV2Client): {
3
+ avatarRef?: string | undefined;
4
+ customerMemberships?: {
5
+ [k: string]: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.UserCustomerMembership;
6
+ } | undefined;
3
7
  email?: string | undefined;
4
- mfaDetails?: {
5
- mfaConfirmed?: boolean;
6
- mfaInitialized?: boolean;
8
+ employeeInformation?: {
9
+ department: string;
10
+ } | undefined;
11
+ isEmployee?: boolean | undefined;
12
+ mfa?: {
13
+ active: boolean;
14
+ setup: boolean;
7
15
  } | undefined;
8
16
  passwordUpdatedAt?: string | undefined;
9
- person?: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.CommonsPerson | undefined;
10
- userId?: string | undefined;
17
+ person: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.CommonsPerson;
18
+ phoneNumber?: string | undefined;
19
+ projectMemberships?: {
20
+ [k: string]: import("@mittwald/api-client").MittwaldAPIV2.Components.Schemas.UserProjectMembership;
21
+ } | undefined;
22
+ registeredAt?: string | undefined;
23
+ userId: string;
11
24
  };
@@ -1,7 +1,7 @@
1
1
  import { assertStatus } from "@mittwald/api-client";
2
2
  import { usePromise } from "@mittwald/react-use-promise";
3
3
  export default function useOwnAccount(client) {
4
- const result = usePromise(() => client.user.getOwnAccount(), []);
4
+ const result = usePromise(() => client.user.getUser({ userId: "self" }), []);
5
5
  assertStatus(result, 200);
6
6
  return result.data;
7
7
  }
@@ -13,7 +13,7 @@ export async function getSSHConnectionForAppInstallation(client, appInstallation
13
13
  });
14
14
  assertStatus(projectResponse, 200);
15
15
  if (sshUser === undefined) {
16
- const userResponse = await client.user.getOwnAccount();
16
+ const userResponse = await client.user.getUser({ userId: "self" });
17
17
  assertStatus(userResponse, 200);
18
18
  sshUser = userResponse.data.email;
19
19
  }
@@ -3,7 +3,7 @@ export async function getSSHConnectionForProject(client, projectId, sshUser) {
3
3
  const projectResponse = await client.project.getProject({ projectId });
4
4
  assertStatus(projectResponse, 200);
5
5
  if (sshUser === undefined) {
6
- const userResponse = await client.user.getOwnAccount();
6
+ const userResponse = await client.user.getUser({ userId: "self" });
7
7
  assertStatus(userResponse, 200);
8
8
  sshUser = userResponse.data.email;
9
9
  }
@@ -28,7 +28,7 @@ export default class Duration {
28
28
  }
29
29
  static fromString(input) {
30
30
  const parsed = parseDuration(input);
31
- if (parsed === undefined) {
31
+ if (parsed === null) {
32
32
  throw new Error("could not parse duration: " + input);
33
33
  }
34
34
  return new Duration(parsed);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import Duration from "./Duration.js";
3
+ describe("Duration class", () => {
4
+ describe("Static factory methods", () => {
5
+ test("fromZero should return a Duration of 0 milliseconds", () => {
6
+ const duration = Duration.fromZero();
7
+ expect(duration.milliseconds).toBe(0);
8
+ });
9
+ test("fromMilliseconds should return a Duration with the given milliseconds", () => {
10
+ const duration = Duration.fromMilliseconds(500);
11
+ expect(duration.milliseconds).toBe(500);
12
+ });
13
+ test("fromSeconds should return a Duration with the given seconds converted to milliseconds", () => {
14
+ const duration = Duration.fromSeconds(2);
15
+ expect(duration.milliseconds).toBe(2000);
16
+ });
17
+ test("fromString should parse valid duration strings", () => {
18
+ const duration = Duration.fromString("2s");
19
+ expect(duration.milliseconds).toBe(2000);
20
+ });
21
+ test("fromString should throw an error for invalid input", () => {
22
+ expect(() => Duration.fromString("invalid")).toThrow("could not parse duration: invalid");
23
+ });
24
+ });
25
+ describe("Instance methods", () => {
26
+ test("seconds should return duration in seconds", () => {
27
+ const duration = Duration.fromMilliseconds(3000);
28
+ expect(duration.seconds).toBe(3);
29
+ });
30
+ test("from should return a Date object offset by the duration", () => {
31
+ const baseDate = new Date("2023-01-01T00:00:00Z");
32
+ const duration = Duration.fromSeconds(10);
33
+ expect(duration.from(baseDate)).toEqual(new Date("2023-01-01T00:00:10Z"));
34
+ });
35
+ test("fromNow should return a future Date object", () => {
36
+ const duration = Duration.fromSeconds(5);
37
+ const now = new Date();
38
+ const future = duration.fromNow();
39
+ expect(future.getTime()).toBeGreaterThan(now.getTime());
40
+ });
41
+ test("add should correctly add two durations", () => {
42
+ const duration1 = Duration.fromSeconds(10);
43
+ const duration2 = Duration.fromSeconds(5);
44
+ const result = duration1.add(duration2);
45
+ expect(result.milliseconds).toBe(15000);
46
+ });
47
+ test("compare should return correct comparison results", () => {
48
+ const duration1 = Duration.fromSeconds(10);
49
+ const duration2 = Duration.fromSeconds(5);
50
+ expect(duration1.compare(duration2)).toBeGreaterThan(0);
51
+ expect(duration2.compare(duration1)).toBeLessThan(0);
52
+ expect(duration1.compare(duration1)).toBe(0);
53
+ });
54
+ test("toString should return formatted duration string", () => {
55
+ expect(Duration.fromMilliseconds(500).toString()).toBe("500ms");
56
+ expect(Duration.fromSeconds(3).toString()).toBe("3s");
57
+ });
58
+ });
59
+ });
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from "react";
2
- interface OptionalValueProps<T> {
2
+ interface OptionalValueProps<T extends ReactNode> {
3
3
  value: T | undefined | null;
4
4
  render?: (value: T) => ReactNode;
5
5
  }
@@ -9,5 +9,5 @@ interface OptionalValueProps<T> {
9
9
  * @class
10
10
  * @param props
11
11
  */
12
- declare function OptionalValue<T>(props: OptionalValueProps<T>): import("react/jsx-runtime").JSX.Element;
12
+ declare function OptionalValue<T extends ReactNode>(props: OptionalValueProps<T>): import("react/jsx-runtime").JSX.Element;
13
13
  export default OptionalValue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mittwald/cli",
3
- "version": "1.2.2",
3
+ "version": "1.2.6",
4
4
  "description": "Hand-crafted CLI for the mittwald API",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -28,7 +28,7 @@
28
28
  "package": "yarn package:tarballs && yarn package:windows && yarn package:macos",
29
29
  "package:macos": "oclif pack macos",
30
30
  "package:tarballs": "oclif pack tarballs --targets=linux-x64,linux-arm64,darwin-x64,darwin-arm64",
31
- "package:windows": "oclif pack win",
31
+ "package:windows": "oclif pack win --targets=win32-x64,win32-x86",
32
32
  "post:generate": "yarn run -T compile && yarn run -T compile:cjs",
33
33
  "test": "yarn test:format && yarn test:licenses && yarn test:unit",
34
34
  "test:format": "yarn lint && yarn format --check",
@@ -59,7 +59,7 @@
59
59
  "marked": "^12.0.0",
60
60
  "marked-terminal": "^6.0.0",
61
61
  "open": "^10.0.3",
62
- "parse-duration": "^1.1.0",
62
+ "parse-duration": "^2.0.1",
63
63
  "pretty-bytes": "^6.1.0",
64
64
  "react": "^18.2.0",
65
65
  "semver": "^7.5.4",
@@ -68,7 +68,7 @@
68
68
  "slice-ansi": "^7.1.0",
69
69
  "string-width": "^7.2.0",
70
70
  "tempfile": "^5.0.0",
71
- "uuid": "^10.0.0"
71
+ "uuid": "^11.0.3"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@jest/globals": "^29.7.0",
@@ -76,24 +76,23 @@
76
76
  "@types/chalk": "^2.2.0",
77
77
  "@types/js-yaml": "^4.0.9",
78
78
  "@types/marked-terminal": "^3.1.3",
79
- "@types/node": "^20.3.3",
79
+ "@types/node": "^22.7.5",
80
80
  "@types/parse-duration": "^0.3.0",
81
81
  "@types/pretty-bytes": "^5.2.0",
82
82
  "@types/react": "^18",
83
83
  "@types/semver": "^7.5.0",
84
84
  "@types/shell-escape": "^0.2.3",
85
- "@types/uuid": "^10.0.0",
86
85
  "@typescript-eslint/eslint-plugin": "^8.3.0",
87
86
  "@typescript-eslint/parser": "^8.3.0",
88
87
  "@yarnpkg/pnpify": "^4.0.0-rc.48",
89
88
  "eslint": "^9.9.1",
90
- "eslint-config-prettier": "^9.1.0",
89
+ "eslint-config-prettier": "^10.0.1",
91
90
  "eslint-plugin-json": "^4.0.0",
92
91
  "eslint-plugin-prettier": "^5.1.3",
93
92
  "globals": "^15.9.0",
94
93
  "jest": "^29.7.0",
95
94
  "license-checker-rseidelsohn": "^4.2.6",
96
- "nock": "^13.5.4",
95
+ "nock": "^14.0.0",
97
96
  "oclif": "^4.14.31",
98
97
  "prettier": "^3.2.5",
99
98
  "prettier-plugin-jsdoc": "^1.3.0",