@pagopa/dx-cli 0.18.3 → 0.18.4

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.
@@ -0,0 +1,25 @@
1
+ // Tests for workspaceSchema transforms (lowercase and trim on domain).
2
+ import { describe, expect, it } from "vitest";
3
+ import { workspaceSchema } from "../prompts.js";
4
+ describe("workspaceSchema — domain transforms", () => {
5
+ it("lowercases an uppercase domain", () => {
6
+ const result = workspaceSchema.safeParse({ domain: "API" });
7
+ expect(result.success).toBe(true);
8
+ expect(result.success && result.data.domain).toBe("api");
9
+ });
10
+ it("lowercases a mixed-case domain", () => {
11
+ const result = workspaceSchema.safeParse({ domain: "MyDomain" });
12
+ expect(result.success).toBe(true);
13
+ expect(result.success && result.data.domain).toBe("mydomain");
14
+ });
15
+ it("trims leading and trailing whitespace from domain", () => {
16
+ const result = workspaceSchema.safeParse({ domain: " aPi " });
17
+ expect(result.success).toBe(true);
18
+ expect(result.success && result.data.domain).toBe("api");
19
+ });
20
+ it("defaults domain to empty string when not provided", () => {
21
+ const result = workspaceSchema.safeParse({});
22
+ expect(result.success).toBe(true);
23
+ expect(result.success && result.data.domain).toBe("");
24
+ });
25
+ });
@@ -7,6 +7,7 @@ import { z } from "zod/v4";
7
7
  import { githubRepoSchema, } from "../../../../domain/github-repo.js";
8
8
  import { githubAppCredentialsSchema } from "../../../../domain/github.js";
9
9
  import { getGithubRepo } from "../../../github/github-repo.js";
