@pagopa/dx-cli 0.15.2 → 0.15.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.
Files changed (195) hide show
  1. package/bin/index.js +8 -1280
  2. package/dist/adapters/azure/__tests__/cloud-account-repository.test.d.ts +1 -0
  3. package/dist/adapters/azure/__tests__/cloud-account-repository.test.js +95 -0
  4. package/dist/adapters/azure/__tests__/cloud-account-service.test.d.ts +1 -0
  5. package/dist/adapters/azure/__tests__/cloud-account-service.test.js +95 -0
  6. package/dist/adapters/azure/cloud-account-repository.d.ts +12 -0
  7. package/dist/adapters/azure/cloud-account-repository.js +23 -0
  8. package/dist/adapters/azure/cloud-account-service.d.ts +22 -0
  9. package/dist/adapters/azure/cloud-account-service.js +255 -0
  10. package/dist/adapters/azure/locations.d.ts +7 -0
  11. package/dist/adapters/azure/locations.js +21 -0
  12. package/dist/adapters/codemods/__tests__/registry.test.d.ts +1 -0
  13. package/dist/adapters/codemods/__tests__/registry.test.js +59 -0
  14. package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.d.ts +1 -0
  15. package/dist/adapters/codemods/__tests__/use-azure-appsvc.test.js +77 -0
  16. package/dist/adapters/codemods/__tests__/use-pnpm.test.d.ts +1 -0
  17. package/dist/adapters/codemods/__tests__/use-pnpm.test.js +148 -0
  18. package/dist/adapters/codemods/git.d.ts +2 -0
  19. package/dist/adapters/codemods/git.js +18 -0
  20. package/dist/adapters/codemods/index.d.ts +3 -0
  21. package/dist/adapters/codemods/index.js +9 -0
  22. package/dist/adapters/codemods/registry.d.ts +8 -0
  23. package/dist/adapters/codemods/registry.js +16 -0
  24. package/dist/adapters/codemods/update-code-review.d.ts +3 -0
  25. package/dist/adapters/codemods/update-code-review.js +60 -0
  26. package/dist/adapters/codemods/use-azure-appsvc.d.ts +3 -0
  27. package/dist/adapters/codemods/use-azure-appsvc.js +84 -0
  28. package/dist/adapters/codemods/use-pnpm.d.ts +22 -0
  29. package/dist/adapters/codemods/use-pnpm.js +214 -0
  30. package/dist/adapters/codemods/yaml.d.ts +2 -0
  31. package/dist/adapters/codemods/yaml.js +8 -0
  32. package/dist/adapters/commander/commands/codemod.d.ts +8 -0
  33. package/dist/adapters/commander/commands/codemod.js +22 -0
  34. package/dist/adapters/commander/commands/doctor.d.ts +4 -0
  35. package/dist/adapters/commander/commands/doctor.js +12 -0
  36. package/dist/adapters/commander/commands/info.d.ts +3 -0
  37. package/dist/adapters/commander/commands/info.js +9 -0
  38. package/dist/adapters/commander/commands/init.d.ts +7 -0
  39. package/dist/adapters/commander/commands/init.js +126 -0
  40. package/dist/adapters/commander/commands/savemoney.d.ts +2 -0
  41. package/dist/adapters/commander/commands/savemoney.js +26 -0
  42. package/dist/adapters/commander/index.d.ts +7 -0
  43. package/dist/adapters/commander/index.js +19 -0
  44. package/dist/adapters/execa/terraform.d.ts +27 -0
  45. package/dist/adapters/execa/terraform.js +28 -0
  46. package/dist/adapters/github/__tests__/github-repo.spec.d.ts +1 -0
  47. package/dist/adapters/github/__tests__/github-repo.spec.js +67 -0
  48. package/dist/adapters/github/github-repo.d.ts +2 -0
  49. package/dist/adapters/github/github-repo.js +31 -0
  50. package/dist/adapters/logtape/validation-reporter.d.ts +2 -0
  51. package/dist/adapters/logtape/validation-reporter.js +14 -0
  52. package/dist/adapters/node/__tests__/data.d.ts +18 -0
  53. package/dist/adapters/node/__tests__/data.js +22 -0
  54. package/dist/adapters/node/__tests__/package-json.test.d.ts +1 -0
  55. package/dist/adapters/node/__tests__/package-json.test.js +86 -0
  56. package/dist/adapters/node/__tests__/repository.test.d.ts +1 -0
  57. package/dist/adapters/node/__tests__/repository.test.js +77 -0
  58. package/dist/adapters/node/fs/__tests__/file-reader.test.d.ts +1 -0
  59. package/dist/adapters/node/fs/__tests__/file-reader.test.js +80 -0
  60. package/dist/adapters/node/fs/file-reader.d.ts +24 -0
  61. package/dist/adapters/node/fs/file-reader.js +26 -0
  62. package/dist/adapters/node/json/__tests__/index.test.d.ts +1 -0
  63. package/dist/adapters/node/json/__tests__/index.test.js +14 -0
  64. package/dist/adapters/node/json/index.d.ts +2 -0
  65. package/dist/adapters/node/json/index.js +2 -0
  66. package/dist/adapters/node/package-json.d.ts +2 -0
  67. package/dist/adapters/node/package-json.js +22 -0
  68. package/dist/adapters/node/release.d.ts +2 -0
  69. package/dist/adapters/node/release.js +33 -0
  70. package/dist/adapters/node/repository.d.ts +2 -0
  71. package/dist/adapters/node/repository.js +47 -0
  72. package/dist/adapters/octokit/__tests__/index.test.d.ts +1 -0
  73. package/dist/adapters/octokit/__tests__/index.test.js +197 -0
  74. package/dist/adapters/octokit/index.d.ts +24 -0
  75. package/dist/adapters/octokit/index.js +65 -0
  76. package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.d.ts +1 -0
  77. package/dist/adapters/plop/actions/__tests__/init-cloud-accounts.test.js +115 -0
  78. package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.d.ts +1 -0
  79. package/dist/adapters/plop/actions/__tests__/provision-terraform-backend.test.js +116 -0
  80. package/dist/adapters/plop/actions/fetch-github-release.d.ts +12 -0
  81. package/dist/adapters/plop/actions/fetch-github-release.js +20 -0
  82. package/dist/adapters/plop/actions/get-node-version.d.ts +2 -0
  83. package/dist/adapters/plop/actions/get-node-version.js +9 -0
  84. package/dist/adapters/plop/actions/get-terraform-backend.d.ts +3 -0
  85. package/dist/adapters/plop/actions/get-terraform-backend.js +17 -0
  86. package/dist/adapters/plop/actions/init-cloud-accounts.d.ts +5 -0
  87. package/dist/adapters/plop/actions/init-cloud-accounts.js +13 -0
  88. package/dist/adapters/plop/actions/provision-terraform-backend.d.ts +10 -0
  89. package/dist/adapters/plop/actions/provision-terraform-backend.js +16 -0
  90. package/dist/adapters/plop/actions/semver.d.ts +19 -0
  91. package/dist/adapters/plop/actions/semver.js +27 -0
  92. package/dist/adapters/plop/actions/setup-pnpm.d.ts +2 -0
  93. package/dist/adapters/plop/actions/setup-pnpm.js +26 -0
  94. package/dist/adapters/plop/generators/environment/__tests__/actions.test.d.ts +2 -0
  95. package/dist/adapters/plop/generators/environment/__tests__/actions.test.js +55 -0
  96. package/dist/adapters/plop/generators/environment/actions.d.ts +2 -0
  97. package/dist/adapters/plop/generators/environment/actions.js +54 -0
  98. package/dist/adapters/plop/generators/environment/index.d.ts +3 -0
  99. package/dist/adapters/plop/generators/environment/index.js +19 -0
  100. package/dist/adapters/plop/generators/environment/prompts.d.ts +66 -0
  101. package/dist/adapters/plop/generators/environment/prompts.js +166 -0
  102. package/dist/adapters/plop/generators/monorepo/actions.d.ts +51 -0
  103. package/dist/adapters/plop/generators/monorepo/actions.js +35 -0
  104. package/dist/adapters/plop/generators/monorepo/index.d.ts +6 -0
  105. package/dist/adapters/plop/generators/monorepo/index.js +17 -0
  106. package/dist/adapters/plop/generators/monorepo/prompts.d.ts +10 -0
  107. package/dist/adapters/plop/generators/monorepo/prompts.js +31 -0
  108. package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.d.ts +1 -0
  109. package/dist/adapters/plop/helpers/__tests__/resource-prefix.test.js +113 -0
  110. package/dist/adapters/plop/helpers/env-short.d.ts +3 -0
  111. package/dist/adapters/plop/helpers/env-short.js +9 -0
  112. package/dist/adapters/plop/helpers/resource-prefix.d.ts +5 -0
  113. package/dist/adapters/plop/helpers/resource-prefix.js +18 -0
  114. package/dist/adapters/plop/index.d.ts +8 -0
  115. package/dist/adapters/plop/index.js +24 -0
  116. package/dist/adapters/terraform/fmt.d.ts +1 -0
  117. package/dist/adapters/terraform/fmt.js +17 -0
  118. package/dist/adapters/yaml/__tests__/index.test.d.ts +1 -0
  119. package/dist/adapters/yaml/__tests__/index.test.js +53 -0
  120. package/dist/adapters/yaml/index.d.ts +8 -0
  121. package/dist/adapters/yaml/index.js +9 -0
  122. package/dist/adapters/zod/index.d.ts +23 -0
  123. package/dist/adapters/zod/index.js +22 -0
  124. package/dist/config.d.ts +6 -0
  125. package/dist/config.js +5 -0
  126. package/dist/domain/__tests__/data.d.ts +17 -0
  127. package/dist/domain/__tests__/data.js +27 -0
  128. package/dist/domain/__tests__/environment.test.d.ts +1 -0
  129. package/dist/domain/__tests__/environment.test.js +282 -0
  130. package/dist/domain/__tests__/info.test.d.ts +1 -0
  131. package/dist/domain/__tests__/info.test.js +77 -0
  132. package/dist/domain/__tests__/package-json.test.d.ts +1 -0
  133. package/dist/domain/__tests__/package-json.test.js +39 -0
  134. package/dist/domain/__tests__/repository.test.d.ts +1 -0
  135. package/dist/domain/__tests__/repository.test.js +101 -0
  136. package/dist/domain/__tests__/workspace.test.d.ts +1 -0
  137. package/dist/domain/__tests__/workspace.test.js +57 -0
  138. package/dist/domain/cloud-account.d.ts +28 -0
  139. package/dist/domain/cloud-account.js +12 -0
  140. package/dist/domain/codemod.d.ts +11 -0
  141. package/dist/domain/codemod.js +1 -0
  142. package/dist/domain/dependencies.d.ts +10 -0
  143. package/dist/domain/dependencies.js +1 -0
  144. package/dist/domain/doctor.d.ts +10 -0
  145. package/dist/domain/doctor.js +50 -0
  146. package/dist/domain/environment.d.ts +40 -0
  147. package/dist/domain/environment.js +57 -0
  148. package/dist/domain/github-repo.d.ts +6 -0
  149. package/dist/domain/github-repo.js +8 -0
  150. package/dist/domain/github.d.ts +37 -0
  151. package/dist/domain/github.js +29 -0
  152. package/dist/domain/info.d.ts +11 -0
  153. package/dist/domain/info.js +52 -0
  154. package/dist/domain/package-json.d.ts +42 -0
  155. package/dist/domain/package-json.js +69 -0
  156. package/dist/domain/remote-backend.d.ts +8 -0
  157. package/dist/domain/remote-backend.js +9 -0
  158. package/dist/domain/repository.d.ts +13 -0
  159. package/dist/domain/repository.js +72 -0
  160. package/dist/domain/validation.d.ts +16 -0
  161. package/dist/domain/validation.js +1 -0
  162. package/dist/domain/workspace.d.ts +9 -0
  163. package/dist/domain/workspace.js +32 -0
  164. package/dist/index.d.ts +2 -0
  165. package/dist/index.js +35 -0
  166. package/dist/use-cases/__tests__/apply-codemod.test.d.ts +1 -0
  167. package/dist/use-cases/__tests__/apply-codemod.test.js +73 -0
  168. package/dist/use-cases/__tests__/list-codemods.test.d.ts +1 -0
  169. package/dist/use-cases/__tests__/list-codemods.test.js +38 -0
  170. package/dist/use-cases/apply-codemod.d.ts +5 -0
  171. package/dist/use-cases/apply-codemod.js +14 -0
  172. package/dist/use-cases/list-codemods.d.ts +4 -0
  173. package/dist/use-cases/list-codemods.js +1 -0
  174. package/package.json +18 -7
  175. package/templates/environment/bootstrapper/{{env.name}}/data.tf.hbs +13 -0
  176. package/templates/environment/bootstrapper/{{env.name}}/main.tf.hbs +70 -0
  177. package/templates/environment/bootstrapper/{{env.name}}/providers.tf.hbs +26 -0
  178. package/templates/environment/core/{{env.name}}/main.tf.hbs +21 -0
  179. package/templates/environment/core/{{env.name}}/outputs.tf.hbs +8 -0
  180. package/templates/environment/core/{{env.name}}/providers.tf.hbs +21 -0
  181. package/templates/environment/shared/backend.tf.hbs +14 -0
  182. package/templates/environment/shared/locals.tf.hbs +26 -0
  183. package/templates/monorepo/.editorconfig +8 -0
  184. package/templates/monorepo/.node-version.hbs +1 -0
  185. package/templates/monorepo/.pre-commit-config.yaml.hbs +34 -0
  186. package/templates/monorepo/.prettierignore +5 -0
  187. package/templates/monorepo/.terraform-version.hbs +1 -0
  188. package/templates/monorepo/.trivyignore +16 -0
  189. package/templates/monorepo/README.md.hbs +163 -0
  190. package/templates/monorepo/infra/repository/main.tf.hbs +11 -0
  191. package/templates/monorepo/infra/repository/outputs.tf.hbs +11 -0
  192. package/templates/monorepo/infra/repository/providers.tf.hbs +13 -0
  193. package/templates/monorepo/package.json.hbs +7 -0
  194. package/templates/monorepo/pnpm-workspace.yaml +7 -0
  195. package/templates/monorepo/turbo.json +29 -0
@@ -0,0 +1,27 @@
1
+ import { errAsync, okAsync } from "neverthrow";
2
+ /**
3
+ * Fetches the latest semantic version using the provided fetch function and writes
4
+ * a formatted version string into the given `answers` object under `answerKey`.
5
+ *
6
+ * @param fetchSemverFn - A zero-arg function that returns a `ResultAsync` resolving
7
+ * to a `SemVer` (or `null`) or rejecting with an `Error`. Typically wraps an
8
+ * Octokit call to fetch a release or tag and parse its semver.
9
+ * @param answers - Mutable answers object (plop prompts) where the resulting
10
+ * formatted version will be stored.
11
+ * @param answerKey - Key name to assign the formatted version into `answers`.
12
+ * @param semverFormatFn - Optional formatter that converts the `SemVer` into
13
+ * the desired string representation (defaults to `semver.toString()`).
14
+ * @returns A human-readable message indicating the fetched version. Throws an
15
+ * `Error` if the fetch fails or yields an invalid version.
16
+ */
17
+ export const fetchLatestSemver = async (fetchSemverFn, answers, answerKey, semverFormatFn = (semver) => semver.toString()) => {
18
+ const version = await fetchSemverFn()
19
+ .andThen((semver) => semver ? okAsync(semver) : errAsync(new Error("Invalid version found")))
20
+ .map(semverFormatFn);
21
+ if (version.isErr()) {
22
+ console.warn(`Could not fetch latest version`);
23
+ throw new Error("Could not fetch latest version", { cause: version.error });
24
+ }
25
+ answers[answerKey] = version.value;
26
+ return `Fetched latest version: ${answers[answerKey]}`;
27
+ };
@@ -0,0 +1,2 @@
1
+ import { type NodePlopAPI } from "node-plop";
2
+ export default function (plop: NodePlopAPI): void;
@@ -0,0 +1,26 @@
1
+ import { $ } from "execa";
2
+ import path from "node:path";
3
+ import { payloadSchema } from "../generators/monorepo/prompts.js";
4
+ export default function (plop) {
5
+ plop.setActionType("setupPnpm", async (data) => {
6
+ const payload = payloadSchema.parse(data);
7
+ const cwd = path.resolve(payload.repoName);
8
+ // If this generator is started by a npm script, it will inherit some
9
+ // config variables that will interfere with pnpm commands.
10
+ // We filter them out here.
11
+ const env = Object.fromEntries(Object.entries(process.env)
12
+ .filter(([key]) => !key.startsWith("npm_config_"))
13
+ // Disable corepack download prompt
14
+ .concat([["COREPACK_ENABLE_DOWNLOAD_PROMPT", "0"]]));
15
+ const pnpm$ = $({
16
+ cwd,
17
+ env,
18
+ extendEnv: false, // Don't include process.env variables
19
+ });
20
+ await pnpm$ `corepack use pnpm@latest`;
21
+ await pnpm$ `pnpm -w add -D turbo @changesets/cli @devcontainers/cli`;
22
+ await pnpm$ `pnpm changeset init`;
23
+ await pnpm$ `pnpm devcontainer templates apply -t ghcr.io/pagopa/devcontainer-templates/node:1`;
24
+ return "Monorepo bootstrapped";
25
+ });
26
+ }
@@ -0,0 +1,2 @@
1
+ import { Payload } from "../prompts.js";
2
+ export declare const getPayload: (includeInit?: boolean) => Payload;
@@ -0,0 +1,55 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import getActions from "../actions.js";
3
+ export const getPayload = (includeInit = false) => {
4
+ const cloudAccount = {
5
+ csp: "azure",
6
+ defaultLocation: "westeurope",
7
+ displayName: "Test-Account",
8
+ id: "test-subscription-id",
9
+ };
10
+ const payload = {
11
+ env: {
12
+ cloudAccounts: [cloudAccount],
13
+ name: "dev",
14
+ prefix: "dx",
15
+ },
16
+ github: {
17
+ owner: "pagopa",
18
+ repo: "dx",
19
+ },
20
+ tags: {},
21
+ workspace: {
22
+ domain: "mytest",
23
+ },
24
+ };
25
+ if (includeInit) {
26
+ payload.init = {
27
+ cloudAccountsToInitialize: [cloudAccount],
28
+ terraformBackend: {
29
+ cloudAccount,
30
+ },
31
+ };
32
+ }
33
+ return payload;
34
+ };
35
+ describe("actions", () => {
36
+ test.each([
37
+ {
38
+ payload: getPayload(true),
39
+ },
40
+ {
41
+ payload: getPayload(false),
42
+ },
43
+ ])("correct order of actions", ({ payload }) => {
44
+ const actionsOrder = ["getTerraformBackend", "addMany", "addMany"];
45
+ if (payload.init) {
46
+ actionsOrder.unshift("initCloudAccounts", "provisionTerraformBackend");
47
+ actionsOrder.push("addMany", "addMany");
48
+ }
49
+ const actions = getActions("/templates/path")(payload);
50
+ const actionTypes = actions
51
+ .filter((action) => typeof action === "object" && Object.hasOwn(action, "type"))
52
+ .map((action) => action.type);
53
+ expect(actionTypes).toEqual(actionsOrder);
54
+ });
55
+ });
@@ -0,0 +1,2 @@
1
+ import type { DynamicActionsFunction } from "node-plop";
2
+ export default function getActions(templatesPath: string): DynamicActionsFunction;
@@ -0,0 +1,54 @@
1
+ import { getLogger } from "@logtape/logtape";
2
+ import * as path from "node:path";
3
+ import { formatTerraformCode } from "../../../terraform/fmt.js";
4
+ import { payloadSchema } from "./prompts.js";
5
+ const addModule = (env, templatesPath) => {
6
+ const cloudAccountsByCsp = Object.groupBy(env.cloudAccounts, (account) => account.csp);
7
+ return (name, terraformBackendKey) => [
8
+ {
9
+ base: templatesPath,
10
+ data: { cloudAccountsByCsp },
11
+ destination: "infra",
12
+ force: true,
13
+ templateFiles: path.join(templatesPath, name),
14
+ transform: formatTerraformCode,
15
+ type: "addMany",
16
+ verbose: true,
17
+ },
18
+ {
19
+ base: path.join(templatesPath, "shared"),
20
+ data: { cloudAccountsByCsp, terraformBackendKey },
21
+ destination: `infra/${name}/{{env.name}}`,
22
+ force: true,
23
+ templateFiles: path.join(templatesPath, "shared"),
24
+ transform: formatTerraformCode,
25
+ type: "addMany",
26
+ verbose: true,
27
+ },
28
+ ];
29
+ };
30
+ export default function getActions(templatesPath) {
31
+ return (payload) => {
32
+ const logger = getLogger(["gen", "env"]);
33
+ logger.debug("payload {payload}", { payload });
34
+ const { env, github, init } = payloadSchema.parse(payload);
35
+ const addEnvironmentModule = addModule(env, templatesPath);
36
+ const actions = [
37
+ {
38
+ type: "getTerraformBackend",
39
+ },
40
+ ...addEnvironmentModule("bootstrapper", `${github.repo}.bootstrapper.${env.name}.tfstate`),
41
+ ];
42
+ if (init) {
43
+ actions.unshift({
44
+ type: "initCloudAccounts",
45
+ }, {
46
+ type: init.terraformBackend
47
+ ? "provisionTerraformBackend"
48
+ : "getTerraformBackend",
49
+ });
50
+ actions.push(...addEnvironmentModule("core", `${env.prefix}.core.${env.name}.tfstate`));
51
+ }
52
+ return actions;
53
+ };
54
+ }
@@ -0,0 +1,3 @@
1
+ import { type NodePlopAPI } from "node-plop";
2
+ import { CloudAccountRepository, CloudAccountService } from "../../../../domain/cloud-account.js";
3
+ export default function (plop: NodePlopAPI, templatesPath: string, cloudAccountRepository: CloudAccountRepository, cloudAccountService: CloudAccountService): void;
@@ -0,0 +1,19 @@
1
+ import setGetTerraformBackend from "../../actions/get-terraform-backend.js";
2
+ import setInitCloudAccountsAction from "../../actions/init-cloud-accounts.js";
3
+ import setProvisionTerraformBackendAction from "../../actions/provision-terraform-backend.js";
4
+ import setEnvShortHelper from "../../helpers/env-short.js";
5
+ import setResourcePrefixHelper from "../../helpers/resource-prefix.js";
6
+ import getActions from "./actions.js";
7
+ import getPrompts from "./prompts.js";
8
+ export default function (plop, templatesPath, cloudAccountRepository, cloudAccountService) {
9
+ setEnvShortHelper(plop);
10
+ setResourcePrefixHelper(plop);
11
+ setGetTerraformBackend(plop, cloudAccountService);
12
+ setProvisionTerraformBackendAction(plop, cloudAccountService);
13
+ setInitCloudAccountsAction(plop, cloudAccountService);
14
+ plop.setGenerator("DX_DeploymentEnvironment", {
15
+ actions: getActions(templatesPath),
16
+ description: "Generate a new deployment environment",
17
+ prompts: getPrompts({ cloudAccountRepository, cloudAccountService }),
18
+ });
19
+ }
@@ -0,0 +1,66 @@
1
+ import inquirer from "inquirer";
2
+ import { type DynamicPromptsFunction } from "node-plop";
3
+ import { CloudAccount, CloudAccountRepository, CloudAccountService, CloudRegion } from "../../../../domain/cloud-account.js";
4
+ type InquirerChoice<T> = inquirer.Separator | {
5
+ name: string;
6
+ value: T;
7
+ };
8
+ import { z } from "zod/v4";
9
+ export declare const workspaceSchema: z.ZodObject<{
10
+ domain: z.ZodDefault<z.ZodString>;
11
+ }, z.core.$strip>;
12
+ export declare const payloadSchema: z.ZodObject<{
13
+ env: z.ZodObject<{
14
+ cloudAccounts: z.ZodArray<z.ZodObject<{
15
+ csp: z.ZodDefault<z.ZodEnum<{
16
+ azure: "azure";
17
+ }>>;
18
+ defaultLocation: z.ZodString;
19
+ displayName: z.ZodString;
20
+ id: z.ZodString;
21
+ }, z.core.$strip>>;
22
+ name: z.ZodEnum<{
23
+ dev: "dev";
24
+ prod: "prod";
25
+ uat: "uat";
26
+ }>;
27
+ prefix: z.ZodString;
28
+ }, z.core.$strip>;
29
+ github: z.ZodObject<{
30
+ owner: z.ZodString;
31
+ repo: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
32
+ }, z.core.$strip>;
33
+ init: z.ZodOptional<z.ZodObject<{
34
+ cloudAccountsToInitialize: z.ZodArray<z.ZodObject<{
35
+ csp: z.ZodDefault<z.ZodEnum<{
36
+ azure: "azure";
37
+ }>>;
38
+ defaultLocation: z.ZodString;
39
+ displayName: z.ZodString;
40
+ id: z.ZodString;
41
+ }, z.core.$strip>>;
42
+ terraformBackend: z.ZodOptional<z.ZodObject<{
43
+ cloudAccount: z.ZodObject<{
44
+ csp: z.ZodDefault<z.ZodEnum<{
45
+ azure: "azure";
46
+ }>>;
47
+ defaultLocation: z.ZodString;
48
+ displayName: z.ZodString;
49
+ id: z.ZodString;
50
+ }, z.core.$strip>;
51
+ }, z.core.$strip>>;
52
+ }, z.core.$strip>>;
53
+ tags: z.ZodRecord<z.ZodString, z.ZodString>;
54
+ workspace: z.ZodObject<{
55
+ domain: z.ZodDefault<z.ZodString>;
56
+ }, z.core.$strip>;
57
+ }, z.core.$strip>;
58
+ export type Payload = z.infer<typeof payloadSchema>;
59
+ export type PromptsDependencies = {
60
+ cloudAccountRepository: CloudAccountRepository;
61
+ cloudAccountService: CloudAccountService;
62
+ };
63
+ declare const prompts: (deps: PromptsDependencies) => DynamicPromptsFunction;
64
+ export declare const getCloudAccountChoices: (cloudAccounts: CloudAccount[]) => InquirerChoice<CloudAccount>[];
65
+ export declare const getCloudLocationChoices: (regions: CloudRegion[]) => InquirerChoice<CloudRegion["name"]>[];
66
+ export default prompts;
@@ -0,0 +1,166 @@
1
+ import * as assert from "node:assert/strict";
2
+ import { cloudAccountSchema, } from "../../../../domain/cloud-account.js";
3
+ import { environmentSchema, getInitializationStatus, hasUserPermissionToInitialize, } from "../../../../domain/environment.js";
4
+ import * as azure from "../../../azure/locations.js";
5
+ import { getLogger } from "@logtape/logtape";
6
+ import { z } from "zod/v4";
7
+ import { githubRepoSchema } from "../../../../domain/github-repo.js";
8
+ import { getGithubRepo } from "../../../github/github-repo.js";
9
+ const initSchema = z.object({
10
+ cloudAccountsToInitialize: z.array(cloudAccountSchema),
11
+ terraformBackend: z
12
+ .object({
13
+ cloudAccount: cloudAccountSchema,
14
+ })
15
+ .optional(),
16
+ });
17
+ const tagsSchema = z.record(z.string(), z.string().min(1));
18
+ export const workspaceSchema = z.object({
19
+ domain: z.string().default(""),
20
+ });
21
+ export const payloadSchema = z.object({
22
+ env: environmentSchema,
23
+ github: githubRepoSchema,
24
+ init: initSchema.optional(),
25
+ tags: tagsSchema,
26
+ workspace: workspaceSchema,
27
+ });
28
+ const prompts = (deps) => async (inquirer) => {
29
+ const logger = getLogger(["gen", "env"]);
30
+ const github = await getGithubRepo();
31
+ assert.ok(github, "This generator only works inside a GitHub repository.");
32
+ logger.debug("github repo {github}", { github });
33
+ const answers = await inquirer.prompt([
34
+ {
35
+ choices: [
36
+ { name: "PROD", value: "prod" },
37
+ { name: "UAT", value: "uat" },
38
+ { name: "DEV", value: "dev" },
39
+ ],
40
+ default: "prod",
41
+ message: "Environment name",
42
+ name: "env.name",
43
+ type: "list",
44
+ },
45
+ {
46
+ choices: [{ name: "Microsoft Azure", value: "azure" }],
47
+ default: ["azure"],
48
+ message: "Cloud provider(s)",
49
+ name: "csp",
50
+ type: "checkbox",
51
+ validate: (value) => Array.isArray(value) && value.length > 0
52
+ ? true
53
+ : "Please select at least one cloud provider.",
54
+ },
55
+ {
56
+ choices: async () => getCloudAccountChoices(await deps.cloudAccountRepository.list()),
57
+ loop: false,
58
+ message: "Account(s)",
59
+ name: "env.cloudAccounts",
60
+ type: "checkbox",
61
+ validate: (value) => Array.isArray(value) && value.length > 0
62
+ ? true
63
+ : "Please select a cloud account.",
64
+ },
65
+ {
66
+ message: "Prefix (2-4 characters)",
67
+ name: "env.prefix",
68
+ transformer: (value) => value.trim().toLowerCase(),
69
+ type: "input",
70
+ validate: (value) => value.length >= 2 && value.length <= 4
71
+ ? true
72
+ : "Please enter a valid prefix.",
73
+ },
74
+ {
75
+ message: "Domain (optional)",
76
+ name: "workspace.domain",
77
+ transformer: (value) => value.trim().toLowerCase(),
78
+ type: "input",
79
+ },
80
+ {
81
+ choices: [
82
+ {
83
+ name: "TECNOLOGIA E SERVIZI",
84
+ value: "TS000",
85
+ },
86
+ ],
87
+ default: "TS000 - Tecnologia e Servizi",
88
+ message: "Cost center",
89
+ name: "tags.CostCenter",
90
+ type: "list",
91
+ validate: (value) => Array.isArray(value) && value.length > 0
92
+ ? true
93
+ : "Please select a Cost Center.",
94
+ },
95
+ {
96
+ message: "Business unit",
97
+ name: "tags.BusinessUnit",
98
+ transformer: (value) => value.trim(),
99
+ validate: (value) => value.trim().length > 0 ? true : "Business Unit cannot be empty.",
100
+ },
101
+ {
102
+ message: "Management team",
103
+ name: "tags.ManagementTeam",
104
+ transformer: (value) => value.trim(),
105
+ validate: (value) => value.trim().length > 0 ? true : "Management Team cannot be empty.",
106
+ },
107
+ ]);
108
+ const payload = payloadSchema.parse({ ...answers, github });
109
+ const locations = await inquirer.prompt(payload.env.cloudAccounts.map((account) => ({
110
+ choices: getCloudLocationChoices(azure.cloudRegions),
111
+ default: azure.defaultLocation,
112
+ message: `Default location for ${account.displayName}`,
113
+ name: account.id,
114
+ type: "list",
115
+ })));
116
+ payload.env.cloudAccounts.forEach((account) => {
117
+ const location = locations[account.id];
118
+ if (location) {
119
+ account.defaultLocation = location;
120
+ }
121
+ });
122
+ const initStatus = await getInitializationStatus(deps.cloudAccountService, payload.env);
123
+ logger.debug("initialization status {initStatus}", { initStatus });
124
+ if (initStatus.initialized) {
125
+ return payload;
126
+ }
127
+ assert.ok(await hasUserPermissionToInitialize(deps.cloudAccountService, payload.env), "You don't have permission to initialize this environment. Ask your Engineering Leader to initialize it for you.");
128
+ const missingRemoteBackend = initStatus.issues.some((issue) => issue.type === "MISSING_REMOTE_BACKEND");
129
+ const initInput = await inquirer.prompt([
130
+ {
131
+ default: true,
132
+ message: "The environment is not initialized. Do you want to initialize it now?",
133
+ name: "init",
134
+ type: "confirm",
135
+ },
136
+ {
137
+ choices: getCloudAccountChoices(payload.env.cloudAccounts),
138
+ message: "Cloud Account to use for the remote Terraform backend",
139
+ name: "terraformBackend.cloudAccount",
140
+ type: "list",
141
+ when: (answers) => answers.init &&
142
+ missingRemoteBackend &&
143
+ payload.env.cloudAccounts.length > 1,
144
+ },
145
+ ]);
146
+ assert.ok(initInput.init, "Can't proceed without initialization");
147
+ payload.init = payloadSchema.shape.init.parse({
148
+ cloudAccountsToInitialize: initStatus.issues
149
+ .filter((issue) => issue.type === "CLOUD_ACCOUNT_NOT_INITIALIZED")
150
+ .map((issue) => issue.cloudAccount),
151
+ terraformBackend: missingRemoteBackend
152
+ ? {
153
+ cloudAccount: initInput.terraformBackend?.cloudAccount ||
154
+ payload.env.cloudAccounts[0],
155
+ }
156
+ : undefined,
157
+ });
158
+ return payload;
159
+ };
160
+ // Creates Inquirer choices, prioritizing those that match the env prefix
161
+ export const getCloudAccountChoices = (cloudAccounts) => cloudAccounts.map((account) => ({
162
+ name: `${account.displayName}`,
163
+ value: account,
164
+ }));
165
+ export const getCloudLocationChoices = (regions) => regions.map((r) => ({ name: r.displayName, value: r.name }));
166
+ export default prompts;
@@ -0,0 +1,51 @@
1
+ declare const getActions: (templatesPath: string) => ({
2
+ type: string;
3
+ repository?: undefined;
4
+ resultKey?: undefined;
5
+ abortOnFail?: undefined;
6
+ base?: undefined;
7
+ destination?: undefined;
8
+ globOptions?: undefined;
9
+ templateFiles?: undefined;
10
+ path?: undefined;
11
+ transform?: undefined;
12
+ } | {
13
+ repository: {
14
+ name: string;
15
+ owner: string;
16
+ };
17
+ resultKey: string;
18
+ type: string;
19
+ abortOnFail?: undefined;
20
+ base?: undefined;
21
+ destination?: undefined;
22
+ globOptions?: undefined;
23
+ templateFiles?: undefined;
24
+ path?: undefined;
25
+ transform?: undefined;
26
+ } | {
27
+ abortOnFail: boolean;
28
+ base: string;
29
+ destination: string;
30
+ globOptions: {
31
+ dot: boolean;
32
+ };
33
+ templateFiles: string;
34
+ type: string;
35
+ repository?: undefined;
36
+ resultKey?: undefined;
37
+ path?: undefined;
38
+ transform?: undefined;
39
+ } | {
40
+ path: string;
41
+ transform: (content: string) => string;
42
+ type: string;
43
+ repository?: undefined;
44
+ resultKey?: undefined;
45
+ abortOnFail?: undefined;
46
+ base?: undefined;
47
+ destination?: undefined;
48
+ globOptions?: undefined;
49
+ templateFiles?: undefined;
50
+ })[];
51
+ export default getActions;
@@ -0,0 +1,35 @@
1
+ import path from "node:path";
2
+ const getActions = (templatesPath) => [
3
+ {
4
+ type: "getNodeVersion",
5
+ },
6
+ {
7
+ repository: { name: "terraform", owner: "hashicorp" },
8
+ resultKey: "terraformVersion",
9
+ type: "fetchGithubRelease",
10
+ },
11
+ {
12
+ repository: { name: "pre-commit-terraform", owner: "antonbabenko" },
13
+ resultKey: "preCommitTerraformVersion",
14
+ type: "fetchGithubRelease",
15
+ },
16
+ {
17
+ abortOnFail: true,
18
+ base: templatesPath,
19
+ destination: "{{repoName}}",
20
+ globOptions: { dot: true },
21
+ templateFiles: path.join(templatesPath),
22
+ type: "addMany",
23
+ },
24
+ {
25
+ path: "{{repoName}}/.gitignore",
26
+ transform: (content) => content
27
+ .trimEnd()
28
+ .concat("\n# Terraform lock files for modules\n**/modules/**/.terraform.lock.hcl\n**/_modules/**/.terraform.lock.hcl\n"),
29
+ type: "modify",
30
+ },
31
+ {
32
+ type: "setupPnpm",
33
+ },
34
+ ];
35
+ export default getActions;
@@ -0,0 +1,6 @@
1
+ import { type NodePlopAPI } from "node-plop";
2
+ import { Octokit } from "octokit";
3
+ import { Payload, payloadSchema } from "./prompts.js";
4
+ export declare const PLOP_MONOREPO_GENERATOR_NAME = "DX_Monorepo";
5
+ export { Payload, payloadSchema };
6
+ export default function (plop: NodePlopAPI, templatesPath: string, octokit: Octokit): void;
@@ -0,0 +1,17 @@
1
+ import setFetchGitHubRelease from "../../actions/fetch-github-release.js";
2
+ import setGetNodeVersionAction from "../../actions/get-node-version.js";
3
+ import setSetupPnpmAction from "../../actions/setup-pnpm.js";
4
+ import getActions from "./actions.js";
5
+ import getPrompts, { payloadSchema } from "./prompts.js";
6
+ export const PLOP_MONOREPO_GENERATOR_NAME = "DX_Monorepo";
7
+ export { payloadSchema };
8
+ export default function (plop, templatesPath, octokit) {
9
+ setSetupPnpmAction(plop);
10
+ setGetNodeVersionAction(plop);
11
+ setFetchGitHubRelease(plop, octokit);
12
+ plop.setGenerator(PLOP_MONOREPO_GENERATOR_NAME, {
13
+ actions: getActions(templatesPath),
14
+ description: "A scaffold for a monorepo repository",
15
+ prompts: getPrompts(),
16
+ });
17
+ }
@@ -0,0 +1,10 @@
1
+ import { type PlopGeneratorConfig } from "node-plop";
2
+ import { z } from "zod/v4";
3
+ export declare const payloadSchema: z.ZodObject<{
4
+ repoDescription: z.ZodOptional<z.ZodString>;
5
+ repoName: z.ZodString;
6
+ repoOwner: z.ZodDefault<z.ZodString>;
7
+ }, z.core.$strip>;
8
+ export type Payload = z.infer<typeof payloadSchema>;
9
+ declare const getPrompts: () => PlopGeneratorConfig["prompts"];
10
+ export default getPrompts;
@@ -0,0 +1,31 @@
1
+ import { z } from "zod/v4";
2
+ export const payloadSchema = z.object({
3
+ repoDescription: z.string().optional(),
4
+ repoName: z.string().trim().min(1, "Repository name cannot be empty"),
5
+ repoOwner: z.string().trim().default("pagopa"),
6
+ });
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
+ const getPrompts = () => [
15
+ {
16
+ message: "Repository name",
17
+ name: "repoName",
18
+ validate: validatePrompt(payloadSchema.shape.repoName),
19
+ },
20
+ {
21
+ default: payloadSchema.shape.repoOwner.def.defaultValue,
22
+ message: "GitHub repository owner (User or Organization)",
23
+ name: "repoOwner",
24
+ validate: validatePrompt(payloadSchema.shape.repoOwner),
25
+ },
26
+ {
27
+ message: "Repository description",
28
+ name: "repoDescription",
29
+ },
30
+ ];
31
+ export default getPrompts;