10
+ import { validatePrompt } from "../../helpers/validate-prompt.js";
10
11
  const initSchema = z.object({
11
12
  cloudAccountsToInitialize: z.array(cloudAccountSchema),
12
13
  runnerAppCredentials: githubAppCredentialsSchema.optional(),
@@ -18,7 +19,7 @@ const initSchema = z.object({
18
19
  });
19
20
  const tagsSchema = z.record(z.string(), z.string().min(1));
20
21
  export const workspaceSchema = z.object({
21
- domain: z.string().default(""),
22
+ domain: z.string().trim().toLowerCase().default(""),
22
23
  });
23
24
  export const payloadSchema = z.object({
24
25
  env: environmentSchema,
@@ -66,18 +67,16 @@ const prompts = (deps) => async (inquirer) => {
66
67
  : "Please select a cloud account.",
67
68
  },
68
69
  {
70
+ filter: (value) => value.trim().toLowerCase(),
69
71
  message: "Prefix (2-4 characters)",
70
72
  name: "env.prefix",
71
- transformer: (value) => value.trim().toLowerCase(),
72
73
  type: "input",
73
- validate: (value) => value.length >= 2 && value.length <= 4
74
- ? true
75
- : "Please enter a valid prefix.",
74
+ validate: validatePrompt(environmentSchema.shape.prefix),
76
75
  },
77
76
  {
77
+ filter: (value) => value.trim().toLowerCase(),
78
78
  message: "Domain (optional)",
79
79
  name: "workspace.domain",
80
- transformer: (value) => value.trim().toLowerCase(),
81
80
  type: "input",
82
81
  },
83
82
  {
@@ -1,16 +1,10 @@
1
1
  import { z } from "zod/v4";
2
+ import { validatePrompt } from "../../helpers/validate-prompt.js";
2
3
  export const payloadSchema = z.object({
3
4
  repoDescription: z.string().optional(),
4
5
  repoName: z.string().trim().min(1, "Repository name cannot be empty"),
5
6
  repoOwner: z.string().trim().default("pagopa"),
6
7
  });
7
- const validatePrompt = (schema) => (input) => {
8
- const error = schema.safeParse(input).error;
9
- return error
10
- ? // Return the error message defined in the Zod schema
11
- z.prettifyError(error)
12
- : true;
13
- };
14
8
  const getPrompts = () => [
15
9
  {
16
10
  message: "Name",
@@ -0,0 +1,60 @@
1
+ // Tests for the shared validatePrompt helper.
2
+ import { describe, expect, it } from "vitest";
3
+ import { z } from "zod/v4";
4
+ import { validatePrompt } from "../validate-prompt.js";
5
+ describe("validatePrompt", () => {
6
+ it("returns true when input is valid", () => {
7
+ const schema = z.string().min(1, "Must not be empty");
8
+ const validate = validatePrompt(schema);
9
+ const result = validate("hello");
10
+ expect(result).toBe(true);
11
+ });
12
+ it("returns a string error message when input is invalid", () => {
13
+ const schema = z.string().min(1, "Must not be empty");
14
+ const validate = validatePrompt(schema);
15
+ const result = validate("");
16
+ expect(typeof result).toBe("string");
17
+ expect(result).not.toBe(true);
18
+ });
19
+ it("returns the pretty-printed zod error message", () => {
20
+ const schema = z.string().min(3, "Too short");
21
+ const validate = validatePrompt(schema);
22
+ const result = validate("ab");
23
+ expect(result).toContain("Too short");
24
+ });
25
+ it("returns true for input that passes a min/max length schema", () => {
26
+ const schema = z.string().min(2).max(4);
27
+ const validate = validatePrompt(schema);
28
+ expect(validate("ab")).toBe(true);
29
+ expect(validate("abc")).toBe(true);
30
+ expect(validate("abcd")).toBe(true);
31
+ });
32
+ it("returns error string when input is too short for min/max length schema", () => {
33
+ const schema = z.string().min(2).max(4);
34
+ const validate = validatePrompt(schema);
35
+ const result = validate("a");
36
+ expect(typeof result).toBe("string");
37
+ });
38
+ it("returns error string when input is too long for min/max length schema", () => {
39
+ const schema = z.string().min(2).max(4);
40
+ const validate = validatePrompt(schema);
41
+ const result = validate("abcde");
42
+ expect(typeof result).toBe("string");
43
+ });
44
+ it("validates input after schema transforms are applied", () => {
45
+ // Schema with trim+toLowerCase then min length check
46
+ const schema = z.string().trim().toLowerCase().min(2);
47
+ const validate = validatePrompt(schema);
48
+ // " A " after trim is "A", after toLowerCase is "a" → length 1 → fails min(2)
49
+ const result = validate(" A ");
50
+ expect(typeof result).toBe("string");
51
+ });
52
+ it("works with a non-string schema", () => {
53
+ const schema = z.array(z.string()).min(1, "Select at least one item");
54
+ const validate = validatePrompt(schema);
55
+ expect(validate(["item1"])).toBe(true);
56
+ const result = validate([]);
57
+ expect(typeof result).toBe("string");
58
+ expect(result).toContain("Select at least one item");
59
+ });
60
+ });
@@ -0,0 +1,7 @@
1
+ import { z } from "zod/v4";
2
+ /**
3
+ * Creates an Inquirer-compatible validate function from a Zod schema.
4
+ * Applies any transforms defined in the schema before validating,
5
+ * so the same rules are enforced at both the prompt and domain levels.
6
+ */
7
+ export declare const validatePrompt: (schema: z.ZodSchema) => (input: unknown) => string | true;
@@ -0,0 +1,13 @@
1
+ // Shared prompt validation helper for plop generators.
2
+ // Bridges Zod schema validation with Inquirer's validate callback signature,
3
+ // returning a human-readable error string on failure or true on success.
4
+ import { z } from "zod/v4";
5
+ /**
6
+ * Creates an Inquirer-compatible validate function from a Zod schema.
7
+ * Applies any transforms defined in the schema before validating,
8
+ * so the same rules are enforced at both the prompt and domain levels.
9
+ */
10
+ export const validatePrompt = (schema) => (input) => {
11
+ const error = schema.safeParse(input).error;
12
+ return error ? z.prettifyError(error) : true;
13
+ };
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
- import { getInitializationStatus, getTerraformBackend, hasUserPermissionToInitialize, } from "../environment.js";
2
+ import { environmentSchema, getInitializationStatus, getTerraformBackend, hasUserPermissionToInitialize, } from "../environment.js";
3
3
  const createMockCloudAccountService = (overrides = {}) => ({
4
4
  getTerraformBackend: vi.fn().mockResolvedValue(undefined),
5
5
  hasUserPermissionToInitialize: vi.fn().mockResolvedValue(true),
@@ -280,3 +280,53 @@ describe("hasUserPermissionToInitialize", () => {
280
280
  expect(hasUserPermissionMock).toHaveBeenNthCalledWith(2, "account-b");
281
281
  });
282
282
  });
283
+ describe("environmentSchema — prefix transforms", () => {
284
+ const baseEnv = {
285
+ cloudAccounts: [
286
+ {
287
+ csp: "azure",
288
+ defaultLocation: "westeurope",
289
+ displayName: "Test Account",
290
+ id: "test-account-id",
291
+ },
292
+ ],
293
+ name: "dev",
294
+ prefix: "dx",
295
+ };
296
+ it("lowercases an uppercase prefix", () => {
297
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: "ABC" });
298
+ expect(result.success).toBe(true);
299
+ expect(result.success && result.data.prefix).toBe("abc");
300
+ });
301
+ it("lowercases a mixed-case prefix", () => {
302
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: "AbCd" });
303
+ expect(result.success).toBe(true);
304
+ expect(result.success && result.data.prefix).toBe("abcd");
305
+ });
306
+ it("trims leading and trailing whitespace from prefix", () => {
307
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: " dx " });
308
+ expect(result.success).toBe(true);
309
+ expect(result.success && result.data.prefix).toBe("dx");
310
+ });
311
+ it("trims and lowercases prefix together", () => {
312
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: " DX " });
313
+ expect(result.success).toBe(true);
314
+ expect(result.success && result.data.prefix).toBe("dx");
315
+ });
316
+ it("still rejects a prefix shorter than 2 characters after trim", () => {
317
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: " a " });
318
+ expect(result.success).toBe(false);
319
+ });
320
+ it("still rejects a prefix longer than 4 characters after trim", () => {
321
+ const result = environmentSchema.safeParse({
322
+ ...baseEnv,
323
+ prefix: "abcde",
324
+ });
325
+ expect(result.success).toBe(false);
326
+ });
327
+ it("accepts a valid lowercase prefix unchanged", () => {
328
+ const result = environmentSchema.safeParse({ ...baseEnv, prefix: "dx" });
329
+ expect(result.success).toBe(true);
330
+ expect(result.success && result.data.prefix).toBe("dx");
331
+ });
332
+ });
@@ -8,7 +8,14 @@ export const environmentShort = {
8
8
  export const environmentSchema = z.object({
9
9
  cloudAccounts: z.array(cloudAccountSchema).min(1),
10
10
  name: z.enum(["dev", "prod", "uat"]),
11
- prefix: z.string().min(2).max(4),
11
+ // Trim and lowercase at the schema level so the constraint is enforced
12
+ // regardless of how the data enters the system (prompt, test, or API).
13
+ prefix: z
14
+ .string()
15
+ .trim()
16
+ .toLowerCase()
17
+ .min(2, "Prefix must have at least 2 chars")
18
+ .max(4, "Prefix must have at most 4 chars"),
12
19
  });
13
20
  export async function getInitializationStatus(cloudAccountService, environment) {
14
21
  const issues = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-cli",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "type": "module",
5
5
  "description": "A CLI useful to manage DX tools.",
6
6
  "repository": {
@@ -33,7 +33,6 @@
33
33
  "@azure/storage-blob": "^12.29.1",
34
34
  "@logtape/logtape": "^1.3.4",
35
35
  "@microsoft/microsoft-graph-client": "^3.0.7",
36
- "@pagopa/dx-savemoney": "0.1.4",
37
36
  "chalk": "^5.6.2",
38
37
  "commander": "^14.0.2",
39
38
  "core-js": "^3.47.0",
@@ -47,7 +46,8 @@
47
46
  "replace-in-file": "^8.4.0",
48
47
  "semver": "^7.7.2",
49
48
  "yaml": "^2.8.2",
50
- "zod": "^4.2.1"
49
+ "zod": "^4.2.1",
50
+ "@pagopa/dx-savemoney": "^0.1.5"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@tsconfig/node24": "24.0.0",
@@ -18,7 +18,7 @@ locals {
18
18
 
19
19
  tags = {
20
20
  CreatedBy = "Terraform"
21
- Environment = "{{env.name}}"
21
+ Environment = "{{titleCase env.name}}"
22
22
  {{#each tags}}
23
23
  {{@key}} = "{{this}}"
24
24
  {{/each}}